├── .ci-scripts └── check-version.py ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── Vagrantfile ├── arduinokeywords ├── __init__.py ├── __pycache__ │ └── arduinokeywords.cpython-35.pyc ├── arduinokeywords.py ├── tests │ ├── __init__.py │ ├── keywords.txt │ ├── samples │ │ ├── DeepClasses │ │ │ ├── ClassA.h │ │ │ ├── ClassB.h │ │ │ ├── DeepOne │ │ │ │ ├── ClassC.h │ │ │ │ ├── DeepTwo │ │ │ │ │ ├── ClassD.h │ │ │ │ │ ├── DeepThree │ │ │ │ │ │ ├── ClassE.h │ │ │ │ │ │ ├── ignore.txt │ │ │ │ │ │ └── ignore_again.txt │ │ │ │ │ ├── ignore.txt │ │ │ │ │ └── ignore_again.txt │ │ │ │ ├── ignore.txt │ │ │ │ └── ignore_again.txt │ │ │ ├── ignore.txt │ │ │ └── ignore_again.txt │ │ ├── MalformedHeader │ │ │ └── MalformedHeader.h │ │ ├── MultipleClasses │ │ │ └── MultipleClasses.h │ │ └── SimpleHeader │ │ │ └── SimpleHeader.h │ └── test_arduinokeywords.py └── version.py ├── bin └── arduino-keywords ├── requirements.txt ├── setup.cfg ├── setup.py └── tox.ini /.ci-scripts/check-version.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import os 6 | import sys 7 | 8 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 9 | 10 | # get version information 11 | exec(open('../arduinokeywords/version.py').read()) 12 | 13 | if len(sys.argv) < 2: 14 | print("Please provide a version string") 15 | sys.exit(1) 16 | 17 | # Make sure given version matches the defined version, otherwise return an error 18 | version = sys.argv[1] 19 | if __version__ != version: 20 | print("Version mismatch - Expected: {expected}; Actual: {actual}".format(expected=__version__, actual=version)) 21 | sys.exit(1) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit tests / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | .venv/ 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | #Vagrant 95 | .vagrant -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.3' 5 | - '3.4' 6 | - '3.5' 7 | - '3.6' 8 | 9 | install: 10 | - pip install tox-travis 11 | - pip install -U tox 12 | 13 | script: 14 | - tox -e ci 15 | 16 | before_deploy: 17 | - .ci-scripts/check-version.py "${TRAVIS_TAG}" 18 | 19 | deploy: 20 | provider: pypi 21 | user: r89m 22 | password: 23 | secure: Z3amvm2PlGVnSZuPaVA0jxVNll06RjpflX58y7aG4mwkKIHvfUiEbICAkvzUc0lFoHBPbzC2YoF3f79qWTQ0Ww+c9jRQDhkfhvcGB3dD9fO6MN82RkcI9BMloxtZ9VGZRcckUYXtAKXEhMSHq414FDGNzOLVjysmYphyTpn+m6CgXJ8yNEci8PvVg01xjXIHVuQ4FkddY3D2yUIBZJZ9+IhDAArg6Y2y4xXuH6JcqI9WqRJ44kX6hwiBpLxxspBt54skdN7+zsz2FecWXQ/rxh0RiC2eyAXWMCNswDKr0fqCxycPUyyW2PKnyE5cvEzRXO3KxtDQP4LwCWt4sL9zki8Ay3/ENtiZkzBGMmPmiXV3e/HVfz9GLlsd1VjmwpY1khu2sgn34ZL7oSLfJ7XdIYMv/2AiRStPdW464FjbOXsBjllcIq1C5uTjPbLCxVQFIRTTeWi7jjRT5Sp3/qji3An36epEIeWMIevtVavT7D3WB9HnsCQqU0PZXxwav64tMRdpWHDFuJY52cZ79wN7s1Yq05hU5HP00U7V6dDvLh1y/zHP7Dpy1N9XyqL3n18V1xzFUaij+/5sioMvHYG6l8NFr9FMnud2UOpfi7rUagDMqnHRQGCA+n//RVK1Bka+egdLwFYnRLxETOcUS9eZqbtJzwCXktAaGbLC6erP8mA= 24 | on: 25 | branch: master 26 | tags: true 27 | 28 | notifications: 29 | email: 30 | on_success: change 31 | on_failure: change 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Richard Miles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arduino-keyword # 2 | 3 | [![Build Status](https://travis-ci.org/r89m/arduino-keywords.svg?branch=master)](https://travis-ci.org/r89m/arduino-keywords) 4 | [![Coverage Status](https://coveralls.io/repos/github/r89m/arduino-keywords/badge.svg?branch=master)](https://coveralls.io/github/r89m/arduino-keywords?branch=master) 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/255128dccb5047a2acd5b6030c6efb7c)](https://www.codacy.com/app/richard-miles/arduino-keywords?utm_source=github.com&utm_medium=referral&utm_content=r89m/arduino-keywords&utm_campaign=Badge_Grade) 6 | 7 | A script for automatically generating a keywords.txt file for Arduino libraries 8 | 9 | ### Supports ### 10 | * Classes (KEYWORD1) 11 | * Methods (KEYWORD2) 12 | 13 | ### Coming Soon ### 14 | * Constants and Enum Values (LITERAL1) 15 | 16 | ## Quick Start ## 17 | ```pip install arduinokeywords``` 18 | 19 | You can then use ```arduino-keywords``` in any directory to produce a ```keywords.txt``` file. 20 | 21 | Use ```arduino-keywords --help``` for additional options 22 | 23 | ## Requirements ## 24 | 25 | Python 2.7, 3.3 or higher. 26 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | 6 | config.vm.box = "ubuntu/trusty64" 7 | 8 | # Ensure that files in /vagrant are not executable, so that nose will pick up any tests 9 | config.vm.synced_folder "./", "/vagrant", 10 | id: "vagrant-root", 11 | mount_options: ["dmode=775,fmode=664"] 12 | 13 | # Enable provisioning with a shell script. Additional provisioners such as 14 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 15 | # documentation for more information about their specific syntax and use. 16 | config.vm.provision "shell", inline: <<-SHELL 17 | sudo apt-get -y install python-software-properties 18 | sudo add-apt-repository -y ppa:fkrull/deadsnakes 19 | sudo apt-get update 20 | 21 | sudo apt-get -y install python2.7 python3.2 python3.3 python3.4 python3.5 pypy 22 | sudo apt-get install -y python-pip python3-pip 23 | 24 | sudo pip install setuptools --upgrade 25 | sudo pip3 install setuptools --upgrade 26 | sudo pip install tox 27 | SHELL 28 | end 29 | -------------------------------------------------------------------------------- /arduinokeywords/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/__init__.py -------------------------------------------------------------------------------- /arduinokeywords/__pycache__/arduinokeywords.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/__pycache__/arduinokeywords.cpython-35.pyc -------------------------------------------------------------------------------- /arduinokeywords/arduinokeywords.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from __future__ import print_function 4 | 5 | import os 6 | 7 | import fnmatch 8 | import glob 9 | 10 | import CppHeaderParser 11 | 12 | KEYWORDS_FILENAME = u"keywords.txt" 13 | KEYWORD_SEP = u"\t" 14 | KEYWORD_FORMAT_CLASS = u"{{class_name}}{separator}KEYWORD1".format(separator=KEYWORD_SEP) 15 | KEYWORD_FORMAT_METHOD = u"{{method}}{separator}KEYWORD2".format(separator=KEYWORD_SEP) 16 | KEYWORD_FORMAT_CONSTANT = u"{{constant}}{separator}LITERAL1".format(separator=KEYWORD_SEP) 17 | 18 | 19 | class ClassKeywords: 20 | 21 | def __init__(self, name, filename=None): 22 | self.name = name 23 | self.filename = filename 24 | self._methods = [] 25 | 26 | def add_method(self, method_name): 27 | self._methods.append(method_name) 28 | 29 | def get_methods(self): 30 | return sorted(set(self._methods)) 31 | 32 | 33 | def parse_library(library_path, max_depth=1): 34 | 35 | header_files = find_header_files(library_path, max_depth) 36 | 37 | classes = [] 38 | 39 | for header in header_files: 40 | classes.extend(parse_header(header)) 41 | 42 | return classes 43 | 44 | 45 | def find_header_files(library_path, max_depth=1): 46 | 47 | header_files = [] 48 | 49 | # Max depth script borrowed from John Scmiddt (http://stackoverflow.com/a/17056922) 50 | for d in range(1, max_depth + 1): 51 | maxGlob = "/".join("*" * d) 52 | topGlob = os.path.join(library_path, maxGlob) 53 | allFiles = glob.glob(topGlob) 54 | for file in allFiles: 55 | if fnmatch.fnmatch(os.path.basename(file), '*.h'): 56 | header_files.append(file) 57 | 58 | return header_files 59 | 60 | 61 | def parse_header(header_path): 62 | try: 63 | cpp_header = CppHeaderParser.CppHeader(header_path) 64 | 65 | classes = [] 66 | 67 | for class_name, header_class in cpp_header.classes.items(): 68 | keyword_class = ClassKeywords(class_name, header_path) 69 | for method in header_class["methods"]["public"]: 70 | # Ignore constructors and destructors 71 | if not (method["constructor"] or method["destructor"]): 72 | keyword_class.add_method(method["name"]) 73 | 74 | classes.append(keyword_class) 75 | 76 | return classes 77 | 78 | except CppHeaderParser.CppParseError as e: 79 | print(e) 80 | return [] 81 | 82 | 83 | def get_keywords_fullpath(keywords_path): 84 | 85 | if(os.path.isdir(keywords_path)): 86 | keywords_path = os.path.join(keywords_path, KEYWORDS_FILENAME) 87 | else: 88 | keywords_path = keywords_path 89 | 90 | return os.path.abspath(keywords_path) 91 | 92 | 93 | def output_keywords(classes, keywords_file, additional_constants=None): 94 | 95 | for output_class in classes: 96 | keywords_file.write(KEYWORD_FORMAT_CLASS.format(class_name=output_class.name)) 97 | keywords_file.write("\n") 98 | for method in output_class.get_methods(): 99 | keywords_file.write(KEYWORD_FORMAT_METHOD.format(method=method)) 100 | keywords_file.write("\n") 101 | 102 | if additional_constants is not None: 103 | for constant in additional_constants: 104 | keywords_file.write(KEYWORD_FORMAT_CONSTANT.format(constant=constant)) 105 | keywords_file.write("\n") 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /arduinokeywords/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/__init__.py -------------------------------------------------------------------------------- /arduinokeywords/tests/keywords.txt: -------------------------------------------------------------------------------- 1 | ClassA KEYWORD1 2 | intMethod KEYWORD2 3 | voidMethod KEYWORD2 4 | ClassB KEYWORD1 5 | intMethod KEYWORD2 6 | voidMethod KEYWORD2 7 | ClassB KEYWORD1 8 | publicBIntMethod KEYWORD2 9 | publicBMethod KEYWORD2 10 | publicBMethodWithInt KEYWORD2 11 | ClassA KEYWORD1 12 | publicAIntMethod KEYWORD2 13 | publicAMethod KEYWORD2 14 | publicAMethodWithInt KEYWORD2 15 | SimpleClass KEYWORD1 16 | publicIntMethod KEYWORD2 17 | publicMethod KEYWORD2 18 | publicMethodWithInt KEYWORD2 19 | ClassC KEYWORD1 20 | intMethod KEYWORD2 21 | voidMethod KEYWORD2 22 | ClassD KEYWORD1 23 | intMethod KEYWORD2 24 | voidMethod KEYWORD2 25 | ClassE KEYWORD1 26 | intMethod KEYWORD2 27 | voidMethod KEYWORD2 28 | -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/ClassA.h: -------------------------------------------------------------------------------- 1 | class ClassA{ 2 | 3 | public: 4 | void voidMethod(); 5 | int intMethod(); 6 | 7 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/ClassB.h: -------------------------------------------------------------------------------- 1 | class ClassB{ 2 | 3 | public: 4 | void voidMethod(); 5 | int intMethod(); 6 | 7 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/ClassC.h: -------------------------------------------------------------------------------- 1 | class ClassC{ 2 | 3 | public: 4 | void voidMethod(); 5 | int intMethod(); 6 | 7 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/ClassD.h: -------------------------------------------------------------------------------- 1 | class ClassD{ 2 | 3 | public: 4 | void voidMethod(); 5 | int intMethod(); 6 | 7 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/DeepThree/ClassE.h: -------------------------------------------------------------------------------- 1 | class ClassE{ 2 | 3 | public: 4 | void voidMethod(); 5 | int intMethod(); 6 | 7 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/DeepThree/ignore.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/DeepThree/ignore.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/DeepThree/ignore_again.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/DeepThree/ignore_again.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/ignore.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/ignore.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/ignore_again.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/DeepOne/DeepTwo/ignore_again.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/ignore.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/DeepOne/ignore.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/DeepOne/ignore_again.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/DeepOne/ignore_again.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/ignore.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/ignore.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/DeepClasses/ignore_again.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r89m/arduino-keywords/4e6f0daea8ce81927e358060d2e78f1f300d711b/arduinokeywords/tests/samples/DeepClasses/ignore_again.txt -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/MalformedHeader/MalformedHeader.h: -------------------------------------------------------------------------------- 1 | clazz MalformedHeader{ 2 | 3 | public: 4 | void voidFunc(); 5 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/MultipleClasses/MultipleClasses.h: -------------------------------------------------------------------------------- 1 | class ClassA{ 2 | 3 | private: 4 | void privateAMethod1(); 5 | void privateAMethod2(); 6 | 7 | public: 8 | void publicAMethod(); 9 | int publicAIntMethod(); 10 | void publicAMethodWithInt(int); 11 | }; 12 | 13 | class ClassB{ 14 | 15 | private: 16 | void privateBMethod1(); 17 | void privateBMethod2(); 18 | 19 | public: 20 | void publicBMethod(); 21 | int publicBIntMethod(); 22 | void publicBMethodWithInt(int); 23 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/samples/SimpleHeader/SimpleHeader.h: -------------------------------------------------------------------------------- 1 | class SimpleClass{ 2 | 3 | private: 4 | void privateMethod1(); 5 | void privateMethod2(); 6 | 7 | public: 8 | SimpleClass(); 9 | ~SimpleClass(); 10 | void publicMethod(); 11 | int publicIntMethod(); 12 | void publicMethodWithInt(int); 13 | }; -------------------------------------------------------------------------------- /arduinokeywords/tests/test_arduinokeywords.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import join 3 | 4 | from unittest import TestCase 5 | from io import StringIO 6 | 7 | from arduinokeywords.arduinokeywords import parse_header, parse_library, output_keywords, find_header_files, get_keywords_fullpath 8 | 9 | THIS_DIR = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | PATCH_OPEN_PATH = "arduinokeywords.arduinokeywords.open" 12 | 13 | 14 | class Test_arduinokeywords(TestCase): 15 | 16 | def testParseSimpleHeader(self): 17 | 18 | test_header_path = join(THIS_DIR, "samples", "SimpleHeader", "SimpleHeader.h") 19 | 20 | expected_class = "SimpleClass" 21 | expected_methods = ["publicMethod", "publicIntMethod", "publicMethodWithInt"] 22 | 23 | parsed_header = parse_header(test_header_path) 24 | 25 | test_class = parsed_header[0] 26 | 27 | self.assertEqual(expected_class, test_class.name) 28 | self.assertListEqual(sorted(expected_methods), sorted(test_class.get_methods())) 29 | 30 | def testParseMalformedHeader(self): 31 | 32 | test_header_path = join(THIS_DIR, "samples", "MalformedHeader", "MalformedHeader.h") 33 | 34 | expected_classes = [] 35 | 36 | self.assertListEqual(expected_classes, parse_header(test_header_path)) 37 | 38 | def testParseHeaderWithMultipleClasses(self): 39 | 40 | test_header_path = join(THIS_DIR, "samples", "MultipleClasses", "MultipleClasses.h") 41 | 42 | expected_a_name = "ClassA" 43 | expected_b_name = "ClassB" 44 | expected_a_methods = ["publicAMethod", "publicAIntMethod", "publicAMethodWithInt"] 45 | expected_b_methods = ["publicBMethod", "publicBIntMethod", "publicBMethodWithInt"] 46 | 47 | parsed_header = parse_header(test_header_path) 48 | 49 | # Assign the classes to the right variables 50 | for check_class in parsed_header: 51 | if check_class.name == expected_a_name: 52 | class_a = check_class 53 | elif check_class.name == expected_b_name: 54 | class_b = check_class 55 | 56 | self.assertEqual(expected_a_name, class_a.name) 57 | self.assertEqual(expected_b_name, class_b.name) 58 | 59 | self.assertListEqual(sorted(expected_a_methods), sorted(class_a.get_methods())) 60 | self.assertListEqual(sorted(expected_b_methods), sorted(class_b.get_methods())) 61 | 62 | def testSearchLibraryForHeadersAtRootLevel(self): 63 | 64 | test_library_path = join(THIS_DIR, "samples", "DeepClasses") 65 | 66 | expected_header_files = ["ClassA.h", "ClassB.h"] 67 | expected_header_files = [join(test_library_path, f) for f in expected_header_files] 68 | 69 | found_headers = find_header_files(test_library_path) 70 | 71 | self.assertListEqual(sorted(expected_header_files), sorted(found_headers)) 72 | 73 | def testSearchLibraryForHeadersWithDepth2(self): 74 | 75 | test_library_path = join(THIS_DIR, "samples","DeepClasses") 76 | 77 | expected_header_files = ["ClassA.h", "ClassB.h", join("DeepOne", "ClassC.h")] 78 | expected_header_files = [join(test_library_path, f) for f in expected_header_files] 79 | 80 | found_headers = find_header_files(test_library_path, 2) 81 | 82 | self.assertListEqual(sorted(expected_header_files), sorted(found_headers)) 83 | 84 | def testSearchLibraryForHeadersWithDepth4(self): 85 | 86 | test_library_path = join(THIS_DIR, "samples","DeepClasses") 87 | 88 | expected_header_files = ["ClassA.h", "ClassB.h", join("DeepOne", "ClassC.h"), 89 | join("DeepOne", "DeepTwo", "ClassD.h"), 90 | join("DeepOne", "DeepTwo", "DeepThree", "ClassE.h")] 91 | 92 | expected_header_files = [join(test_library_path, f) for f in expected_header_files] 93 | 94 | found_headers = find_header_files(test_library_path, 4) 95 | 96 | self.assertListEqual(sorted(expected_header_files), sorted(found_headers)) 97 | 98 | def testParseLibraryWithSingleRootLevelHeader(self): 99 | 100 | test_library_path = join(THIS_DIR, "samples", "SimpleHeader") 101 | expected_classes = ["SimpleClass"] 102 | 103 | parsed_classes = parse_library(test_library_path) 104 | retrieved_classes = sorted([c.name for c in parsed_classes]) 105 | 106 | self.assertListEqual(expected_classes, retrieved_classes) 107 | 108 | def testParseLibraryWithMultipleRootLevelHeaders(self): 109 | 110 | test_library_path = join(THIS_DIR, "samples", "DeepClasses") 111 | expected_classes = ["ClassA", "ClassB"] 112 | 113 | parsed_classes = parse_library(test_library_path) 114 | retrieved_classes = sorted([c.name for c in parsed_classes]) 115 | 116 | self.assertListEqual(expected_classes, retrieved_classes) 117 | 118 | def testParseLibraryWithMultipleHeadersOverSeveralDirectories_MaxDepth2(self): 119 | 120 | test_library_path = join(THIS_DIR, "samples", "DeepClasses") 121 | expected_classes = ["ClassA", "ClassB", "ClassC"] 122 | 123 | parsed_classes = parse_library(test_library_path, max_depth=2) 124 | retrieved_classes = sorted([c.name for c in parsed_classes]) 125 | 126 | self.assertListEqual(expected_classes, retrieved_classes) 127 | 128 | def testParseLibraryWithMultipleHeadersOverSeveralDirectories_MaxDepth4(self): 129 | 130 | test_library_path = join(THIS_DIR, "samples", "DeepClasses") 131 | expected_classes = ["ClassA", "ClassB", "ClassC", "ClassD", "ClassE"] 132 | 133 | parsed_classes = parse_library(test_library_path, max_depth=4) 134 | retrieved_classes = sorted([c.name for c in parsed_classes]) 135 | 136 | self.assertListEqual(expected_classes, retrieved_classes) 137 | 138 | def testGetKeywordsPath_WithFilename(self): 139 | 140 | keywords_filename = join(THIS_DIR, "samples", "keywords.txt") 141 | expected_result = os.path.abspath(keywords_filename) 142 | 143 | self.assertEqual(expected_result, get_keywords_fullpath(keywords_filename)) 144 | 145 | def testGetKeywordsPath_WithoutFilename(self): 146 | 147 | keywords_filename = join(THIS_DIR, "samples") 148 | expected_result = join(os.path.abspath(keywords_filename), "keywords.txt") 149 | 150 | self.assertEqual(expected_result, get_keywords_fullpath(keywords_filename)) 151 | 152 | def testWritingKeywordsTxtFile(self): 153 | 154 | test_library_path = join(THIS_DIR, "samples", "SimpleHeader") 155 | classes = parse_library(test_library_path) 156 | 157 | outfile = StringIO() 158 | output_keywords(classes, outfile) 159 | outfile.seek(0) 160 | content = outfile.read() 161 | self.assertEqual(content, "SimpleClass\tKEYWORD1\npublicIntMethod\tKEYWORD2\npublicMethod\tKEYWORD2\npublicMethodWithInt\tKEYWORD2\n") 162 | 163 | 164 | def testWritingKeywordsTxtFileWithAdditionalMethodsAndConstants(self): 165 | 166 | test_library_path = join(THIS_DIR, "samples", "SimpleHeader") 167 | 168 | classes = parse_library(test_library_path) 169 | 170 | additional_constants = ["const1", "const2"] 171 | 172 | outfile = StringIO() 173 | output_keywords(classes, outfile, additional_constants) 174 | outfile.seek(0) 175 | content = outfile.read() 176 | self.assertEqual(content, "SimpleClass\tKEYWORD1\npublicIntMethod\tKEYWORD2\npublicMethod\tKEYWORD2\npublicMethodWithInt\tKEYWORD2\nconst1\tLITERAL1\nconst2\tLITERAL1\n") 177 | -------------------------------------------------------------------------------- /arduinokeywords/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.4" -------------------------------------------------------------------------------- /bin/arduino-keywords: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | import argparse 7 | 8 | from arduinokeywords.arduinokeywords import parse_header, parse_library, output_keywords, get_keywords_fullpath 9 | from arduinokeywords.version import __version__ 10 | 11 | # Setup command line parameters 12 | parser = argparse.ArgumentParser(description='Generate a keywords.txt file for the Arduino IDE.') 13 | parser.add_argument(type=str, dest='source', metavar="dir", default=os.getcwd(), nargs='?', 14 | help='Your library\'s root directory. Defaults to current directory') 15 | parser.add_argument('-o', '--output', type=str, dest='output', metavar="dir", default="./", 16 | help='The folder in which the keywords.txt file will be created. Defaults to --source') 17 | parser.add_argument('-c', '--constant', type=str, dest='constants', default=[], action="append", 18 | help='Specify additional constants to be written to keywords.txt. Multiple constants can be ' 19 | 'defined by repeating this argument', metavar="CONSTANT") 20 | parser.add_argument('-f', '--header', '--header-file', type=str, dest='headers', default=[], action="append", 21 | help='Manually specify the header files to be parsed. Multiple files can be ' 22 | 'defined by repeating this argument', metavar="file") 23 | parser.add_argument('-d', '--depth', type=int, dest='depth', default=1, 24 | help='Specify how deep the automatic header search should look') 25 | parser.add_argument('-v', '--version', help='Print version and quit', action='store_true') 26 | 27 | args = parser.parse_args() 28 | 29 | if args.version: 30 | print(__version__) 31 | sys.exit(0) 32 | 33 | args.source = os.path.abspath(args.source) 34 | 35 | if not os.path.exists(args.source): 36 | print("The source path {path} doesn't exist, quitting...".format(path=args.source)) 37 | sys.exit(1) 38 | 39 | if not os.path.isdir(args.source): 40 | print("The source path {path} isn't a directory, quitting...".format(path=args.source)) 41 | sys.exit(1) 42 | 43 | # If header files are specifed use those, otherwise search 'source' to a max depth of 'depth' 44 | if len(args.headers) == 0: 45 | print("Searching for header files in {path}".format(path=args.source)) 46 | print("Search max depth is: {depth}".format(depth=args.depth)) 47 | 48 | classes = parse_library(args.source, args.depth) 49 | else: 50 | print("Parsing the following header files:") 51 | classes = [] 52 | for header in args.headers: 53 | print("\t{header}".format(header=header)) 54 | try: 55 | classes.extend(parse_header(os.path.join(args.source, header))) 56 | except FileNotFoundError: 57 | print("\tFile {file} not found, skipping".format(file=header)) 58 | 59 | print("") 60 | print("Classes found: {count}".format(count=len(classes))) 61 | for clazz in classes: 62 | filename = os.path.relpath(clazz.filename, args.source) 63 | print("\t {class_name} (./{filename})".format(class_name=clazz.name, filename=filename)) 64 | print("") 65 | 66 | if len(args.constants) > 0: 67 | print("Additional constants:") 68 | for constant in args.constants: 69 | print("\t {constant}".format(constant=constant)) 70 | 71 | keywords_filename = get_keywords_fullpath(os.path.join(args.source, args.output)) 72 | print("Outputting keywords.txt to: {path}".format(path=keywords_filename)) 73 | 74 | with open(keywords_filename, 'w+') as keywords_file: 75 | output_keywords(classes, keywords_file, args.constants) 76 | 77 | print("Done") 78 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --index-url https://pypi.python.org/simple/ 2 | 3 | -e . -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | # allow setup.py to be run from any path 5 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 6 | 7 | # get version information 8 | exec(open('arduinokeywords/version.py').read()) 9 | 10 | setup(name="ArduinoKeywords", 11 | version=__version__, 12 | description="A script for automatically generating a keywords.txt file for Arduino libraries", 13 | author="Richard Miles", 14 | author_email="pypi@fast-chat.co.uk", 15 | packages=['arduinokeywords'], 16 | url="https://github.com/r89m/arduino-keywords", 17 | install_requires=["CppHeaderParser==2.7.4"], 18 | scripts=[ 19 | "bin/arduino-keywords" 20 | ], 21 | test_suite='nose.collector', 22 | tests_require=[ 23 | 'nose==1.3.7' 24 | ]) 25 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,32,33,34,35,36},pypy 3 | skipsdist = true 4 | 5 | [testenv] 6 | usedevelop=True 7 | commands = pip install coverage 8 | coverage run --source=arduinokeywords --omit=arduinokeywords/run.py setup.py test 9 | 10 | ; For use by the CI server 11 | ; python-coveralls only supported on Linux / OSX 12 | ; coveralls will only work when supplied with the secret API key 13 | [testenv:ci] 14 | usedevelop = {[testenv]usedevelop} 15 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH 16 | commands = {[testenv]commands} 17 | pip install coveralls 18 | coveralls 19 | --------------------------------------------------------------------------------