diff --git a/.moban.cd/changelog.yml b/.moban.cd/changelog.yml index c6563382..a8b54c02 100644 --- a/.moban.cd/changelog.yml +++ b/.moban.cd/changelog.yml @@ -1,6 +1,14 @@ name: moban organisation: moremoban releases: +- changes: + - action: Updated + details: + - "`#141`: disable file permissions copy feature and not to check file permission changes on windows." + - "`#154`: introduce first ever positional argument for string base template." + - "`#157`: the exit code behavior changed. for backward compactibility please use --exit-code. Otherwise, moban will not tell if there is any changes." + date: 12-1-2019 + version: 0.3.8 - changes: - action: Updated details: diff --git a/.moban.cd/moban.yml b/.moban.cd/moban.yml index 96a4c863..e354076a 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.7 -current_version: 0.3.7 -release: 0.3.7 +version: 0.3.8 +current_version: 0.3.8 +release: 0.3.8 branch: master command_line_interface: "moban" entry_point: "moban.main:main" diff --git a/.travis.yml b/.travis.yml index 8295c536..16330f5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ python: - 3.7-dev - 3.6 - 3.5 - - 3.4 - 2.7 before_install: - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e3d12085..0b9b467e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,20 @@ Change log ================================================================================ +0.3.8 - 12-1-2019 +-------------------------------------------------------------------------------- + +Updated +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +#. `#141 `_: disable file + permissions copy feature and not to check file permission changes on windows. +#. `#154 `_: introduce first ever + positional argument for string base template. +#. `#157 `_: the exit code + behavior changed. for backward compactibility please use --exit-code. + Otherwise, moban will not tell if there is any changes. + 0.3.7 - 6-1-2019 -------------------------------------------------------------------------------- diff --git a/README.rst b/README.rst index e699b4f4..00cdd090 100644 --- a/README.rst +++ b/README.rst @@ -50,7 +50,25 @@ or clone it and install it: Quick start ================================================================================ -Here is a simple example: +.. code-block:: bash + + $ export HELLO="world" + $ moban "{{HELLO}}" + Warning: Both data.yml and /.../.moban.cd/data.yml does not exist + Warning: Attempting to use environment vars as data... + Templating {{HELLO}}... to moban.output + Templated 1 file. + $ cat moban.output + world + +Or simply + +.. code-block:: bash + + $ HELLO="world" moban "{{HELLO}}" + + +A bit formal example: .. code-block:: bash @@ -69,6 +87,8 @@ moban.output will contain:: world +Please note that data.yml will take precedence over environment variables. + `the tutorial`_ has more use cases. .. _the tutorial: http://moban.readthedocs.org/en/latest/#tutorial @@ -77,15 +97,19 @@ moban.output will contain:: Usage ================================================================================ -:: +.. code-block:: bash usage: moban [-h] [-cd CONFIGURATION_DIR] [-c CONFIGURATION] [-td [TEMPLATE_DIR [TEMPLATE_DIR ...]]] [-t TEMPLATE] [-o OUTPUT] [-f] [-m MOBANFILE] - + [template] + Yet another jinja2 cli command for static text generation + positional arguments: + template string templates + optional arguments: -h, --help show this help message and exit -cd CONFIGURATION_DIR, --configuration_dir CONFIGURATION_DIR @@ -104,12 +128,19 @@ Usage the template type, default is jinja2 -f force moban to template all files despite of .moban.hashes + --exit-code tell moban to change exit code -m MOBANFILE, --mobanfile MOBANFILE custom moban file -exit codes +Exit codes -------------------------------------------------------------------------------- +By default: + +- 0 : no changes +- 1 : error occured + +With `--exit-code`: - 0 : no changes - 1 : has changes diff --git a/docs/conf.py b/docs/conf.py index b8bd3a7b..ce753fd0 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.7' +version = u'0.3.8' # The full version, including alpha/beta/rc tags -release = u'0.3.7' +release = u'0.3.8' # -- General configuration --------------------------------------------------- diff --git a/moban/_version.py b/moban/_version.py index 2c02a9fe..846cb583 100644 --- a/moban/_version.py +++ b/moban/_version.py @@ -1,2 +1,2 @@ -__version__ = "0.3.7" +__version__ = "0.3.8" __author__ = "C. W." diff --git a/moban/constants.py b/moban/constants.py index ae11d2f9..a667e874 100644 --- a/moban/constants.py +++ b/moban/constants.py @@ -21,6 +21,7 @@ LABEL_CONFIG_DIR = "configuration_dir" LABEL_PLUGIN_DIRS = "plugin_dir" LABEL_TEMPLATE = "template" +POSITIONAL_LABEL_TEMPLATE = "template_in_string" LABEL_TMPL_DIRS = "template_dir" LABEL_OUTPUT = "output" LABEL_TEMPLATE_TYPE = "template_type" @@ -30,6 +31,7 @@ LABEL_MOBANFILE = "mobanfile" LABEL_FORCE = "force" LABEL_REQUIRES = "requires" +LABEL_EXIT_CODE = "exit-code" DEFAULT_CONFIGURATION_DIRNAME = ".moban.cd" DEFAULT_TEMPLATE_DIRNAME = ".moban.td" diff --git a/moban/jinja2/engine.py b/moban/jinja2/engine.py index eef41c02..10a59726 100644 --- a/moban/jinja2/engine.py +++ b/moban/jinja2/engine.py @@ -1,8 +1,9 @@ -from jinja2 import Environment, FileSystemLoader +from jinja2 import Template, Environment, FileSystemLoader from lml.loader import scan_plugins_regex from lml.plugin import PluginInfo, PluginManager +from jinja2.exceptions import TemplateNotFound -import moban.constants as constants +from moban import constants, exceptions JINJA2_LIBRARIES = "^moban_jinja2_.+$" JINJA2_EXENSIONS = [ @@ -91,9 +92,15 @@ def get_template(self, template_file): template file exists at: '/User/moban-pro/my-template/templates/myfile.jj2' """ - template = self.jj2_environment.get_template(template_file) + try: + template = self.jj2_environment.get_template(template_file) + except TemplateNotFound: + raise exceptions.FileNotFound("%s does not exist" % template_file) return template + def get_template_from_string(self, string): + return Template(string) + def apply_template(self, template, data, output): """ It is not expected this function to write content to file system. diff --git a/moban/main.py b/moban/main.py index 17e75722..f811efa0 100644 --- a/moban/main.py +++ b/moban/main.py @@ -34,23 +34,30 @@ def main(): if moban_file: try: count = handle_moban_file(moban_file, options) - if count: - sys.exit(count) + moban_exit(options[constants.LABEL_EXIT_CODE], count) except ( exceptions.DirectoryNotFound, exceptions.NoThirdPartyEngine, exceptions.MobanfileGrammarException, ) as e: reporter.report_error_message(str(e)) - sys.exit(constants.ERROR) + moban_exit(options[constants.LABEL_EXIT_CODE], constants.ERROR) else: try: count = handle_command_line(options) - if count: - sys.exit(count) + moban_exit(options[constants.LABEL_EXIT_CODE], count) except exceptions.NoTemplate as e: reporter.report_error_message(str(e)) - sys.exit(constants.ERROR) + moban_exit(options[constants.LABEL_EXIT_CODE], constants.ERROR) + + +def moban_exit(exit_code_toggle_flag, exit_code): + if exit_code_toggle_flag: + if exit_code: + sys.exit(exit_code) + else: + if exit_code == constants.ERROR: + sys.exit(1) def create_parser(): @@ -91,9 +98,23 @@ def create_parser(): default=False, help="force moban to template all files despite of .moban.hashes", ) + parser.add_argument( + "--%s" % constants.LABEL_EXIT_CODE, + action="store_true", + dest=constants.LABEL_EXIT_CODE, + default=False, + help="tell moban to change exit code", + ) parser.add_argument( "-m", "--%s" % constants.LABEL_MOBANFILE, help="custom moban file" ) + parser.add_argument( + constants.POSITIONAL_LABEL_TEMPLATE, + metavar="template", + type=str, + nargs="?", + help="string templates", + ) return parser @@ -153,18 +174,26 @@ def handle_command_line(options): act upon command options """ options = merge(options, constants.DEFAULT_OPTIONS) - if options[constants.LABEL_TEMPLATE] is None: - raise exceptions.NoTemplate(constants.ERROR_NO_TEMPLATE) engine = plugins.ENGINES.get_engine( options[constants.LABEL_TEMPLATE_TYPE], options[constants.LABEL_TMPL_DIRS], options[constants.LABEL_CONFIG_DIR], ) - engine.render_to_file( - options[constants.LABEL_TEMPLATE], - options[constants.LABEL_CONFIG], - options[constants.LABEL_OUTPUT], - ) + if options[constants.LABEL_TEMPLATE] is None: + if options[constants.POSITIONAL_LABEL_TEMPLATE] is None: + raise exceptions.NoTemplate(constants.ERROR_NO_TEMPLATE) + else: + engine.render_string_to_file( + options[constants.POSITIONAL_LABEL_TEMPLATE], + options[constants.LABEL_CONFIG], + options[constants.LABEL_OUTPUT], + ) + else: + engine.render_to_file( + options[constants.LABEL_TEMPLATE], + options[constants.LABEL_CONFIG], + options[constants.LABEL_OUTPUT], + ) engine.report() HASH_STORE.save_hashes() exit_code = reporter.convert_to_shell_exit_code( diff --git a/moban/plugins.py b/moban/plugins.py index a258fe2a..5ba36cee 100644 --- a/moban/plugins.py +++ b/moban/plugins.py @@ -52,6 +52,7 @@ def render_to_file(self, template_file, data_file, output_file): template_abs_path = utils.get_template_path( self.template_dirs, template_file ) + flag = self.apply_template( template_abs_path, template, data, output_file ) @@ -59,21 +60,41 @@ def render_to_file(self, template_file, data_file, output_file): reporter.report_templating(template_file, output_file) self.templated_count += 1 + def render_string_to_file( + self, template_in_string, data_file, output_file + ): + self.file_count = 1 + template = self.engine.get_template_from_string(template_in_string) + template_abs_path = template_in_string[:10] + "..." + data = self.context.get_data(data_file) + flag = self.apply_template( + template_abs_path, template, data, output_file + ) + if flag: + reporter.report_templating(template_abs_path, output_file) + self.templated_count += 1 + def apply_template(self, template_abs_path, template, data, output_file): rendered_content = self.engine.apply_template( template, data, output_file ) rendered_content = utils.strip_off_trailing_new_lines(rendered_content) rendered_content = rendered_content.encode("utf-8") - flag = HASH_STORE.is_file_changed( - output_file, rendered_content, template_abs_path - ) - if flag: + try: + flag = HASH_STORE.is_file_changed( + output_file, rendered_content, template_abs_path + ) + if flag: + utils.write_file_out( + output_file, rendered_content, strip=False, encode=False + ) + utils.file_permissions_copy(template_abs_path, output_file) + return flag + except exceptions.FileNotFound: utils.write_file_out( output_file, rendered_content, strip=False, encode=False ) - utils.file_permissions_copy(template_abs_path, output_file) - return flag + return True def render_to_files(self, array_of_param_tuple): sta = Strategy(array_of_param_tuple) diff --git a/moban/utils.py b/moban/utils.py index 7d542909..36de191b 100644 --- a/moban/utils.py +++ b/moban/utils.py @@ -126,6 +126,8 @@ def file_permissions_copy(source, dest): def file_permissions(afile): + if sys.platform == "win32": + return "no-permission-support" if not os.path.exists(afile): raise exceptions.FileNotFound(afile) return stat.S_IMODE(os.stat(afile).st_mode) diff --git a/setup.py b/setup.py index dbaca9af..decfd6ff 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ NAME = 'moban' AUTHOR = 'C. W.' -VERSION = '0.3.7' +VERSION = '0.3.8' 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.7.tar.gz' % URL +DOWNLOAD_URL = '%s/archive/0.3.8.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.7 ' + - "Find 0.3.7 in changelog for more details") +GS_COMMAND = ('gs moban v0.3.8 ' + + "Find 0.3.8 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/test.bat b/test.bat index 9d124fa6..17ae36a6 100644 --- a/test.bat +++ b/test.bat @@ -1,7 +1,4 @@ pip freeze -cd tests\moban-mako -python setup.py install -cd ..\..\ nosetests --with-coverage --cover-package=moban --cover-package=tests flake8 . --exclude=docs,.moban.d --ignore=E203,E121,E123,E126,E226,E24,E704,W503,W504 diff --git a/test.sh b/test.sh index 3fb012dc..6f15b0f5 100644 --- a/test.sh +++ b/test.sh @@ -1,6 +1,3 @@ pip freeze -cd tests/moban-mako -python setup.py install -cd ../../ nosetests --with-cov --with-doctest --doctest-extension=.rst --cover-package moban --cover-package tests && flake8 . --exclude=.moban.d,docs --ignore=E203,E121,E123,E126,E226,E24,E704,W503,W504 diff --git a/tests/integration_tests/test_command_line_options.py b/tests/integration_tests/test_command_line_options.py index a4c667c9..666fe3f8 100644 --- a/tests/integration_tests/test_command_line_options.py +++ b/tests/integration_tests/test_command_line_options.py @@ -82,6 +82,18 @@ def test_default_options(self, fake_template_doer): "a.jj2", "data.yml", "moban.output" ) + @patch("moban.plugins.BaseEngine.render_string_to_file") + def test_string_template(self, fake_template_doer): + string_template = "{{HELLO}}" + test_args = ["moban", string_template] + with patch.object(sys, "argv", test_args): + from moban.main import main + + main() + fake_template_doer.assert_called_with( + string_template, "data.yml", "moban.output" + ) + @raises(SystemExit) def test_no_argments(self): test_args = ["moban"] diff --git a/tests/moban-mako/moban_mako/__init__.py b/tests/moban-mako/moban_mako/__init__.py deleted file mode 100644 index 5e8d2458..00000000 --- a/tests/moban-mako/moban_mako/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from lml.plugin import PluginInfo - -from moban.constants import TEMPLATE_ENGINE_EXTENSION - - -@PluginInfo(TEMPLATE_ENGINE_EXTENSION, tags=["mako"]) -class MakoEngine: - def __init__(self, template_dirs): - pass diff --git a/tests/moban-mako/setup.py b/tests/moban-mako/setup.py deleted file mode 100644 index fe8011a9..00000000 --- a/tests/moban-mako/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -""" - moban-mako - ~~~~~~~~~~~~~~ - - It is a test plugin -""" - -try: - from setuptools import setup, find_packages -except ImportError: - from ez_setup import use_setuptools - - use_setuptools() - from setuptools import setup, find_packages - -setup( - name="moban_mako", - author="C. W.", - version="0.0.1", - author_email="wangc_2011 at hotmail.com", - packages=find_packages(exclude=["ez_setup", "examples", "tests"]), - include_package_data=True, - long_description=__doc__, - zip_safe=False, - classifiers=[ - "Development Status :: 3 - Alpha", - "Topic :: Office/Business", - "Topic :: Utilities", - "Topic :: Software Development :: Libraries", - "Programming Language :: Python", - "License :: OSI Approved :: GNU General Public License v3", - "Intended Audience :: Developers", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - ], -) diff --git a/tests/test_engine.py b/tests/test_engine.py index 521be93d..9ce98862 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -43,9 +43,15 @@ def test_default_template_type(): assert engine.engine_cls == Engine -def test_default_mako_type(): # fake mako - engine = ENGINES.get_engine("mako", [], "") - assert engine.engine_cls.__name__ == "MakoEngine" +class FakeEngine: + def __init__(self, template_dirs): + pass + + +@patch("moban.plugins.PluginManager.load_me_now", return_value=FakeEngine) +def test_default_mako_type(_): # fake mako + engine = ENGINES.get_engine("fake", [], "") + assert engine.engine_cls.__name__ == "FakeEngine" @raises(exceptions.NoThirdPartyEngine) @@ -113,3 +119,14 @@ def test_environ_variables_as_data(): content = output_file.read() eq_(content, "foo") os.unlink(output) + + +def test_string_template(): + output = "test.txt" + path = os.path.join("tests", "fixtures") + engine = BaseEngine([path], path, Engine) + engine.render_string_to_file("{{simple}}", "simple.yaml", output) + with open(output, "r") as output_file: + content = output_file.read() + eq_(content, "yaml") + os.unlink(output) diff --git a/tests/test_hash_store.py b/tests/test_hash_store.py index 659b3b5b..51e43dac 100644 --- a/tests/test_hash_store.py +++ b/tests/test_hash_store.py @@ -1,7 +1,4 @@ import os -import sys - -from nose import SkipTest from moban.hashstore import HashStore @@ -80,8 +77,6 @@ def test_dest_file_file_permision_changed(self): Save as above, but this time, the generated file had file permision change """ - if sys.platform == "win32": - raise SkipTest("No actual chmod on windows") hs = HashStore() flag = hs.is_file_changed(*self.fixture) if flag: diff --git a/tests/test_jinja2_engine.py b/tests/test_jinja2_engine.py index d3d888f1..1a979971 100644 --- a/tests/test_jinja2_engine.py +++ b/tests/test_jinja2_engine.py @@ -5,7 +5,7 @@ from moban.jinja2.engine import Engine -def test_handlebars_template_not_found(): +def test_jinja2_template(): path = os.path.join("tests", "fixtures", "jinja_tests") engine = Engine([path]) template = engine.get_template("file_tests.template") @@ -13,3 +13,13 @@ def test_handlebars_template_not_found(): result = engine.apply_template(template, data, None) expected = "yes\nhere" eq_(expected, result) + + +def test_jinja2_template_string(): + path = os.path.join("tests", "fixtures", "jinja_tests") + engine = Engine([path]) + template = engine.get_template_from_string("{{test}}") + data = dict(test="here") + result = engine.apply_template(template, data, None) + expected = "here" + eq_(expected, result) diff --git a/tests/test_main.py b/tests/test_main.py index 308e8b64..0d73f7b2 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -137,6 +137,31 @@ def tearDown(self): os.unlink(".moban.hashes") @raises(SystemExit) + @patch("moban.main.handle_moban_file") + @patch("moban.mobanfile.find_default_moban_file") + def test_has_many_files_with_exit_code( + self, fake_find_file, fake_moban_file + ): + fake_find_file.return_value = "abc" + fake_moban_file.return_value = 1 + from moban.main import main + + with patch.object(sys, "argv", ["moban", "--exit-code"]): + main() + + @raises(SystemExit) + @patch("moban.main.handle_command_line") + @patch("moban.mobanfile.find_default_moban_file") + def test_handle_single_change_with_exit_code( + self, fake_find_file, fake_command_line + ): + fake_find_file.return_value = None + fake_command_line.return_value = 1 + from moban.main import main + + with patch.object(sys, "argv", ["moban", "--exit-code"]): + main() + @patch("moban.main.handle_moban_file") @patch("moban.mobanfile.find_default_moban_file") def test_has_many_files(self, fake_find_file, fake_moban_file): @@ -147,7 +172,6 @@ def test_has_many_files(self, fake_find_file, fake_moban_file): with patch.object(sys, "argv", ["moban"]): main() - @raises(SystemExit) @patch("moban.main.handle_command_line") @patch("moban.mobanfile.find_default_moban_file") def test_handle_single_change(self, fake_find_file, fake_command_line): diff --git a/tests/test_utils.py b/tests/test_utils.py index cd3a4bf8..56ed331d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -44,6 +44,14 @@ def test_file_permission_copy(): os.unlink(test_dest) +def file_permissions_disabled_on_windows(): + if sys.platform == 'win32': + permissions = file_permissions('abc') + eq_('no-permission-support', permissions) + else: + raise SkipTest("No test required") + + @raises(FileNotFound) def test_file_permissions_file_not_found(): file_permissions("I does not exist")