Skip to content
10 changes: 3 additions & 7 deletions source/analysis/top_gdp/topgdp_value.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
from pathlib import Path
import pandas as pd
from source.lib.JMSLab.autofill import GenerateAutofillMacros
from source.lib.JMSLab.autofill import AutoFill

def Main():
instub = Path("output/derived/wb_clean")
outstub = Path("output/analysis/top_gdp")

df = pd.read_csv(instub / "gdp_education.csv")
TopGDPValue = df['GDP_2010'].sort_values(ascending=False).iloc[0]
top_gdp_value = df['GDP_2010'].sort_values(ascending=False).iloc[0]

GenerateAutofillMacros(
["TopGDPValue"],
"{:,.0f}",
outstub / "top_gdp.tex"
)
AutoFill({"TopGDPValue": top_gdp_value}, outstub / "top_gdp.tex", "{:,.0f}")
Comment thread
zhizhongpu marked this conversation as resolved.


if __name__ == '__main__':
Expand Down
110 changes: 57 additions & 53 deletions source/lib/JMSLab/autofill.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
import inspect

def Autofill(var, format = "{}", namespace = None):
newcommand = f"\\newcommand{{{{\\{{}}}}}}{{{{{format}}}}}\n"

# Look for var in parent frames
if namespace is None:
parent = inspect.currentframe().f_back
while var not in parent.f_locals.keys() and parent.f_back:
parent = parent.f_back

if var not in parent.f_locals.keys() or parent is None:
raise Exception(f"Autofill: Variable '{var}' not found")

namespace = parent.f_locals

commandname, content = var, namespace[var]

return newcommand.format(commandname, content)

def GenerateAutofillMacros(autofill_lists, autofill_formats = "{:.2f}", autofill_outfile = "autofill.tex"):
'''

Parameters
----------
autofill_lists : TYPE - list
autofill_formats : TYPE - str or list
autofill_outfile : TYPE - str

Returns
-------
.tex file

'''
if type(autofill_lists) != list:
raise Exception("Argument 'autofill_lists' must be list")

nested_list = any(isinstance(i, list) for i in autofill_lists)

if ((nested_list and type(autofill_formats) is str)
or (not nested_list and type(autofill_formats) is list)):
raise Exception("Arguments 'autofill_lists' and 'autofill_formats' are incompatible")

autofill_file = open(autofill_outfile, 'w')

if type(autofill_formats) == str:
output_macros = ''.join(Autofill(autofill_var, autofill_formats) for autofill_var in autofill_lists)
from pathlib import Path
from typing import Any, Literal


def AutoFill(
macros: dict[str, Any] | list[str],
outfile: str | Path,
format: str | list[str | None] | None = None,
Comment thread
liaochris marked this conversation as resolved.
append: bool = False,
mode: Literal["math", "text"] = "math",
) -> None:
if isinstance(macros, dict):
macro_values = macros
elif isinstance(macros, list):
caller_frame = inspect.currentframe().f_back # type: ignore
macro_values = {}
for var in macros:
frame = caller_frame
found = False
while frame is not None:
found, value = _LookupVar(var, frame)
if found:
break
frame = frame.f_back
if not found:
raise Exception(f"AutoFill: Variable '{var}' not found")
macro_values[var] = value
else:
autofill_macros = []
for autofill_list, autofill_format in zip(autofill_lists, autofill_formats):
autofill_macros.append(''.join(Autofill(autofill_var, autofill_format) for autofill_var in autofill_list))
output_macros = ''.join(autofill_macros)

autofill_file.write(output_macros)
autofill_file.close()
raise Exception("Argument 'macros' must be a dict or list")

if isinstance(format, list):
if len(format) != len(macro_values):
raise Exception("AutoFill: 'format' list length must match number of macros")
formats = format
else:
formats = [format] * len(macro_values)

output = "".join(
_FormatMacro(name, value, fmt, mode)
for (name, value), fmt in zip(macro_values.items(), formats)
)
open_mode = "a" if append else "w"
with open(outfile, open_mode) as f:
f.write(output)


def _LookupVar(name: str, frame) -> tuple[bool, Any]:
if name in frame.f_locals:
return True, frame.f_locals[name]
if name in frame.f_globals:
return True, frame.f_globals[name]
return False, None


def _FormatMacro(name: str, value: Any, format: str | None, mode: str) -> str:
formatted = format.format(value) if format is not None else str(value)
if mode == "text":
return f"\\newcommand{{\\{name}}}{{\\textnormal{{{formatted}}}}}\n"
return f"\\newcommand{{\\{name}}}{{{formatted}}}\n"
135 changes: 86 additions & 49 deletions source/lib/JMSLab/tests/test_autofill.py
Comment thread
liaochris marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,69 +1,106 @@
from unittest import main, TestCase
from os.path import exists
import os
import tempfile
import shutil
from pathlib import Path

import tempfile, shutil
from ..autofill import AutoFill

from ..autofill import GenerateAutofillMacros

class Test(TestCase):
class TestOutputContent(TestCase):

def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.outfile = self.tempdir + r"output_macros.tex"
return None
self.tempdir = tempfile.mkdtemp()
self.outfile = os.path.join(self.tempdir, "output_macros.tex")

def tearDown(self):
shutil.rmtree(self.tempdir)
return

def test_file_exists(self):
Epsilon = -1.19

GenerateAutofillMacros(["Epsilon"], autofill_outfile = self.outfile)
self.assertTrue(exists(self.outfile))
return None
def test_dict_output(self):
for outfile in [self.outfile, Path(self.outfile)]:
AutoFill({"NegativePi": -3.1415, "Pi": 3.1415}, outfile, "{:.2f}")
content = Path(outfile).read_text()
self.assertEqual(content, "\\newcommand{\\NegativePi}{-3.14}\n\\newcommand{\\Pi}{3.14}\n")

def test_exception(self):
with self.assertRaises(Exception) as context:
GenerateAutofillMacros(["Epsilon"], autofill_outfile = self.outfile)

self.assertTrue("Autofill: Variable 'Epsilon' not found" in str(context.exception))
return None

def test_output(self):
Epsilon = - 1.19
MarginalCost = 2.59

GenerateAutofillMacros([["Epsilon"], ["MarginalCost"]],
autofill_formats = ["{:.2f}", "\\textnormal{{{:.2f}}}"],
autofill_outfile = self.outfile)
tex_file = open(self.outfile, 'r')
content = tex_file.read()
self.assertEqual(content, "\\newcommand{\\Epsilon}{-1.19}\n\\newcommand{\\MarginalCost}{\\textnormal{2.59}}\n")
tex_file.close()
return None

def test_list_format(self):
Epsilon = - 1.19
MarginalCost = 2.59
def test_list_output(self):
NegativePi = -3.1415
Pi = 3.1415
AutoFill(["NegativePi", "Pi"], self.outfile, "{:.2f}")
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\NegativePi}{-3.14}\n\\newcommand{\\Pi}{3.14}\n")

with self.assertRaises(Exception) as context:
GenerateAutofillMacros("Epsilon", autofill_outfile = self.outfile)
def test_none_format_numeric(self):
AutoFill({"NegativePi": -3.1415, "Pi": 3.1415}, self.outfile)
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\NegativePi}{-3.1415}\n\\newcommand{\\Pi}{3.1415}\n")

def test_none_format_text(self):
AutoFill({"SampleStart": "January 2010"}, self.outfile)
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\SampleStart}{January 2010}\n")

self.assertTrue("Argument 'autofill_lists' must be list" in str(context.exception))

with self.assertRaises(Exception) as context:
GenerateAutofillMacros(["Epsilon"], autofill_formats = ["{:.2f}", "\\textnormal{{{:.2f}}}"],
autofill_outfile = self.outfile)
class TestModeAndFormat(TestCase):

def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.outfile = os.path.join(self.tempdir, "output_macros.tex")

def tearDown(self):
shutil.rmtree(self.tempdir)

self.assertTrue("Arguments 'autofill_lists' and 'autofill_formats' are incompatible" in str(context.exception))
def test_text_mode(self):
AutoFill({"Pi": 3.1415}, self.outfile, "{:.2f}", mode="text")
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\Pi}{\\textnormal{3.14}}\n")

def test_format_list(self):
AutoFill({"NegativePi": -3.1415, "IntegerPi": 3.1415}, self.outfile, ["{:.2f}", "{:.0f}"])
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\NegativePi}{-3.14}\n\\newcommand{\\IntegerPi}{3}\n")


class TestFileBehavior(TestCase):

def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.outfile = os.path.join(self.tempdir, "output_macros.tex")

def tearDown(self):
shutil.rmtree(self.tempdir)

def test_append(self):
AutoFill({"NegativePi": -3.1415}, self.outfile, "{:.2f}")
AutoFill({"Pi": 3.1415}, self.outfile, "{:.2f}", append=True)
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\NegativePi}{-3.14}\n\\newcommand{\\Pi}{3.14}\n")

def test_overwrite_without_append(self):
AutoFill({"NegativePi": -3.1415}, self.outfile, "{:.2f}")
AutoFill({"Pi": 3.1415}, self.outfile, "{:.2f}")
content = Path(self.outfile).read_text()
self.assertEqual(content, "\\newcommand{\\Pi}{3.14}\n")


class TestErrors(TestCase):

def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.outfile = os.path.join(self.tempdir, "output_macros.tex")

def tearDown(self):
shutil.rmtree(self.tempdir)

def test_list_variable_not_found(self):
with self.assertRaises(Exception) as context:
GenerateAutofillMacros([["Epsilon"], ["MarginalCost"]], autofill_outfile = self.outfile)
AutoFill(["NegativePi"], outfile=self.outfile)
self.assertIn("AutoFill: Variable 'NegativePi' not found", str(context.exception))

def test_invalid_macros_type(self):
with self.assertRaises(Exception) as context:
AutoFill("NegativePi", outfile=self.outfile)
self.assertIn("Argument 'macros' must be a dict or list", str(context.exception))


self.assertTrue("Arguments 'autofill_lists' and 'autofill_formats' are incompatible" in str(context.exception))
return None

if __name__ == '__main__':
main()

Loading