diff --git a/.moban.cd/changelog.yml b/.moban.cd/changelog.yml index 708cc62a..c6563382 100644 --- a/.moban.cd/changelog.yml +++ b/.moban.cd/changelog.yml @@ -1,6 +1,13 @@ name: moban organisation: moremoban releases: +- changes: + - action: Updated + details: + - "`#146`: added a low-setup usage mode via environment variables to moban" + - "`#148`: include test related files in the package for package validation when distributing via linux system, i.e. OpenSuse" + date: 6-1-2019 + version: 0.3.7 - changes: - action: Updated details: diff --git a/.moban.cd/moban.yml b/.moban.cd/moban.yml index c7ae2e2b..96a4c863 100644 --- a/.moban.cd/moban.yml +++ b/.moban.cd/moban.yml @@ -3,9 +3,9 @@ organisation: moremoban author: C. W. contact: wangc_2011@hotmail.com license: MIT -version: 0.3.6 -current_version: 0.3.6 -release: 0.3.6 +version: 0.3.7 +current_version: 0.3.7 +release: 0.3.7 branch: master command_line_interface: "moban" entry_point: "moban.main:main" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 80320369..e3d12085 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,18 @@ Change log ================================================================================ +0.3.7 - 6-1-2019 +-------------------------------------------------------------------------------- + +Updated +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +#. `#146 `_: added a low-setup + usage mode via environment variables to moban +#. `#148 `_: include test related + files in the package for package validation when distributing via linux + system, i.e. OpenSuse + 0.3.6 - 30-12-2018 -------------------------------------------------------------------------------- diff --git a/MANIFEST.in b/MANIFEST.in index 8a80a6dd..e86ae541 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,6 @@ include README.rst include LICENSE include CHANGELOG.rst -include CONTRIBUTORS.rst \ No newline at end of file +include CONTRIBUTORS.rst +recursive-include tests * +recursive-include docs * diff --git a/README.rst b/README.rst index 44b0d0ed..e699b4f4 100644 --- a/README.rst +++ b/README.rst @@ -91,11 +91,13 @@ Usage -cd CONFIGURATION_DIR, --configuration_dir CONFIGURATION_DIR the directory for configuration file lookup -c CONFIGURATION, --configuration CONFIGURATION - the dictionary file + the dictionary file. if not present, moban + will try to use environment vars as data -td [TEMPLATE_DIR [TEMPLATE_DIR ...]], --template_dir [TEMPLATE_DIR [TEMPLATE_DIR ...]] the directories for template file lookup -t TEMPLATE, --template TEMPLATE - the template file + the template file. this overrides any targets + defined in a custom moban file -o OUTPUT, --output OUTPUT the output file --template_type TEMPLATE_TYPE diff --git a/docs/conf.py b/docs/conf.py index 731a5619..b8bd3a7b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,9 +28,9 @@ author = u'C. W.' # The short X.Y version -version = u'0.3.6' +version = u'0.3.7' # The full version, including alpha/beta/rc tags -release = u'0.3.6' +release = u'0.3.7' # -- General configuration --------------------------------------------------- diff --git a/docs/level-1-jinja2-cli/README.rst b/docs/level-1-jinja2-cli/README.rst index 78d29cde..59bb9b1e 100644 --- a/docs/level-1-jinja2-cli/README.rst +++ b/docs/level-1-jinja2-cli/README.rst @@ -2,7 +2,8 @@ Level 1 Jinja2 on command line ================================================================================ `moban` reads data in yaml format, renders a template file in jinja2 format and -outputs it to `moban.output`. By default, it looks for `data.yml` as its data file +outputs it to `moban.output`. By default, it looks for `data.yml` as its data file, +but it will fallback to environment variables if a data file cannot be found Evaluation -------------------------------------------------------------------------------- diff --git a/moban/_version.py b/moban/_version.py index 64fcab11..2c02a9fe 100644 --- a/moban/_version.py +++ b/moban/_version.py @@ -1,2 +1,2 @@ -__version__ = "0.3.6" +__version__ = "0.3.7" __author__ = "C. W." diff --git a/moban/main.py b/moban/main.py index a453e19b..17e75722 100644 --- a/moban/main.py +++ b/moban/main.py @@ -165,6 +165,7 @@ def handle_command_line(options): options[constants.LABEL_CONFIG], options[constants.LABEL_OUTPUT], ) + engine.report() HASH_STORE.save_hashes() exit_code = reporter.convert_to_shell_exit_code( engine.number_of_templated_files() diff --git a/moban/mobanfile.py b/moban/mobanfile.py index ffff7277..1223db29 100644 --- a/moban/mobanfile.py +++ b/moban/mobanfile.py @@ -37,7 +37,23 @@ def find_default_moban_file(): def handle_moban_file_v1(moban_file_configurations, command_line_options): merged_options = None - target = extract_target(command_line_options) + + targets = moban_file_configurations.get(constants.LABEL_TARGETS) + try: + target = extract_target(command_line_options) + except Exception as exception: + if targets: + template = command_line_options.get(constants.LABEL_TEMPLATE) + for t in targets: + found_template = template in t.values() + if found_template: + target = [dict(t)] + if not found_template: + # Warn user if template not defined under targets in moban file + reporter.report_template_not_in_moban_file(template) + else: + raise exception + if constants.LABEL_CONFIG in moban_file_configurations: merged_options = merge( command_line_options, @@ -52,12 +68,14 @@ def handle_moban_file_v1(moban_file_configurations, command_line_options): if requires: handle_requires(requires) - targets = moban_file_configurations.get(constants.LABEL_TARGETS) if targets: + # If template specified via CLI flag `-t: + # 1. Only update the specified template + # 2. Do not copy if target: - # if command line option exists, append its template to targets - # issue 30 - targets += target + targets = target + if constants.LABEL_COPY in moban_file_configurations: + del moban_file_configurations[constants.LABEL_COPY] number_of_templated_files = handle_targets(merged_options, targets) else: number_of_templated_files = 0 diff --git a/moban/plugins.py b/moban/plugins.py index 544dc2a4..a258fe2a 100644 --- a/moban/plugins.py +++ b/moban/plugins.py @@ -46,6 +46,7 @@ def number_of_templated_files(self): return self.templated_count def render_to_file(self, template_file, data_file, output_file): + self.file_count = 1 data = self.context.get_data(data_file) template = self.engine.get_template(template_file) template_abs_path = utils.get_template_path( @@ -56,6 +57,7 @@ def render_to_file(self, template_file, data_file, output_file): ) if flag: reporter.report_templating(template_file, output_file) + self.templated_count += 1 def apply_template(self, template_abs_path, template, data, output_file): rendered_content = self.engine.apply_template( @@ -183,15 +185,23 @@ def __init__(self, context_dirs): ) def get_data(self, file_name): - file_extension = os.path.splitext(file_name)[1] - if file_extension == ".json": - data = utils.open_json(self.context_dirs, file_name) - elif file_extension in [".yml", ".yaml"]: - data = utils.open_yaml(self.context_dirs, file_name) + try: + file_extension = os.path.splitext(file_name)[1] + if file_extension == ".json": + data = utils.open_json(self.context_dirs, file_name) + elif file_extension in [".yml", ".yaml"]: + data = utils.open_yaml(self.context_dirs, file_name) + else: + raise exceptions.IncorrectDataInput utils.merge(data, self.__cached_environ_variables) - else: - raise exceptions.IncorrectDataInput - return data + return data + except (IOError, exceptions.IncorrectDataInput) as exception: + # If data file doesn't exist: + # 1. Alert the user of their (potential) mistake + # 2. Attempt to use environment vars as data + reporter.report_warning_message(str(exception)) + reporter.report_using_env_vars() + return self.__cached_environ_variables def make_sure_all_pkg_are_loaded(): diff --git a/moban/reporter.py b/moban/reporter.py index bdb5bc42..bbfbab1e 100644 --- a/moban/reporter.py +++ b/moban/reporter.py @@ -12,6 +12,8 @@ MESSAGE_COPIED_ALL = "Copied {0} files." MESSAGE_PULLING_REPO = "Updating {0}..." MESSAGE_CLONING_REPO = "Cloning {0}..." +MESSAGE_USING_ENV_VARS = "Attempting to use environment vars as data..." +MESSAGE_TEMPLATE_NOT_IN_MOBAN_FILE = "{0} is not defined in your moban file!" def report_templating(source_file, destination_file): @@ -47,6 +49,14 @@ def report_error_message(message): print(crayons.white("Error: ", bold=True) + crayons.red(message)) +def report_warning_message(message): + print(crayons.white("Warning: ", bold=True) + crayons.yellow(message)) + + +def report_info_message(message): + print(crayons.white("Info: ") + crayons.green(message)) + + def report_up_to_date(): print(crayons.green(MESSAGE_UP_TO_DATE, bold=True)) @@ -89,6 +99,15 @@ def report_git_clone(repo): print(MESSAGE_CLONING_REPO.format(colored_repo)) +def report_using_env_vars(): + report_warning_message(MESSAGE_USING_ENV_VARS) + + +def report_template_not_in_moban_file(template): + message = MESSAGE_TEMPLATE_NOT_IN_MOBAN_FILE.format(template) + report_warning_message(message) + + def _format_single(message, count): if count == 1: return message.replace("files", "file") diff --git a/setup.py b/setup.py index 27f65c2e..dbaca9af 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ NAME = 'moban' AUTHOR = 'C. W.' -VERSION = '0.3.6' +VERSION = '0.3.7' EMAIL = 'wangc_2011@hotmail.com' LICENSE = 'MIT' ENTRY_POINTS = { @@ -25,7 +25,7 @@ 'Yet another jinja2 cli command for static text generation' ) URL = 'https://github.com/moremoban/moban' -DOWNLOAD_URL = '%s/archive/0.3.6.tar.gz' % URL +DOWNLOAD_URL = '%s/archive/0.3.7.tar.gz' % URL FILES = ['README.rst', 'CONTRIBUTORS.rst', 'CHANGELOG.rst'] KEYWORDS = [ 'python', @@ -60,8 +60,8 @@ # You do not need to read beyond this line PUBLISH_COMMAND = '{0} setup.py sdist bdist_wheel upload -r pypi'.format( sys.executable) -GS_COMMAND = ('gs moban v0.3.6 ' + - "Find 0.3.6 in changelog for more details") +GS_COMMAND = ('gs moban v0.3.7 ' + + "Find 0.3.7 in changelog for more details") NO_GS_MESSAGE = ('Automatic github release is disabled. ' + 'Please install gease to enable it.') UPLOAD_FAILED_MSG = ( diff --git a/tests/fixtures/environ_vars_as_data/test.template b/tests/fixtures/environ_vars_as_data/test.template new file mode 100644 index 00000000..b9590f82 --- /dev/null +++ b/tests/fixtures/environ_vars_as_data/test.template @@ -0,0 +1 @@ +{{ TEST_ENVIRONMENT_VARIABLE }} \ No newline at end of file diff --git a/tests/integration_tests/test_command_line_options.py b/tests/integration_tests/test_command_line_options.py index e0ee5e9c..a4c667c9 100644 --- a/tests/integration_tests/test_command_line_options.py +++ b/tests/integration_tests/test_command_line_options.py @@ -95,7 +95,7 @@ def tearDown(self): os.unlink(self.config_file) -@raises(IOError) +@raises(Exception) def test_missing_configuration(): test_args = ["moban", "-t", "a.jj2"] with patch.object(sys, "argv", test_args): @@ -143,14 +143,7 @@ def test_single_command_with_a_few_options(self, fake_template_doer): main() call_args = list(fake_template_doer.call_args[0][0]) - eq_( - call_args, - [ - ("README.rst.jj2", "data.yaml", "README.rst"), - ("setup.py.jj2", "data.yaml", "setup.py"), - ("abc.jj2", "data.yaml", "xyz.output"), - ], - ) + eq_(call_args, [("abc.jj2", "data.yaml", "xyz.output")]) @patch("moban.plugins.BaseEngine.render_to_files") def test_single_command_with_options(self, fake_template_doer): @@ -168,17 +161,9 @@ def test_single_command_with_options(self, fake_template_doer): main() call_args = list(fake_template_doer.call_args[0][0]) - eq_( - call_args, - [ - ("README.rst.jj2", "new.yml", "README.rst"), - ("setup.py.jj2", "new.yml", "setup.py"), - ("abc.jj2", "new.yml", "xyz.output"), - ], - ) + eq_(call_args, [("abc.jj2", "new.yml", "xyz.output")]) @raises(Exception) - @patch("moban.plugins.BaseEngine.render_to_files") def test_single_command_without_output_option(self, fake_template_doer): test_args = ["moban", "-t", "abc.jj2"] with patch.object(sys, "argv", test_args): @@ -265,6 +250,44 @@ def tearDown(self): os.unlink(self.data_file) +class TestTemplateOption: + def setUp(self): + self.config_file = "custom-moban.txt" + copyfile( + os.path.join("tests", "fixtures", ".moban.yml"), self.config_file + ) + self.patcher1 = patch( + "moban.plugins.verify_the_existence_of_directories" + ) + self.patcher1.start() + + @patch("moban.plugins.BaseEngine.render_to_file") + def test_template_option_override_moban_file(self, fake_template_doer): + test_args = ["moban", "-t", "setup.py.jj2"] + with patch.object(sys, "argv", test_args): + from moban.main import main + + main() + fake_template_doer.assert_called_with( + "setup.py.jj2", "data.yml", "moban.output" + ) + + @patch("moban.plugins.BaseEngine.render_to_file") + def test_template_option_not_in_moban_file(self, fake_template_doer): + test_args = ["moban", "-t", "foo.jj2"] + with patch.object(sys, "argv", test_args): + from moban.main import main + + main() + fake_template_doer.assert_called_with( + "foo.jj2", "data.yml", "moban.output" + ) + + def tearDown(self): + self.patcher1.stop() + os.unlink(self.config_file) + + @patch("moban.plugins.verify_the_existence_of_directories") def test_duplicated_targets_in_moban_file(fake_verify): config_file = "duplicated.moban.yml" diff --git a/tests/test_context.py b/tests/test_context.py index c1177b89..53d03570 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -18,3 +18,21 @@ def test_environ_variables(): context = Context(os.path.join("tests", "fixtures")) data = context.get_data("simple.yaml") eq_(data[test_var], test_value) + + +def test_json_data_overrides_environ_variables(): + test_var = "TEST_ENVIRONMENT_VARIABLE" + test_value = "am I found" + os.environ[test_var] = test_value + context = Context(os.path.join("tests", "fixtures")) + data = context.get_data("simple.json") + eq_(data[test_var], test_value) + + +def test_unknown_data_file(): + test_var = "TEST_ENVIRONMENT_VARIABLE" + test_value = "am I found" + os.environ[test_var] = test_value + context = Context(os.path.join("tests", "fixtures")) + data = context.get_data("unknown.data") + eq_(data[test_var], test_value) diff --git a/tests/test_docs.py b/tests/test_docs.py index 80c8fdc2..1a4470b4 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -120,7 +120,10 @@ def _moban(self, folder, expected): def _raw_moban(self, args, folder, expected, output): os.chdir(os.path.join("docs", folder)) with patch.object(sys, "argv", args): - main() + try: + main() + except SystemExit as e: + eq_("1", str(e)) _verify_content(output, expected) os.unlink(output) diff --git a/tests/test_engine.py b/tests/test_engine.py index 9d8d46eb..521be93d 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -99,3 +99,17 @@ def test_nested_global_template_variables(): content = output_file.read() eq_(content, "template: nested.template\ntarget: test.txt\nhere") os.unlink(output) + + +def test_environ_variables_as_data(): + test_var = "TEST_ENVIRONMENT_VARIABLE" + test_value = "foo" + os.environ[test_var] = test_value + output = "test.txt" + path = os.path.join("tests", "fixtures", "environ_vars_as_data") + engine = BaseEngine([path], path, Engine) + engine.render_to_file("test.template", "this_does_not_exist.yml", output) + with open(output, "r") as output_file: + content = output_file.read() + eq_(content, "foo") + os.unlink(output) diff --git a/tests/test_main.py b/tests/test_main.py index 3ae6b72b..308e8b64 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -108,6 +108,20 @@ def test_no_third_party_engine( with patch.object(sys, "argv", ["moban"]): main() + @raises(SystemExit) + @patch("os.path.exists") + @patch("moban.main.handle_moban_file") + @patch("moban.reporter.report_error_message") + def test_double_underscore_main( + self, fake_reporter, fake_moban_file, fake_file + ): + fake_file.return_value = True + fake_moban_file.side_effect = exceptions.DirectoryNotFound + from moban.__main__ import main + + with patch.object(sys, "argv", ["moban"]): + main() + class TestExitCodes: def setUp(self): diff --git a/tests/test_reporter.py b/tests/test_reporter.py index b5757362..5e60daeb 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -36,6 +36,22 @@ def test_error_message(): eq_(fake_stdout.getvalue(), "Error: something wrong\n") +def test_info_message(): + patcher = patch("sys.stdout", new_callable=StringIO) + fake_stdout = patcher.start() + reporter.report_info_message("for your information") + patcher.stop() + eq_(fake_stdout.getvalue(), "Info: for your information\n") + + +def test_warning_message(): + patcher = patch("sys.stdout", new_callable=StringIO) + fake_stdout = patcher.start() + reporter.report_warning_message("Maybe you wanna know") + patcher.stop() + eq_(fake_stdout.getvalue(), "Warning: Maybe you wanna know\n") + + def test_report_templating(): patcher = patch("sys.stdout", new_callable=StringIO) fake_stdout = patcher.start() @@ -56,3 +72,14 @@ def test_format_single(): message = "1 files" ret = reporter._format_single(message, 1) eq_(ret, "1 file") + + +def test_report_template_not_in_moban_file(): + patcher = patch("sys.stdout", new_callable=StringIO) + fake_stdout = patcher.start() + reporter.report_template_not_in_moban_file("test.jj2") + patcher.stop() + eq_( + fake_stdout.getvalue(), + "Warning: test.jj2 is not defined in your moban file!\n", + ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 13176cc7..cd3a4bf8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,6 +9,7 @@ from moban.utils import ( mkdir_p, + open_json, get_repo_name, write_file_out, file_permissions, @@ -169,3 +170,9 @@ def test_get_repo_name(): actual = [get_repo_name(repo) for repo in repos] expected = ["repo", "repo"] eq_(expected, actual) + + +def test_open_json(): + content = open_json(os.path.join("tests", "fixtures"), "child.json") + expected = {"key": "hello world", "pass": "ox"} + eq_(expected, content)