diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ddae52c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,57 @@ +name: build + +on: [push, pull_request] + +jobs: + linux: + runs-on: ubuntu-latest + container: sublimetext/unittesting + steps: + - uses: actions/checkout@v1 + - run: sh -e /etc/init.d/xvfb start + - run: curl -OL https://raw.githubusercontent.com/SublimeText/UnitTesting/master/sbin/github.sh + - run: | + PATH="$HOME/.local/bin:$PATH" + sh github.sh bootstrap + sh github.sh install_package_control + sh github.sh run_tests --coverage + # sh github.sh run_syntax_tests --coverage + - run: | + apt-get install python3-pip -y + pip3 install coverage==4.5.4 codecov==2.0.15 + codecov + env: + CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} + + macos: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - run: curl -OL https://raw.githubusercontent.com/SublimeText/UnitTesting/master/sbin/github.sh + - run: | + export PATH="$HOME/.local/bin:$PATH" + sh github.sh bootstrap + sh github.sh install_package_control + sh github.sh run_tests --coverage + # sh github.sh run_syntax_tests --coverage + - run: | + pip3 install coverage==4.5.4 codecov==2.0.15 + codecov + env: + CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + - run: (new-object net.webclient).DownloadFile("https://raw.githubusercontent.com/SublimeText/UnitTesting/master/sbin/github.ps1","github.ps1") + - run: | + ./github.ps1 "bootstrap" -verbose + ./github.ps1 "install_package_control" -verbose + ./github.ps1 "run_tests" -coverage -verbose + # ./github.ps1 "run_syntax_tests" -coverage -verbose + - run: | + pip3 install coverage==4.5.4 codecov==2.0.15 + codecov + env: + CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} diff --git a/Default.sublime-commands b/Default.sublime-commands index 25f3e17..95461a2 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -1,6 +1,6 @@ [ { - "caption": "STProjectMaker: Create project", + "caption": "ProjectMaker: Create project", "command": "project_maker" } ] diff --git a/Main.sublime-menu b/Main.sublime-menu index 759526b..e98496b 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -28,7 +28,7 @@ "command": "open_file", "args": { - "file": "${packages}/STProjectMaker/STProjectMaker.sublime-settings" + "file": "${packages}/ProjectMaker/ProjectMaker.sublime-settings" } }, { @@ -36,7 +36,7 @@ "command": "open_file", "args": { - "file": "${packages}/User/STProjectMaker.sublime-settings" + "file": "${packages}/User/ProjectMaker.sublime-settings" } } ] diff --git a/STProjectMaker.sublime-settings b/ProjectMaker.sublime-settings similarity index 86% rename from STProjectMaker.sublime-settings rename to ProjectMaker.sublime-settings index 251a703..963afc8 100644 --- a/STProjectMaker.sublime-settings +++ b/ProjectMaker.sublime-settings @@ -1,10 +1,10 @@ { "non_parsed_ext": [ - ".jpg", + ".jpg", ".jpeg", ".tiff", - ".gif", - ".png", + ".gif", + ".png", ".bmp", ".svg", ".eps", @@ -14,14 +14,14 @@ ".psd", ".ico", - ".swf", + ".swf", ".swc", ".fla", ".flv", - ".mp3", - ".mp4", - ".ogg", + ".mp3", + ".mp4", + ".ogg", ".m4v", ".wav", ".m3u", @@ -54,9 +54,9 @@ ".woff" ], - + "non_parsed_files": [ ".DS_Store", "build.xml" ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index ab64f85..974a0db 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -STProjectMaker -=================== + -A Sublime Text 2/3 plug-in to allow creating any kind of project from your own custom templates. +# ProjectMaker +A Sublime Text 3 plug-in to allow creating any kind of project from your own custom templates. Note, now works with BOTH ST2 and ST3! Thanks to [Daniel Shannon](https://github.com/phyllisstein) for that work. ## Installation @@ -11,11 +11,11 @@ Note, now works with BOTH ST2 and ST3! Thanks to [Daniel Shannon](https://github 1. Install Package Control from http://wbond.net/sublime_packages/package_control -2. From within Package Control, look for STProjectMaker and install. +2. From within Package Control, look for ProjectMaker and install. ### Manually: -Clone or download this project into a folder named "STProjectMaker" in your Sublime Text 2 or 3 Packages folder. If you're not sure where your Packages folder is, use menu `Preferences/Browse Packages...` +Clone or download this project into a folder named "ProjectMaker" in your Sublime Text 2 or 3 Packages folder. If you're not sure where your Packages folder is, use menu `Preferences/Browse Packages...` ## Usage @@ -37,14 +37,14 @@ A template is simply a folder that can contain any number and types of files and There are a few sample templates in the Sample-Templates directory. You can just copy those into your template dir to use them. -By default, templates are looked for in a folder named `STProjectMakerTemplates` in your home directory, you will have to create this folder and add your templates there. If no STProjectMakerTemplates exists, then `Sample-Templates` folder in plugin directory is used. +By default, templates are looked for in a folder named `ProjectMakerTemplates` in your home directory, you will have to create this folder and add your templates there. If no ProjectMakerTemplates exists, then `Sample-Templates` folder in plugin directory is used. -Alternately, you can add a "template_path" property to your settings to tell STProjectMaker where to look for your templates. This is described below in the "Settings" section and would look like the following: +Alternately, you can add a "template_path" property to your settings to tell ProjectMaker where to look for your templates. This is described below in the "Settings" section and would look like the following: { "template_path": "path/to/your/templates/" } -There is another repository, [https://github.com/bit101/STProjectMaker-templates](https://github.com/bit101/STProjectMaker-templates) that will be used to host additional templates created by other users. +There is another repository, [https://github.com/bit101/ProjectMaker-templates](https://github.com/bit101/ProjectMaker-templates) that will be used to host additional templates created by other users. ### Tokens @@ -92,31 +92,31 @@ Project path is `/foo/bar/baz/MyProject/` ### Settings -You can access STProjectMaker setting via the menu `Preferences / Package Settings / Project Maker` and then `Settings - Default` or `Settings - User`. Generally you should leave the default settings as-is and add your own settings to user settings. +You can access ProjectMaker setting via the menu `Preferences / Package Settings / Project Maker` and then `Settings - Default` or `Settings - User`. Generally you should leave the default settings as-is and add your own settings to user settings. The available settings are: `non_parsed_ext` This is a list of file extensions that will not be parsed for token replacement. Files matching these extensions will still be copied to your project however. -`non_parsed_files` This is a list of individual file names that will not be parsed for token replacement. Currently this includes `build.xml` as Ant build files will generally have tokens in the same format as STProjectMaker tokens and STProjectMaker will try to replace these. Files matching these non-parsed file names will still be copied to the new project. +`non_parsed_files` This is a list of individual file names that will not be parsed for token replacement. Currently this includes `build.xml` as Ant build files will generally have tokens in the same format as ProjectMaker tokens and ProjectMaker will try to replace these. Files matching these non-parsed file names will still be copied to the new project. -`template_path` As described above. This is the path where STProjectMaker will look for your templates. It is best to set this to something outside of the STProjectMaker directory so updates/upgrades/reinstalls will not delete or overwrite your templates. If not set, this defaults to `~/STProjectMakerTemplates`. +`template_path` As described above. This is the path where ProjectMaker will look for your templates. It is best to set this to something outside of the ProjectMaker directory so updates/upgrades/reinstalls will not delete or overwrite your templates. If not set, this defaults to `~/ProjectMakerTemplates`. -`project_files_folder` This is the path where STProjectMaker will save the autogenerated `.sublime-project` file. This defaults to the project path. Note that if your template includes its own `.sublime-project` file, it will simply be copied over as usual and this setting will have no effect. +`project_files_folder` This is the path where ProjectMaker will save the autogenerated `.sublime-project` file. This defaults to the project path. Note that if your template includes its own `.sublime-project` file, it will simply be copied over as usual and this setting will have no effect. -`default_project_path` This is the path that is displayed by default when STProjectMaker asks for the Project Location. This defaults to `~/project_name`. +`default_project_path` This is the path that is displayed by default when ProjectMaker asks for the Project Location. This defaults to `~/project_name`. ### Sublime Project files -If the chosen template has a `.sublime-project` file in the top level, that file will be copied over and processed like any other file in the template. However, if this does not exist, a default `.sublime-project` file will be created using the `project_name` token as its base name. +If the chosen template has a `.sublime-project` file in the top level, that file will be copied over and processed like any other file in the template. However, if this does not exist, a default `.sublime-project` file will be created using the `project_name` token as its base name. ### Ignored Files -Obviously, you don't want to try to do token replacement in binary files. The plug-in has a long list of file types that it will ignore when doing token replacement. You can always add your own if any files in your templates cause a problem. The list is contained in the `STProjectMaker.sublime-settings` file. Note, these files _will_ be copied into the project. They will just not be parsed for tokens. +Obviously, you don't want to try to do token replacement in binary files. The plug-in has a long list of file types that it will ignore when doing token replacement. You can always add your own if any files in your templates cause a problem. The list is contained in the `ProjectMaker.sublime-settings` file. Note, these files _will_ be copied into the project. They will just not be parsed for tokens. ### Auto File Downloading and Other Tasks -Todd Anderson (http://custardbelly.com/blog/) has created an additional configuration feature that I have added to the project. This allows additional functions to be run after the project is created to perform additional tasks. +Todd Anderson (http://custardbelly.com/blog/) has created an additional configuration feature that I have added to the project. This allows additional functions to be run after the project is created to perform additional tasks. The one task it is currently capable of performing is downloading files and adding them to your project. This can be useful if you want every project you create to have the latest version of a particular library such as jQuery. The files to download are defined with a `config.json` file in the template. You can see an example of how this works in the `AppRequire.js` template. Known issue: the file task will fail on https urls under Linux. This is because the version of Python bundled with Sublime Text under Linux does not include the ssl module for reasons I am not quite clear on. diff --git a/Sample-Templates/PythonProject/main.py b/Sample-Templates/PythonProject/main.py index d171ae8..9428287 100644 --- a/Sample-Templates/PythonProject/main.py +++ b/Sample-Templates/PythonProject/main.py @@ -1,13 +1,10 @@ - - class MainClass: + def __init__(self): + self.say_hello() - def __init__(self): - self.say_hello() - - def say_hello(self): - print("Hello, World!") + def say_hello(self): + print("Hello, World!") -if(__name__ == "__main__"): - MainClass() \ No newline at end of file +if __name__ == "__main__": + MainClass() diff --git a/messages.json b/messages.json index cc52d24..cb78cf1 100644 --- a/messages.json +++ b/messages.json @@ -1,3 +1,3 @@ { - "2.0.0": "messages/2.0.0.txt" + "0.1.0": "messages/0.1.0.txt" } diff --git a/messages/2.0.0.txt b/messages/2.0.0.txt index 44f4be1..6b6ffd4 100644 --- a/messages/2.0.0.txt +++ b/messages/2.0.0.txt @@ -1,5 +1 @@ -Full ST3 integration. Commands, keymap files, menu settings, "template_path" se -tting to specify where you keep your templates. Package is expanded, so sample tem -plates are installed. Also, sample templates are installed into Sample-Templates -directory, so upgrading the package will not overwrite your existing templates in -the Templates directory. +`STProjectMaker` was renamed to just `ProjectMaker`. diff --git a/projectmaker.py b/project_maker.py similarity index 86% rename from projectmaker.py rename to project_maker.py index 2fc968f..3a338bb 100644 --- a/projectmaker.py +++ b/project_maker.py @@ -8,23 +8,26 @@ class ProjectMakerCommand(sublime_plugin.WindowCommand): - def run(self): - settings = sublime.load_settings("STProjectMaker.sublime-settings") - templates_path_setting = settings.get('template_path') - default_project_path_setting = settings.get('default_project_path') + settings = sublime.load_settings("ProjectMaker.sublime-settings") + templates_path_setting = settings.get("template_path") + default_project_path_setting = settings.get("default_project_path") if not default_project_path_setting: - self.default_project_path = os.path.normpath(os.path.expanduser("~/project_name")) + self.default_project_path = os.path.normpath( + os.path.expanduser("~/project_name") + ) else: - self.default_project_path = os.path.normpath(os.path.expanduser(default_project_path_setting)) + self.default_project_path = os.path.normpath( + os.path.expanduser(default_project_path_setting) + ) - self.project_files_folder = settings.get('project_files_folder') + self.project_files_folder = settings.get("project_files_folder") self.non_parsed_ext = settings.get("non_parsed_ext") self.non_parsed_files = settings.get("non_parsed_files") self.existing_names = [] - self.plugin_path = os.path.join(sublime.packages_path(), "STProjectMaker") + self.plugin_path = os.path.join(sublime.packages_path(), "ProjectMaker") if not templates_path_setting: - templates_path = os.path.expanduser("~/STProjectMakerTemplates") + templates_path = os.path.expanduser("~/ProjectMakerTemplates") if os.path.exists(templates_path): self.templates_path = templates_path else: @@ -32,7 +35,9 @@ def run(self): os.path.dirname(os.path.abspath(__file__)), "Sample-Templates" ) else: - self.templates_path = os.path.normpath(os.path.expanduser(templates_path_setting)) + self.templates_path = os.path.normpath( + os.path.expanduser(templates_path_setting) + ) self.template_names = [] self.choose_template() @@ -71,10 +76,10 @@ def on_project_path(self, path): self.project_name = os.path.basename(self.project_path) if os.path.exists(self.project_path): decision = sublime.ok_cancel_dialog( - "Something already exists at " + - self.project_path + - ".\nDo you want to create project in that folder?" + - "\n(Existing objects will not be overwritten)" + "Something already exists at " + + self.project_path + + ".\nDo you want to create project in that folder?" + + "\n(Existing objects will not be overwritten)" ) if decision: self.create_project() @@ -140,15 +145,14 @@ def get_tokens_from_path(self, path): def get_token_from_file_name(self, path, file_name): dot_index = file_name.find(".") - if file_name[0:1] == "_" and file_name[dot_index - 1:dot_index] == "_": + if file_name[0:1] == "_" and file_name[dot_index - 1 : dot_index] == "_": file_path = os.path.join(path, file_name) self.tokenized_titles.append(file_path) - token = file_name[1: dot_index - 1] - if not token in self.tokens: + token = file_name[1 : dot_index - 1] + if token not in self.tokens: self.tokens.append(token) def open_file(self, file_path, mode="r", return_content=True): - has_exception = False try: file_ref = codecs.open(file_path, mode, "utf-8") content = file_ref.read() @@ -159,8 +163,8 @@ def open_file(self, file_path, mode="r", return_content=True): else: return file_ref - except UnicodeDecodeError as e: - has_exception = True + except UnicodeDecodeError: + pass try: file_ref = codecs.open(file_path, mode, "latin-1") content = file_ref.read() @@ -171,8 +175,8 @@ def open_file(self, file_path, mode="r", return_content=True): else: return file_ref - except UnicodeDecodeError as e: - has_exception = True + except UnicodeDecodeError: + pass sublime.error_message("Could not open " + file_path) def get_tokens_from_file(self, file_path): @@ -186,7 +190,7 @@ def get_tokens_from_file(self, file_path): self.tokenized_files.append(file_path) for match in matches: token = match[2:-1] - if not token in self.tokens: + if token not in self.tokens: self.tokens.append(token) def get_token_values(self): @@ -214,7 +218,7 @@ def get_next_token_value(self): # custom token. get value from user: else: self.window.show_input_panel( - "Value for token \"" + token + "\"", + 'Value for token "' + token + '"', "", self.on_token_value, None, @@ -282,10 +286,10 @@ def create_project_file(self): file_ref.write( ( "{\n" - " \"folders\":\n" + ' "folders":\n' " [\n" " {\n" - " \"path\": \"" + self.project_path + "\"\n" + ' "path": "' + self.project_path + '"\n' " }\n" " ]\n" "}\n" @@ -301,7 +305,7 @@ def exec_tasks(self): exec_args = { "target": "exec", "shell": True, - "working_dir": self.project_path + "working_dir": self.project_path, } exec_args.update(t) target = exec_args.pop("target") diff --git a/test/filetasktest.py b/test/filetasktest.py deleted file mode 100644 index 5b6e8e0..0000000 --- a/test/filetasktest.py +++ /dev/null @@ -1,92 +0,0 @@ -""" Unit tests for configuration.py and filetask.py from STProjectMaker project. """ -""" [NOTE] filetask module imports sublime for error handling within the Sublime Text 2 IDE. Comment references to sublime to run tests properly. """ - -import sys, os, json, shutil, unittest -sys.path.append('../') -from configuration import ConfigurationReader -from filetask import RemoteFileFetchTask - -class TestConfigurationLoad(unittest.TestCase): - """Testing load and parse of config.json""" - - configuration = None - - def setUp(self): - """ Python <2.7 psuedo setUpBeforeClass """ - if self.__class__.configuration is None: - config = ConfigurationReader() - self.__class__.configuration = config.load_config('config_test.json') - - def test_load_config_not_none(self): - self.assertTrue(self.__class__.configuration is not None) - - def test_config_has_file_array(self): - self.assertTrue(type(self.__class__.configuration['files']) == list) - - def test_file_array_length(self): - files = self.__class__.configuration['files'] - self.assertEqual(len(files), 2 ) - - def test_file_read(self): - files = self.__class__.configuration['files'] - file_obj = files[0] - name = file_obj['name'] - url = file_obj['url'] - locations = file_obj['locations'] - self.assertTrue(url is not None) - self.assertTrue(name is not None) - self.assertEqual(len(locations), 1) - -class TestFileTask(unittest.TestCase): - """Testing FileTask on configuration""" - - configuration = None - task = None - - def setUp(self): - """ Python <2.7 psuedo setUpBeforeClass """ - if self.__class__.configuration is None: - f = open('config_test.json') - self.__class__.configuration = json.loads(f.read()) - f.close() - if self.__class__.task is None: - self.__class__.task = RemoteFileFetchTask() - - def test_file_read(self): - files = self.__class__.configuration['files'] - url = files[0]['url'] - contents = self.__class__.task.read_file(url) - self.assertTrue(contents is not None) - - def test_file_write(self): - files = self.__class__.configuration['files'] - url = files[0]['url'] - contents = self.__class__.task.read_file(url) - filepath = os.path.join(os.curdir,files[0]['name']) - self.assertTrue(self.__class__.task.write_file(contents, filepath)) - if os.path.exists(filepath): - try: - os.remove(filepath) - except Exception, e: - raise e - - def test_execute_from_list(self): - files = self.__class__.configuration['files'] - self.__class__.task.execute(files, os.curdir) - to_dir = os.path.join(os.curdir,'libs') - self.assertTrue(os.path.exists(to_dir)) - if os.path.exists(to_dir): - try: - shutil.rmtree(to_dir) - except Exception, e: - raise e - - def test_exceptions_from_execute(self): - files = [{'url':'httpf://code.jquery.com/jquery-latest.js', 'name':'badurltest', 'locations':[]}] - exceptions = self.__class__.task.execute(files, os.curdir) - self.assertEqual(len(exceptions), 1) - -suite = unittest.TestSuite() -suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestConfigurationLoad)) -suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFileTask)) -unittest.TextTestRunner(verbosity=2).run(suite) \ No newline at end of file diff --git a/test/config_test.json b/tests/config_test.json similarity index 100% rename from test/config_test.json rename to tests/config_test.json diff --git a/tests/filetasktest.py b/tests/filetasktest.py new file mode 100644 index 0000000..b8b29a0 --- /dev/null +++ b/tests/filetasktest.py @@ -0,0 +1,107 @@ +""" Unit tests for configuration.py and filetask.py from ProjectMaker project. """ +""" [NOTE] filetask module imports sublime for error handling within the Sublime Text 2 IDE. Comment references to sublime to run tests properly. """ + +import json +import os +import shutil +import sys +import unittest + +from configuration import ConfigurationReader +from filetask import RemoteFileFetchTask + +sys.path.append("../") + + +class TestConfigurationLoad(unittest.TestCase): + """Testing load and parse of config.json""" + + configuration = None + + def setUp(self): + """ Python <2.7 psuedo setUpBeforeClass """ + if self.__class__.configuration is None: + config = ConfigurationReader() + self.__class__.configuration = config.load_config("config_test.json") + + def test_load_config_not_none(self): + self.assertTrue(self.__class__.configuration is not None) + + def test_config_has_file_array(self): + self.assertTrue(type(self.__class__.configuration["files"]) == list) + + def test_file_array_length(self): + files = self.__class__.configuration["files"] + self.assertEqual(len(files), 2) + + def test_file_read(self): + files = self.__class__.configuration["files"] + file_obj = files[0] + name = file_obj["name"] + url = file_obj["url"] + locations = file_obj["locations"] + self.assertTrue(url is not None) + self.assertTrue(name is not None) + self.assertEqual(len(locations), 1) + + +class TestFileTask(unittest.TestCase): + """Testing FileTask on configuration""" + + configuration = None + task = None + + def setUp(self): + """ Python <2.7 psuedo setUpBeforeClass """ + if self.__class__.configuration is None: + f = open("config_test.json") + self.__class__.configuration = json.loads(f.read()) + f.close() + if self.__class__.task is None: + self.__class__.task = RemoteFileFetchTask() + + def test_file_read(self): + files = self.__class__.configuration["files"] + url = files[0]["url"] + contents = self.__class__.task.read_file(url) + self.assertTrue(contents is not None) + + def test_file_write(self): + files = self.__class__.configuration["files"] + url = files[0]["url"] + contents = self.__class__.task.read_file(url) + filepath = os.path.join(os.curdir, files[0]["name"]) + self.assertTrue(self.__class__.task.write_file(contents, filepath)) + if os.path.exists(filepath): + try: + os.remove(filepath) + except Exception, e: + raise e + + def test_execute_from_list(self): + files = self.__class__.configuration["files"] + self.__class__.task.execute(files, os.curdir) + to_dir = os.path.join(os.curdir, "libs") + self.assertTrue(os.path.exists(to_dir)) + if os.path.exists(to_dir): + try: + shutil.rmtree(to_dir) + except Exception, e: + raise e + + def test_exceptions_from_execute(self): + files = [ + { + "url": "httpf://code.jquery.com/jquery-latest.js", + "name": "badurltest", + "locations": [], + } + ] + exceptions = self.__class__.task.execute(files, os.curdir) + self.assertEqual(len(exceptions), 1) + + +suite = unittest.TestSuite() +suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestConfigurationLoad)) +suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFileTask)) +unittest.TextTestRunner(verbosity=2).run(suite)