"""
Tests for the macro expansion machinery.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


import os

from gavo.helpers import testhelpers

from gavo import base
from gavo import grammars
from gavo import rscdef
from gavo import rscdesc
from gavo.base import macros


def getCleaned(rawIter):
	"""returns cleaned rawdicts form a rawdict iterator

	(this currently just kills the parser_ key).
	"""
	res = []
	for d in rawIter:
		del d["parser_"]
		res.append(d)
	return res


class NakedMacroTest(testhelpers.VerboseTest):
	"""tests for macros.py's MacroExpander.
	"""
	class SimplePackage(macros.MacroPackage):
		def getSourcePosition(self):
			return "(testing)"

		def macro_noArg(self):
			return "foo"
		def macro_oneArg(self, arg):
			return arg
		def macro_twoArg(self, arg1, arg2):
			return arg1+arg2
		def macro_something(self):
			return "it's something"

	def testBasic(self):
		me = macros.MacroExpander(self.SimplePackage())
		for unEx, ex in [
				("No macro calls in here", "No macro calls in here"),
				(r"\noArg", "foo"),
				(r"\\noArg expands to \noArg", r"\noArg expands to foo"),
				(r'\oneArg{bla}', r"bla"),
				(r'\quote{\oneArg{bla}}', '"bla"'),
				(r'\oneArg{\oneArg{bla}}', 'bla'),
				(r'Here is \twoArg{\quote{ba\{\}r}}{\noArg}', 'Here is "ba{}r"foo'),
				(r'Here is \twoArg{bar}{\noArg}', "Here is barfoo"),
				# we probably wouldn't want the following, but it's hard to work around.
				(r'Lots \ of \@ weirdness', "Lots \\ of \\@ weirdness"),
			]:
			self.assertEqual(me.expand(unEx), ex)

	def testWhitespace(self):
		me = self.SimplePackage().getExpander()
		for unEx, ex in [
				(r"There is \noArg\+whitespace here", "There is foowhitespace here"),
				(r"There is \noArg whitespace here", "There is foo whitespace here"),
				("Two\nlines", "Two\nlines"),
				("One\\\nline", "One line"),
			]:
			self.assertEqual(me.expand(unEx), ex)

	def testInsideOut(self):
		me = self.SimplePackage().getExpander()
		self.assertEqual(me.expand(r"\sqlquote{\something}"),
			"'it''s something'")

	def testErrors(self):
		me = self.SimplePackage().getExpander()
		self.assertRaisesWithMsg(base.MacroError,
			'In Element at (testing): No macro \\unknown available in a SimplePackage context',
			me.expand, (r"an \unknown Macro",))
		self.assertRaisesWithMsg(base.MacroError,
			'In Element at unknown position: Invalid macro arguments to \\quote: []',
			me.expand, (r"\quote takes an argument",))


class ExpandedAttributeTest(testhelpers.VerboseTest):
	"""tests for macro expansion in structure arguments.
	"""


class MacDefTest(testhelpers.VerboseTest):
	def testExpansion(self):
		res = base.parseFromString(rscdesc.RD, """<resource schema="test">
			<macDef name="yx">x-panded</macDef></resource>""")
		self.assertEqual(res.expand(r"\yx"), "x-panded")

	def testMinLength(self):
		self.assertRaisesWithMsg(base.StructureError,
			'At IO:\'<resource schema="test"> <macDef name="x">gurk</macDe...\', (2, 25):'
			" 'x' is not a valid value for name",
			base.parseFromString,
			(rscdesc.RD, """<resource schema="test">
				<macDef name="x">gurk</macDef></resource>"""))

	def testNoJunk(self):
		self.assertRaisesWithMsg(base.StructureError,
			'At IO:\'<resource schema="test"> <macDef name="yx"><column na...\', (2, 22):'
			" macDef elements have no column attributes or children.",
			base.parseFromString,
			(rscdesc.RD, """<resource schema="test">
				<macDef name="yx"><column name="u"/></macDef></resource>"""))


class _FakeRowIterator(grammars.RowIterator):
	def _iterRows(self):
		yield {"fake": "sure"}
		del self.grammar


class _FakeFileGrammar(grammars.Grammar):
	"""A grammar for testing purposes.

	The source token is a string supposed to be a file name.  The row
	iterator returns a constant row {"fake": "sure"}.
	"""
	name_ = "fakeFileGrammar"
	rowIterator = _FakeRowIterator


class GrammarMacroTest(testhelpers.VerboseTest):
	def _testOne(self, macroDef, input, result):
		g = base.parseFromString(_FakeFileGrammar, """<fakeFileGrammar>
			<rowfilter><setup>
			<par late="True" name="res">%s</par></setup>
			<code>
				row["res"] = res
				yield row
			</code></rowfilter></fakeFileGrammar>"""%macroDef)
		irp = os.path.join(base.getConfig("inputsDir"), input)
		expectedRow = {'fake': 'sure', 'res': result}
		if isinstance(result, Exception):
			self.assertRaises(result.__class__,
				lambda: getCleaned(g.parse(irp)))
		else:
			self.assertEqual(getCleaned(g.parse(irp)),
				[expectedRow])

	def testInputRelative(self):
		self._testOne(r"\inputRelativePath", "foo.one", "foo.one")

	def testNonStrict(self):
		self._testOne(r"\inputRelativePath", "foo+ .one&", "foo+ .one&")

	def testStrict(self):
		self._testOne(r"\inputRelativePath{False}", "foo+ .one&", ValueError())

	def testOffInputs(self):
		self._testOne(r"\inputRelativePath", "/etc/passwd", ValueError())


class _PlainExpander(testhelpers.TestResource):
	def make(self, deps):
		return macros.StandardMacroMixin()


class StandardMacroTest(testhelpers.VerboseTest):
	# these are just for some that are missing coverage from elsewhere in
	# the unit tests.
	resources = [("expander", _PlainExpander())]

	def testServicelinkOneArg(self):
		self.assertEqual(
			self.expander.expand(r"\RSTservicelink{test/q/s/form}"),
			"`test/q/s/form </test/q/s/form>`_")

	def testServicelinkTwoArgs(self):
		self.assertEqual(
			self.expander.expand(r"\RSTservicelink{test/q/s/form}{testing 123}"),
			"`testing 123 </test/q/s/form>`_")

	def testRSTtable(self):
		self.assertEqual(
			self.expander.expand(r"\RSTtable{tap_schema.table}"),
			"`tap_schema.table </tableinfo/tap_schema.table>`_")

	def testUrlquote(self):
		self.assertEqual(
			self.expander.expand(r"\urlquote{Käse=cheese?}"),
			"K%C3%A4se%3Dcheese%3F")
	
	def testToday(self):
		import datetime
		self.assertEqual(
			self.expander.expand(r"\today"),
			str(datetime.date.today()))

	def testIfFalse(self):
		self.assertEqual(self.expander.expand(r"\if{}{something}"), "")
		self.assertEqual(self.expander.expand(r"\if{0}{something}"), "")
		self.assertEqual(
			self.expander.expand(r"\if{\reSub{aa}{}{aa}}{something}"), "")

	def testIfTrue(self):
		self.assertEqual(
			self.expander.expand(r"\if{x}{something}"), "something")
		self.assertEqual(
			self.expander.expand(r"\if{1}{something}"), "something")
		self.assertEqual(
			self.expander.expand(r"\if{\reSub{aa}{x}{aa}}{\reSub{x}{aa}{x}}"), "aa")


class RDMacroTest(testhelpers.VerboseTest):
	def testSundry(self):
		rd = base.parseFromString(rscdesc.RD, r"""<resource schema="test">
			<macDef name="base">Foo Bar</macDef>
			<meta name="testing" format="rst">Go to
			\internallink{h/e/l/l}</meta>
			<meta name="lowername">\decapitalize{\base}</meta>
			<meta name="alllower">\lower{\base}</meta>
			</resource>""")
		self.assertTrue("</a>" in
			rd.getMeta("testing").getContent(targetFormat="html", macroPackage=rd))
		self.assertEqual(base.getMetaText(rd, "lowername"),
			"foo Bar")
		self.assertEqual(base.getMetaText(rd, "alllower"),
			"foo bar")
	
	def testRaw(self):
		rd = base.parseFromString(rscdesc.RD, r"""<resource schema="test">
			<macDef raw="True" name="base">\rdId</macDef>
			<meta name="something">\base</meta>
			</resource>""")
		self.assertEqual(base.getMetaText(rd, "something"), "\\rdId")


	def testResdir(self):
		rd = base.parseFromString(rscdesc.RD, r"""<resource schema="test"
			resdir="mod1"><service id="x" allowed="static">
				<nullCore/>
				<property name="staticData">\resdir/data</property>
				</service></resource>""")
		self.assertEqual(rd.getById("x").getProperty("staticData"),
			"mod1/data")

	def testInheritance(self):
		rd = base.parseFromString(rscdesc.RD, r"""<resource schema="test">
			<macDef name="foo">expanded</macDef>
			<data id="isfoo"><meta name="description">\foo</meta></data>
			</resource>""")
		self.assertEqual(
			base.getMetaText(rd.getById("isfoo"), "description"),
			"expanded")

	def testMacroExpandedInMacdef(self):
		rd = base.parseFromString(rscdesc.RD, r"""<resource schema="test">
			<macDef name="idBase">ivo://\getConfig{ivoa}{authority}/dag</macDef>
			<meta name="testing">\idBase/knatter</meta></resource>""")
		self.assertEqual(
			base.getMetaText(rd, "testing"), "ivo://x-testing/dag/knatter")

	def testSensibleArgErrorMessage(self):
		exc = self.assertRaisesWithMsg(
			base.MacroError,
			'In Element at IO:\'<resource schema="test"> <macDef name="idBase">\\reSub...\', line 2: Invalid macro arguments to \\reSub: [\'arg\', \'barg\']',
			base.parseFromString,
			(rscdesc.RD, r"""<resource schema="test">
				<macDef name="idBase">\reSub{arg}{barg}</macDef></resource>"""))
		self.assertEqual(
			str(exc.pos),
			'IO:\'<resource schema="test"> <macDef name="idBase">\\reSub...\', line 2')

	def testInternalErrorBehaviour(self):
		# should we wrap such errors coming from DaCHS' bowels during
		# macro expansion to help people figure out where they come from?
		self.assertRaisesWithMsg(
			base.MacroError,
			'In Element at IO:\'<resource schema="test"> <macDef name="idBase">\\reSub...\', line 3: While expanding macro \\reSub{**}{barg}{foo}: nothing to repeat at position 0',
			base.parseFromString,
			(rscdesc.RD, r"""<resource
					schema="test">
				<macDef name="idBase">\reSub{**}{barg}{foo}</macDef></resource>"""))


class TableMacroTest(testhelpers.VerboseTest):
	def testgetParamNull(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="test"><param name="foo"/></table>""")
		self.assertEqual(t.expand(r"\getParam{foo}"), "NULL")

	def testGetNonexPar(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="test"><param name="foo"/></table>""")
		self.assertEqual(t.expand(r"\getParam{bar}"), "")

	def testGetFloatPar(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="test"><param name="foo">0.3</param></table>""")
		self.assertEqual(t.expand(r"\getParam{foo}"), "0.3")

	def testGetDatePar(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="test"><param name="foo" type="timestamp"
				>1975-03-02</param></table>""")
		self.assertEqual(t.expand(r"\getParam{foo}"), "1975-03-02")

	def testSQLQuote(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="test"><param name="foo" type="text"
				>bar's param</param></table>""")
		self.assertEqual(t.expand(r"\sqlquote{\getParam{foo}}"),
			"'bar''s param'")
		self.assertEqual(t.expand(r"\sqlquote{\getParam{fake}{NULL}}"),
			"NULL")
		self.assertEqual(t.expand(r"\sqlquote{'%replace this%}"),
			"'''%%replace this%%'")


	def testSQLPubDID(self):
		t = base.parseFromString(rscdef.TableDef,
			r"""<table id="test"><column name="accref"/>
				<viewStatement>\sql_standardPubDID as withaccref,
					\sql_standardPubDID{wurz} as withwurz</viewStatement></table>""")
		self.assertEqual(t.expand(t.viewStatement),
			 "'ivo://x-testing/~?' || gavo_urlescape(accref) as withaccref,\n"
			 "\t\t\t\t\t'ivo://x-testing/~?' || gavo_urlescape(wurz) as withwurz")

	def testColNamesPlain(self):
		t = base.parseFromString(rscdef.TableDef,
			r"""<table id="test"><column name="accref"/><column name="barz"/>
				<column name="woo"/>
				<viewStatement>\colNames</viewStatement></table>""")
		self.assertEqual(t.expand(t.viewStatement),
			 "accref, barz, woo")

	def testColNamesRedacted(self):
		t = base.parseFromString(rscdef.TableDef,
			r"""<table id="test"><column name="accref"/><column name="barz"/>
				<column name="woo"/>
				<viewStatement>\reSub{, barz, }{}{\colNames}</viewStatement>
				</table>""")
		self.assertEqual(t.expand(t.viewStatement),
			 "accrefwoo")


if __name__=="__main__":
	testhelpers.main(TableMacroTest)
