├── .gitignore ├── .pylintrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── TODOs.md ├── VISION.md ├── benchmarks.md ├── chatette ├── __init__.py ├── __main__.py ├── adapters │ ├── __init__.py │ ├── _base.py │ ├── factory.py │ ├── jsonl.py │ ├── rasa.py │ └── rasa_md.py ├── cli │ ├── __init__.py │ ├── interactive_commands │ │ ├── __init__.py │ │ ├── add_rule_command.py │ │ ├── command_strategy.py │ │ ├── declare_command.py │ │ ├── delete_command.py │ │ ├── examples_command.py │ │ ├── execute_command.py │ │ ├── exist_command.py │ │ ├── exit_command.py │ │ ├── generate_command.py │ │ ├── hide_command.py │ │ ├── parse_command.py │ │ ├── rename_command.py │ │ ├── rule_command.py │ │ ├── save_command.py │ │ ├── set_modifier_command.py │ │ ├── show_command.py │ │ ├── stats_command.py │ │ └── unhide_command.py │ ├── interpreter.py │ └── terminal_writer.py ├── configuration.py ├── deprecations.py ├── facade.py ├── generator.py ├── log.py ├── modifiers │ ├── __init__.py │ ├── argument.py │ ├── casegen.py │ ├── randgen.py │ └── representation.py ├── parsing │ ├── __init__.py │ ├── input_file_manager.py │ ├── lexing │ │ ├── __init__.py │ │ ├── lexer.py │ │ ├── lexing_rule.py │ │ ├── rule_annotation.py │ │ ├── rule_arg_assignment.py │ │ ├── rule_arg_decl.py │ │ ├── rule_comment.py │ │ ├── rule_content_rule_and_choice.py │ │ ├── rule_file_inclusion.py │ │ ├── rule_key_value.py │ │ ├── rule_line.py │ │ ├── rule_percent_gen.py │ │ ├── rule_rand_gen.py │ │ ├── rule_slot_val.py │ │ ├── rule_unit_decl.py │ │ ├── rule_unit_decl_line.py │ │ ├── rule_unit_ref.py │ │ ├── rule_unit_rule.py │ │ ├── rule_unit_start.py │ │ ├── rule_variation.py │ │ ├── rule_whitespaces.py │ │ └── rule_word.py │ ├── line_count_file_wrapper.py │ ├── parser.py │ └── utils.py ├── prechecks │ ├── __init__.py │ ├── deprecations.py │ ├── preconditions.py │ └── pyversion.py ├── statistics.py ├── units │ ├── __init__.py │ ├── ast.py │ ├── generating_item.py │ ├── modifiable │ │ ├── __init__.py │ │ ├── choice.py │ │ ├── definitions │ │ │ ├── __init__.py │ │ │ ├── alias.py │ │ │ ├── intent.py │ │ │ ├── slot.py │ │ │ └── unit_definition.py │ │ └── unit_reference.py │ ├── rule.py │ └── word.py └── utils.py ├── examples ├── complex │ ├── airport │ │ ├── aliases.chatette │ │ ├── master.chatette │ │ └── slots │ │ │ ├── cities.chatette │ │ │ ├── departure-times.chatette │ │ │ └── nb-people.chatette │ └── metal-work │ │ ├── aliases.chatette │ │ ├── client_info.chatette │ │ ├── master.chatette │ │ ├── prod_lines.chatette │ │ └── slots.chatette └── simple │ ├── airport │ ├── aliases.chatette │ ├── master.chatette │ └── slots │ │ ├── cities.chatette │ │ └── departure-times.chatette │ ├── gif │ └── gif.chatette │ ├── mood │ ├── aliases_and_slots.chatette │ └── master.chatette │ ├── restaurant │ ├── included.chatette │ └── master.chatette │ └── toilets │ └── toilets.chatette ├── public ├── images │ ├── chatette-logo.png │ ├── chatette-logo.svg │ ├── interactive-mode-long.gif │ ├── interactive-mode-short.gif │ ├── preview.png │ └── preview.svg └── uml │ ├── 1.3.2 │ ├── classes_No_Name.png │ └── packages_No_Name.png │ ├── 1.4.0 │ ├── classes_No_Name.png │ └── packages_No_Name.png │ ├── 1.5.0 │ ├── classes_No_Name.png │ └── packages_No_Name.png │ ├── 1.6.0 │ ├── classes_No_Name.png │ └── packages_No_Name.png │ ├── 1.6.1 │ ├── classes.png │ └── packages.png │ └── 1.6.2 │ ├── classes.png │ └── packages.png ├── requirements ├── common.txt ├── develop.txt └── test.txt ├── setup.py ├── syntax-specs.md ├── tests ├── notes.md ├── system-testing │ ├── inputs │ │ ├── generate-all │ │ │ ├── alias.chatette │ │ │ ├── alias.solution │ │ │ ├── include.chatette │ │ │ ├── include.solution │ │ │ ├── included.chatette │ │ │ ├── only-words.chatette │ │ │ ├── only-words.solution │ │ │ ├── simplest.chatette │ │ │ ├── simplest.solution │ │ │ ├── slot.chatette │ │ │ ├── slot.solution │ │ │ ├── slot.syn │ │ │ ├── words-and-groups.chatette │ │ │ └── words-and-groups.solution │ │ └── generate-nb │ │ │ └── training-only │ │ │ ├── alias.chatette │ │ │ ├── alias.solution │ │ │ ├── bugfixes │ │ │ ├── bug-22-slot-position.chatette │ │ │ └── bug-22-slot-position.solution │ │ │ ├── include.chatette │ │ │ ├── include.solution │ │ │ ├── included.chatette │ │ │ ├── one-ex.chatette │ │ │ ├── only-words.chatette │ │ │ ├── only-words.solution │ │ │ ├── slot.chatette │ │ │ ├── slot.solution │ │ │ ├── slot.syn │ │ │ ├── words-and-groups.chatette │ │ │ ├── words-and-groups.solution │ │ │ └── zero-ex.chatette │ └── test_system.py └── unit-testing │ ├── adapter │ ├── test_base.py │ ├── test_factory.py │ ├── test_jsonl.py │ ├── test_rasa.py │ └── test_rasa_md.py │ ├── cli │ ├── interactive_commands │ │ ├── other.chatette │ │ ├── test_command_strategy.py │ │ ├── test_declare_command.py │ │ ├── test_delete_command.py │ │ ├── test_exist_command.py │ │ ├── test_exit_command.py │ │ ├── test_hide_command.py │ │ ├── test_parse_command.py │ │ ├── test_rename_command.py │ │ ├── test_show_command.py │ │ ├── test_stats_command.py │ │ └── toilets.chatette │ └── test_terminal_writer.py │ ├── modifiers │ ├── test_argument.py │ ├── test_casegen.py │ ├── test_randgen.py │ └── test_representation.py │ ├── parsing │ └── test_init.py │ ├── prechecks.py │ └── test_pyversion.py │ ├── test_deprecations.py │ ├── test_log.py │ ├── test_statistics.py │ └── test_utils.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Chatette default outputs 2 | output/ 3 | output* 4 | output.json 5 | testing-dataset.json 6 | *.out 7 | examples/test* 8 | 9 | ## Visual Studio code 10 | .vscode/ 11 | 12 | # Created by .ignore support plugin (hsz.mobi) 13 | 14 | ### Linux template 15 | *~ 16 | 17 | # temporary files which can be created if a process still has a handle open of a deleted file 18 | .fuse_hidden* 19 | 20 | # KDE directory preferences 21 | .directory 22 | 23 | # Linux trash folder which might appear on any partition or disk 24 | .Trash-* 25 | 26 | # .nfs files are created when an open file is removed but is still being accessed 27 | .nfs* 28 | 29 | ### Windows template 30 | # Windows thumbnail cache files 31 | Thumbs.db 32 | ehthumbs.db 33 | ehthumbs_vista.db 34 | 35 | # Dump file 36 | *.stackdump 37 | 38 | # Folder config file 39 | [Dd]esktop.ini 40 | 41 | # Recycle Bin used on file shares 42 | $RECYCLE.BIN/ 43 | 44 | # Windows Installer files 45 | *.cab 46 | *.msi 47 | *.msix 48 | *.msm 49 | *.msp 50 | 51 | # Windows shortcuts 52 | *.lnk 53 | 54 | ### JetBrains template 55 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 56 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 57 | 58 | # User-specific stuff 59 | /.idea/ 60 | 61 | # Gradle and Maven with auto-import 62 | # When using Gradle or Maven with auto-import, you should exclude module files, 63 | # since they will be recreated, and may cause churn. Uncomment if using 64 | # auto-import. 65 | # .idea/modules.xml 66 | # .idea/*.iml 67 | # .idea/modules 68 | 69 | # CMake 70 | cmake-build-*/ 71 | 72 | # Mongo Explorer plugin 73 | .idea/**/mongoSettings.xml 74 | 75 | # File-based project format 76 | *.iws 77 | 78 | # IntelliJ 79 | out/ 80 | 81 | # mpeltonen/sbt-idea plugin 82 | .idea_modules/ 83 | 84 | # JIRA plugin 85 | atlassian-ide-plugin.xml 86 | 87 | # Cursive Clojure plugin 88 | .idea/replstate.xml 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | 96 | # Editor-based Rest Client 97 | .idea/httpRequests 98 | 99 | ### Python template 100 | # Byte-compiled / optimized / DLL files 101 | __pycache__/ 102 | *.py[cod] 103 | *$py.class 104 | 105 | # C extensions 106 | *.so 107 | 108 | # Distribution / packaging 109 | .Python 110 | build/ 111 | develop-eggs/ 112 | dist/ 113 | downloads/ 114 | eggs/ 115 | .eggs/ 116 | lib/ 117 | lib64/ 118 | parts/ 119 | sdist/ 120 | var/ 121 | wheels/ 122 | *.egg-info/ 123 | .installed.cfg 124 | *.egg 125 | MANIFEST 126 | 127 | # PyInstaller 128 | # Usually these files are written by a python script from a template 129 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 130 | *.manifest 131 | *.spec 132 | 133 | # Installer logs 134 | pip-log.txt 135 | pip-delete-this-directory.txt 136 | 137 | # Unit test / coverage reports 138 | htmlcov/ 139 | .tox/ 140 | .coverage 141 | .coverage.* 142 | .cache 143 | nosetests.xml 144 | coverage.xml 145 | *.cover 146 | .hypothesis/ 147 | .pytest_cache/ 148 | 149 | # Translations 150 | *.mo 151 | *.pot 152 | 153 | # Django stuff: 154 | *.log 155 | local_settings.py 156 | db.sqlite3 157 | 158 | # Flask stuff: 159 | instance/ 160 | .webassets-cache 161 | 162 | # Scrapy stuff: 163 | .scrapy 164 | 165 | # Sphinx documentation 166 | docs/_build/ 167 | 168 | # PyBuilder 169 | target/ 170 | 171 | # Jupyter Notebook 172 | .ipynb_checkpoints 173 | 174 | # pyenv 175 | .python-version 176 | 177 | # celery beat schedule file 178 | celerybeat-schedule 179 | 180 | # SageMath parsed files 181 | *.sage.py 182 | 183 | # Environments 184 | .env 185 | .venv 186 | env/ 187 | venv/ 188 | ENV/ 189 | env.bak/ 190 | venv.bak/ 191 | 192 | # Spyder project settings 193 | .spyderproject 194 | .spyproject 195 | 196 | # Rope project settings 197 | .ropeproject 198 | 199 | # mkdocs documentation 200 | /site 201 | 202 | # mypy 203 | .mypy_cache/ 204 | 205 | ### Vim template 206 | # Swap 207 | [._]*.s[a-v][a-z] 208 | [._]*.sw[a-p] 209 | [._]s[a-rt-v][a-z] 210 | [._]ss[a-gi-z] 211 | [._]sw[a-p] 212 | 213 | # Session 214 | Session.vim 215 | 216 | # Temporary 217 | .netrwhist 218 | # Auto-generated tag files 219 | tags 220 | # Persistent undo 221 | [._]*.un~ 222 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.4' 5 | - '3.5' 6 | - '3.6' 7 | - '3.6-dev' 8 | - '3.7-dev' 9 | install: 10 | - pip install -r requirements/test.txt 11 | script: 12 | - python -m pytest --cov=chatette tests/ 13 | after_script: 14 | - cd ~ 15 | after_success: 16 | - codecov 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## TL;DR 4 | 5 | *Just be excellent to each other.* 6 | 7 | ## Our Pledge 8 | 9 | In the interest of fostering an open and welcoming environment, we as 10 | contributors and maintainers pledge to making participation in our project and 11 | our community a harassment-free experience for everyone, regardless of age, body 12 | size, disability, ethnicity, sex characteristics, gender identity and expression, 13 | level of experience, education, socio-economic status, nationality, personal 14 | appearance, race, religion, or sexual identity and orientation. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to creating a positive environment 19 | include: 20 | 21 | * Using welcoming and inclusive language 22 | * Being respectful of differing viewpoints and experiences 23 | * Gracefully accepting constructive criticism 24 | * Focusing on what is best for the community 25 | * Showing empathy towards other community members 26 | 27 | Examples of unacceptable behavior by participants include: 28 | 29 | * The use of sexualized language or imagery and unwelcome sexual attention or 30 | advances 31 | * Trolling, insulting/derogatory comments, and personal or political attacks 32 | * Public or private harassment 33 | * Publishing others' private information, such as a physical or electronic 34 | address, without explicit permission 35 | * Other conduct which could reasonably be considered inappropriate in a 36 | professional setting 37 | 38 | ## Our Responsibilities 39 | 40 | Project maintainers are responsible for clarifying the standards of acceptable 41 | behavior and are expected to take appropriate and fair corrective action in 42 | response to any instances of unacceptable behavior. 43 | 44 | Project maintainers have the right and responsibility to remove, edit, or 45 | reject comments, commits, code, wiki edits, issues, and other contributions 46 | that are not aligned to this Code of Conduct, or to ban temporarily or 47 | permanently any contributor for other behaviors that they deem inappropriate, 48 | threatening, offensive, or harmful. 49 | 50 | ## Scope 51 | 52 | This Code of Conduct applies both within project spaces and in public spaces 53 | when an individual is representing the project or its community. Examples of 54 | representing a project or community include using an official project e-mail 55 | address, posting via an official social media account, or acting as an appointed 56 | representative at an online or offline event. Representation of a project may be 57 | further defined and clarified by project maintainers. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported by contacting the project team on this GitHub repository by mentioning the name of the maintainer, or at simon.gustin@hotmail.com. 63 | All complaints will be reviewed and investigated and will result in a response that 64 | is deemed necessary and appropriate to the circumstances. The project team is 65 | obligated to maintain confidentiality with regard to the reporter of an incident. 66 | Further details of specific enforcement policies may be posted separately. 67 | 68 | Project maintainers who do not follow or enforce the Code of Conduct in good 69 | faith may face temporary or permanent repercussions as determined by other 70 | members of the project's leadership. 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 75 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 76 | 77 | [homepage]: https://www.contributor-covenant.org 78 | 79 | For answers to common questions about this code of conduct, see 80 | https://www.contributor-covenant.org/faq 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | This repository follows the motto *code first, discuss later*. If you want to add a feature or make a change to the code, feel free to do so by forking the repo and make a pull request when you deem your change worth being merged into the upstream code. 3 | This pull request can then be discussed with the maintainer and possibly accepted. 4 | 5 | Please note we have a code of conduct, please follow it in all your interactions with the project. 6 | 7 | ## Pull Request Process 8 | 1. Update the README.md with details of changes to the interface (this includes new environment variables, exposed ports, useful file locations and container parameters). 9 | 2. Make a pull request on the GitHub repository. The branch to merge to pull request in should be `dev` (unless otherwise discussed). 10 | 3. The pull request will then be discussed, and accepted or rejected. If changes are required before a merge, please make those changes as soon as possible. 11 | 12 | Please do not increase the version numbers in any examples files and the README.md. The maintainer is in charge of releasing new versions. 13 | 14 | ## Code of Conduct 15 | ### TL;DR 16 | *Just be excellent to each other.* 17 | 18 | ### Our Pledge 19 | In the interest of fostering an open and welcoming environment, we as 20 | contributors and maintainers pledge to making participation in our project and 21 | our community a harassment-free experience for everyone, regardless of age, body 22 | size, disability, ethnicity, gender identity and expression, level of experience, 23 | nationality, personal appearance, race, religion, or sexual identity and 24 | orientation. 25 | 26 | ### Our Standards 27 | Examples of behavior that contributes to creating a positive environment 28 | include: 29 | * Using welcoming and inclusive language 30 | * Being respectful of differing viewpoints and experiences 31 | * Gracefully accepting constructive criticism 32 | * Focusing on what is best for the community 33 | * Showing empathy towards other community members 34 | 35 | Examples of unacceptable behavior by participants include: 36 | * The use of sexualized language or imagery and unwelcome sexual attention or 37 | advances 38 | * Trolling, insulting/derogatory comments, and personal or political attacks 39 | * Public or private harassment 40 | * Publishing others' private information, such as a physical or electronic 41 | address, without explicit permission 42 | * Other conduct which could reasonably be considered inappropriate in a 43 | professional setting 44 | 45 | ### Our Responsibilities 46 | Project maintainers are responsible for clarifying the standards of acceptable 47 | behavior and are expected to take appropriate and fair corrective action in 48 | response to any instances of unacceptable behavior. 49 | 50 | Project maintainers have the right and responsibility to remove, edit, or 51 | reject comments, commits, code, wiki edits, issues, and other contributions 52 | that are not aligned to this Code of Conduct, or to ban temporarily or 53 | permanently any contributor for other behaviors that they deem inappropriate, 54 | threatening, offensive, or harmful. 55 | 56 | ### Scope 57 | This Code of Conduct applies both within project spaces and in public spaces 58 | when an individual is representing the project or its community. Examples of 59 | representing a project or community include using an official project e-mail 60 | address, posting via an official social media account, or acting as an appointed 61 | representative at an online or offline event. Representation of a project may be 62 | further defined and clarified by project maintainers. 63 | 64 | ### Enforcement 65 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 66 | reported by mentioning the name of the maintainer in the relevant thread. 67 | All complaints will be reviewed and investigated and will result in a response that 68 | is deemed necessary and appropriate to the circumstances. The project team is 69 | obligated to maintain confidentiality with regard to the reporter of an incident. 70 | Further details of specific enforcement policies may be posted separately. 71 | 72 | Project maintainers who do not follow or enforce the Code of Conduct in good 73 | faith may face temporary or permanent repercussions as determined by other 74 | members of the project's leadership. 75 | 76 | ### Attribution 77 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 78 | available at [http://contributor-covenant.org/version/1/4][version] 79 | 80 | [homepage]: http://contributor-covenant.org 81 | [version]: http://contributor-covenant.org/version/1/4/ 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SimGus 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. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /chatette/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | """ 4 | Module `chatette` 5 | A generator of example sentences based on templates. 6 | """ 7 | 8 | # Retrieve the version number using setuptools 9 | import pkg_resources 10 | try: 11 | __version__ = pkg_resources.require("chatette")[0].version 12 | except pkg_resources.DistributionNotFound: 13 | __version__ = "" # TODO: find another way 14 | -------------------------------------------------------------------------------- /chatette/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import argparse 5 | import sys 6 | 7 | from chatette import __version__, prechecks 8 | from chatette.facade import Facade 9 | from chatette.cli.interpreter import CommandLineInterpreter 10 | 11 | 12 | def main(): 13 | argument_parser = make_argument_parser() 14 | if len(sys.argv[1:]) == 0: 15 | argument_parser.print_help() 16 | argument_parser.exit() 17 | 18 | args = argument_parser.parse_args() 19 | 20 | prechecks.ensure_preconditions() 21 | prechecks.check_for_deprecations() 22 | 23 | if not args.interactive_mode and args.interactive_commands_file is None: 24 | facade = Facade.get_or_create_from_args(args) 25 | facade.run() 26 | else: 27 | cli = CommandLineInterpreter(args) 28 | cli.wait_for_input() 29 | 30 | 31 | def make_argument_parser(): 32 | # pylint: disable=bad-continuation 33 | argument_parser = argparse.ArgumentParser( 34 | description="Chatette v" + __version__ + " -- " + 35 | "Generates NLU datasets from template files", 36 | epilog="SimGus -- 2018 -- Released under MIT license", 37 | prog="Chatette", 38 | add_help=True 39 | ) 40 | 41 | _add_positional_arguments( 42 | argument_parser, ("-i" in sys.argv or "--interactive" in sys.argv) 43 | ) 44 | 45 | argument_parser.add_argument( 46 | "-v", "--version", action="version", version="%(prog)s v" + __version__, 47 | help="Print the version number of the module" 48 | ) 49 | _add_optional_arguments(argument_parser) 50 | 51 | return argument_parser 52 | 53 | 54 | def _add_positional_arguments(argument_parser, should_be_optional=False): 55 | if should_be_optional: 56 | argument_parser.add_argument( 57 | "input", type=str, 58 | nargs="?", 59 | help="Path to master template file" 60 | ) 61 | else: 62 | argument_parser.add_argument( 63 | "input", type=str, 64 | help="Path to master template file" 65 | ) 66 | 67 | 68 | def _add_optional_arguments(argument_parser): 69 | argument_parser.add_argument( 70 | "-o", "--out", dest="output", required=False, type=str, default=None, 71 | help="Output directory path" 72 | ) 73 | argument_parser.add_argument( 74 | "-l", "--local", dest="local", required=False, 75 | action="store_true", default=False, 76 | help="Change the base directory for output files " + \ 77 | "from the current working directory to the directory containing " + \ 78 | "the template file" 79 | ) 80 | argument_parser.add_argument( 81 | "-a", "--adapter", dest="adapter", required=False, 82 | type=str, default="rasa", 83 | help="Write adapter. " + \ 84 | "Possible values: ['rasa', 'rasamd' or 'rasa-md', 'jsonl']" 85 | ) 86 | argument_parser.add_argument( 87 | "--base-file", dest="base_filepath", 88 | required=False, type=str, default=None, 89 | help="Path to base file to extend with examples and synonyms. " + \ 90 | "Only with Rasa adapter." 91 | ) 92 | argument_parser.add_argument( 93 | "-s", "--seed", dest="seed", 94 | required=False, type=str, default=None, 95 | help="Seed for the random generator (any string " + \ 96 | "without spaces will work)" 97 | ) 98 | argument_parser.add_argument( 99 | "-i", "--interactive", dest="interactive_mode", 100 | required=False, action="store_true", default=False, 101 | help="Runs Chatette in interactive mode" 102 | ) 103 | argument_parser.add_argument( 104 | "-I", "--interactive-commands-file", dest="interactive_commands_file", 105 | required=False, default=None, type=str, 106 | help="Path to a file containing interactive mode commands " + \ 107 | "that will be directly run" 108 | ) 109 | argument_parser.add_argument( 110 | "-f", "--force", dest="force", 111 | required=False, action="store_true", default=False, 112 | help="Don't ask for confirmation before overwriting files and folders" 113 | ) 114 | 115 | 116 | if __name__ == "__main__": 117 | main() 118 | -------------------------------------------------------------------------------- /chatette/adapters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `adapters` 3 | Contains all the adapters, i.e. the classes 4 | responsible for transforming the generated examples 5 | into output file(s). 6 | """ 7 | 8 | from ._base import (Adapter, Batch) 9 | from .jsonl import (JsonListAdapter, ) 10 | from .rasa import (RasaAdapter, ) 11 | 12 | -------------------------------------------------------------------------------- /chatette/adapters/factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.adapters.factory`. 3 | Defines a factory method that allows to create an adapter from a string name. 4 | """ 5 | 6 | from chatette.adapters.jsonl import JsonListAdapter 7 | from chatette.adapters.rasa import RasaAdapter 8 | from chatette.adapters.rasa_md import RasaMdAdapter 9 | 10 | 11 | def create_adapter(adapter_name, base_filepath=None): 12 | """ 13 | Instantiate an adapter and returns it given the name of the adapter as a str. 14 | Names are: 15 | - 'rasa': RasaAdapter 16 | - 'rasa-md' or 'rasamd': RasaMdAdapter 17 | - 'jsonl': JsonListAdapter 18 | """ 19 | if adapter_name is None: 20 | return None 21 | adapter_name = adapter_name.lower() 22 | if adapter_name == 'rasa': 23 | return RasaAdapter(base_filepath) 24 | elif adapter_name in ('rasa-md', 'rasamd'): 25 | return RasaMdAdapter(base_filepath) 26 | elif adapter_name == 'jsonl': 27 | return JsonListAdapter(base_filepath) 28 | raise ValueError("Unknown adapter was selected.") 29 | -------------------------------------------------------------------------------- /chatette/adapters/jsonl.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import io 3 | import json 4 | import os 5 | 6 | from chatette.utils import cast_to_unicode 7 | from ._base import Adapter 8 | 9 | 10 | class JsonListAdapter(Adapter): 11 | @classmethod 12 | def _get_file_extension(cls): 13 | return "jsonl" 14 | 15 | def prepare_example(self, example): 16 | return json.dumps( 17 | cast_to_unicode(example.as_dict()), 18 | ensure_ascii=False, sort_keys=True 19 | ) 20 | 21 | def _write_batch(self, output_file_handle, batch): 22 | output_file_handle.writelines([ 23 | self.prepare_example(example) + "\n" 24 | for example in batch.examples 25 | ]) 26 | 27 | def write(self, output_directory, examples, synonyms): 28 | super(JsonListAdapter, self).write(output_directory, examples, synonyms) 29 | 30 | processed_synonyms = self.__format_synonyms(synonyms) 31 | if processed_synonyms is not None: 32 | synonyms_file_path = os.path.join(output_directory, "synonyms.json") 33 | with io.open(synonyms_file_path, 'w') as output_file: 34 | output_file.write( 35 | json.dumps( 36 | cast_to_unicode(processed_synonyms), 37 | ensure_ascii=False, sort_keys=True, indent=2 38 | ) 39 | ) 40 | 41 | 42 | @classmethod 43 | def __format_synonyms(cls, synonyms): 44 | result = { 45 | key: values 46 | for (key, values) in synonyms.items() 47 | if len(values) > 1 or values[0] != key 48 | } 49 | if not result: 50 | return None 51 | return result 52 | -------------------------------------------------------------------------------- /chatette/adapters/rasa.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.adapters.rasa` 4 | Contains the definition of the adapter that writes output in JSON 5 | for Rasa NLU. 6 | """ 7 | 8 | import io 9 | import json 10 | 11 | from chatette.utils import cast_to_unicode 12 | from ._base import Adapter 13 | 14 | 15 | class RasaAdapter(Adapter): 16 | def __init__(self, base_filepath=None, batch_size=10000):# -> None: 17 | super(RasaAdapter, self).__init__(base_filepath, batch_size) 18 | self._base_file_contents = None 19 | 20 | @classmethod 21 | def _get_file_extension(cls): 22 | return "json" 23 | 24 | 25 | def _write_batch(self, output_file_handle, batch): 26 | rasa_entities = [self.prepare_example(ex) for ex in batch.examples] # TODO rename this? 27 | 28 | json_data = self._get_base_to_extend() 29 | json_data["rasa_nlu_data"]["common_examples"] = rasa_entities 30 | json_data["rasa_nlu_data"]["entity_synonyms"] = \ 31 | self.__format_synonyms(batch.synonyms) 32 | json_data = cast_to_unicode(json_data) 33 | 34 | output_file_handle.write( 35 | json.dumps(json_data, ensure_ascii=False, indent=2, sort_keys=True) 36 | ) 37 | 38 | 39 | def prepare_example(self, example): 40 | def entity_to_rasa(entity): 41 | return { 42 | "entity": entity.slot_name, 43 | "value": entity.value, 44 | "start": entity._start_index, 45 | "end": entity._start_index + entity._len, 46 | } 47 | 48 | return { 49 | "intent": example.intent_name, 50 | "text": example.text, 51 | "entities": [entity_to_rasa(entity) for entity in example.entities] 52 | } 53 | 54 | @classmethod 55 | def __format_synonyms(cls, synonyms): 56 | # {str: [str]} -> [{"value": str, "synonyms": [str]}] 57 | return [ 58 | { 59 | "value": slot_name, 60 | "synonyms": synonyms[slot_name] 61 | } 62 | for slot_name in synonyms 63 | if len(synonyms[slot_name]) > 1 64 | ] 65 | 66 | 67 | def _get_base_to_extend(self): 68 | if self._base_file_contents is None: 69 | if self._base_filepath is None: 70 | return self._get_empty_base() 71 | with io.open(self._base_filepath, 'r') as base_file: 72 | self._base_file_contents = json.load(base_file) 73 | self.check_base_file_contents() 74 | return self._base_file_contents 75 | 76 | def _get_empty_base(self): 77 | return { 78 | "rasa_nlu_data": { 79 | "common_examples": None, 80 | "regex_features": [], 81 | "lookup_tables": [], 82 | "entity_synonyms": None, 83 | } 84 | } 85 | 86 | def check_base_file_contents(self): 87 | """ 88 | Checks that `self._base_file_contents` contains well formatted JSON. 89 | Throws a `SyntaxError` if the data is incorrect. 90 | """ 91 | if self._base_file_contents is None: 92 | return 93 | if not isinstance(self._base_file_contents, dict): 94 | self._base_file_contents = None 95 | raise SyntaxError( 96 | "Couldn't load valid data from base file '" + \ 97 | self._base_filepath + "'" 98 | ) 99 | else: 100 | if "rasa_nlu_data" not in self._base_file_contents: 101 | self._base_file_contents = None 102 | raise SyntaxError( 103 | "Expected 'rasa_nlu_data' as a root of base file '" + \ 104 | self._base_filepath + "'") 105 | -------------------------------------------------------------------------------- /chatette/adapters/rasa_md.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.adapters.rasa_md` 4 | Contains the definition of the adapter that writes output in Markdown 5 | for Rasa NLU. 6 | """ 7 | 8 | import os 9 | import io 10 | from six import string_types 11 | 12 | from chatette import __version__ 13 | from chatette.adapters._base import Adapter 14 | from chatette.utils import append_to_list_in_dict, cast_to_unicode 15 | 16 | 17 | class RasaMdAdapter(Adapter): 18 | def __init__(self, base_filepath=None): 19 | super(RasaMdAdapter, self).__init__(base_filepath, None) 20 | self._base_file_contents = None 21 | 22 | 23 | @classmethod 24 | def _get_file_extension(cls): 25 | return "md" 26 | def __get_file_name(self, batch, output_directory, single_file): 27 | if single_file: 28 | return \ 29 | os.path.join( 30 | output_directory, "nlu." + self._get_file_extension() 31 | ) 32 | raise ValueError( 33 | "Tried to generate several files with Rasa Markdown adapter." 34 | ) 35 | 36 | 37 | def _write_batch(self, output_file_handle, batch): 38 | output_file_handle.write( 39 | "\n\n" 41 | ) 42 | 43 | prepared_examples = dict() 44 | for example in batch.examples: 45 | append_to_list_in_dict( 46 | prepared_examples, 47 | example.intent_name, self.prepare_example(example) 48 | ) 49 | 50 | for intent_name in prepared_examples: 51 | output_file_handle.write( 52 | "## intent:" + cast_to_unicode(intent_name) + '\n' 53 | ) 54 | for text in prepared_examples[intent_name]: 55 | output_file_handle.write(cast_to_unicode("- " + text + '\n')) 56 | output_file_handle.write(cast_to_unicode('\n')) 57 | 58 | output_file_handle.write( 59 | cast_to_unicode(self.__format_synonyms(batch.synonyms)) 60 | ) 61 | 62 | remainder = self._get_base_to_extend() 63 | if remainder is not None: 64 | output_file_handle.write(cast_to_unicode(remainder) + '\n') 65 | 66 | 67 | def prepare_example(self, example): 68 | if len(example.entities) == 0: 69 | return example.text 70 | 71 | sorted_entities = \ 72 | sorted( 73 | example.entities, 74 | reverse=True, 75 | key=lambda entity: entity._start_index 76 | ) 77 | result = example.text[:] 78 | for entity in sorted_entities: 79 | result = \ 80 | result[:entity._start_index] + "[" + \ 81 | result[entity._start_index:entity._start_index + entity._len] + \ 82 | "](" + entity.slot_name + ")" + \ 83 | result[entity._start_index + entity._len:] 84 | return result 85 | 86 | 87 | @classmethod 88 | def __format_synonyms(cls, synonyms): 89 | """ 90 | Returns a str that will be written in the output files for all 91 | the synonyms `synonyms`. 92 | """ 93 | result = "" 94 | for syn_name in synonyms: 95 | if len(synonyms[syn_name]) > 1: 96 | result += "## synonym:" + syn_name + '\n' 97 | for syn in synonyms[syn_name]: 98 | if syn != syn_name: 99 | result += "- " + syn + '\n' 100 | result += '\n' 101 | return result 102 | 103 | 104 | def _get_base_to_extend(self): 105 | if self._base_file_contents is None: 106 | if self._base_filepath is None: 107 | return self._get_empty_base() 108 | with io.open(self._base_filepath, 'r') as base_file: 109 | self._base_file_contents = ''.join(base_file.readlines()) 110 | self.check_base_file_contents() 111 | return self._base_file_contents 112 | 113 | def _get_empty_base(self): 114 | return "" 115 | 116 | def check_base_file_contents(self): 117 | if self._base_file_contents is None: 118 | return 119 | if not isinstance(self._base_file_contents, string_types): 120 | raise SyntaxError( 121 | "Couldn't load valid data from base file '" + \ 122 | self._base_file_contents + "'" 123 | ) 124 | -------------------------------------------------------------------------------- /chatette/cli/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli`. 3 | Contains every class related to 4 | the interactive mode of execution of the program. 5 | """ 6 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands`. 3 | Contains all the implementations of commands for the interactive mode 4 | (implemented using the design pattern "strategy"). 5 | """ 6 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/add_rule_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.add_rule_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `add-rule` which allows to add a rule to a unit definition. 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | 9 | from chatette.facade import Facade 10 | from chatette.units.ast import AST 11 | 12 | 13 | class AddRuleCommand(CommandStrategy): 14 | usage_str = 'add-rule "" ""' 15 | 16 | def execute(self): 17 | # TODO support variations 18 | if len(self.command_tokens) < 4: 19 | self.print_wrapper.error_log( 20 | "Missing some arguments\nUsage: " + self.usage_str) 21 | return 22 | 23 | unit_type = \ 24 | CommandStrategy.get_unit_type_from_str(self.command_tokens[1]) 25 | if unit_type is None: 26 | self.print_wrapper.error_log( 27 | "Unknown unit type: '" + str(self.command_tokens[1]) + "'." 28 | ) 29 | return 30 | 31 | unit_regex = self.get_regex_name(self.command_tokens[2]) 32 | rule_str = CommandStrategy.remove_quotes(self.command_tokens[3]) 33 | if unit_regex is None: 34 | try: 35 | [unit_name, variation_name] = \ 36 | CommandStrategy.split_exact_unit_name( 37 | self.command_tokens[2] 38 | ) 39 | except SyntaxError: 40 | self.print_wrapper.error_log( 41 | "Unit identifier couldn't be interpreted. " + \ 42 | "Did you mean to escape some hashtags '#'?" 43 | ) 44 | return 45 | self._add_rule(unit_type, unit_name, variation_name, rule_str) 46 | else: 47 | count = 0 48 | for unit_name in self.next_matching_unit_name( 49 | unit_type, unit_regex 50 | ): 51 | self._add_rule(unit_type, unit_name, None, rule_str) 52 | count += 1 53 | if count == 0: 54 | self.print_wrapper.write("No " + unit_type.name + " matched.") 55 | 56 | def _add_rule(self, unit_type, unit_name, variation_name, rule_str): 57 | parser = Facade.get_or_create().parser 58 | rule_tokens = parser.lexer.lex("\t" + rule_str) 59 | rule = parser._parse_rule(rule_tokens[1:]) 60 | 61 | unit = AST.get_or_create()[unit_type][unit_name] 62 | unit.add_rule(rule, variation_name) 63 | 64 | self.print_wrapper.write( 65 | "Rule successfully added to " + unit_type.name + " '" + \ 66 | unit_name + "'." 67 | ) 68 | 69 | 70 | # Override abstract methods 71 | def execute_on_unit(self, facade, unit_type, unit_name): 72 | raise NotImplementedError() 73 | def finish_execution(self, facade): 74 | raise NotImplementedError() 75 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/declare_command.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.cli.interactive_commands.declare_command`. 4 | Contains the strategy class that represents the interactive mode command 5 | `declare` which creates a new empty unit and add it to the list of units 6 | of the parser. 7 | """ 8 | 9 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 10 | 11 | from chatette.utils import UnitType 12 | from chatette.parsing import \ 13 | AliasDefBuilder, SlotDefBuilder, IntentDefBuilder 14 | 15 | from chatette.units.ast import AST 16 | 17 | 18 | class DeclareCommand(CommandStrategy): 19 | usage_str = 'declare ""' 20 | 21 | def execute(self): 22 | """ 23 | Implements the command `rule` which generates a certain number of 24 | examples according to a provided rule. 25 | """ 26 | if len(self.command_tokens) < 3: 27 | self.print_wrapper.error_log( 28 | "Missing some arguments\nUsage: " + self.usage_str 29 | ) 30 | return 31 | 32 | unit_type = \ 33 | CommandStrategy.get_unit_type_from_str(self.command_tokens[1]) 34 | if unit_type is None: 35 | self.print_wrapper.error_log( 36 | "Unknown unit type: '" + str(self.command_tokens[1]) + "'." 37 | ) 38 | return 39 | 40 | try: 41 | [unit_name, variation_name] = \ 42 | CommandStrategy.split_exact_unit_name(self.command_tokens[2]) 43 | except SyntaxError: 44 | self.print_wrapper.error_log( 45 | "Unit identifier couldn't be interpreted. " + \ 46 | "Did you mean to escape some hashtags '#'?" 47 | ) 48 | return 49 | if variation_name is not None and variation_name != "": 50 | self.print_wrapper.error_log( 51 | "Variation name detected, while units cannot be " + \ 52 | "declared with a variation. " + \ 53 | "Did you mean to escape some hashtags '#'?" 54 | ) 55 | return 56 | 57 | relevant_dict = AST.get_or_create()[unit_type] 58 | if unit_type == UnitType.alias: 59 | builder = AliasDefBuilder() 60 | builder.identifier = unit_name 61 | declaration = builder.create_concrete() 62 | elif unit_type == UnitType.slot: 63 | builder = SlotDefBuilder() 64 | builder.identifier = unit_name 65 | declaration = builder.create_concrete() 66 | else: # intent 67 | builder = IntentDefBuilder() 68 | builder.identifier = unit_name 69 | declaration = builder.create_concrete() 70 | 71 | if unit_name in relevant_dict: 72 | self.print_wrapper.write( 73 | unit_type.name.capitalize() + " '" + unit_name + \ 74 | "' was NOT defined, as it already is defined." 75 | ) 76 | return 77 | relevant_dict[unit_name] = declaration 78 | self.print_wrapper.write( 79 | unit_type.name.capitalize() + " '" + unit_name + \ 80 | "' was successfully declared." 81 | ) 82 | 83 | 84 | # Override abstract methods 85 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 86 | raise NotImplementedError() 87 | def finish_execution(self): 88 | raise NotImplementedError() 89 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/delete_command.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.cli.interactive_commands.delete_command`. 4 | Contains the strategy class that represents the interactive mode command 5 | `delete` which deletes a unit declaration from the parser. 6 | """ 7 | 8 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 9 | 10 | from chatette.units.ast import AST 11 | 12 | 13 | class DeleteCommand(CommandStrategy): 14 | usage_str = 'delete ""' # TODO support variations 15 | def __init__(self, command_str, quiet=False): 16 | super(DeleteCommand, self).__init__(command_str, quiet) 17 | self._units_to_delete = [] 18 | 19 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 20 | self._units_to_delete.append((unit_type, unit_name, variation_name)) 21 | 22 | def finish_execution(self): 23 | for (unit_type, unit_name, variation_name) in self._units_to_delete: 24 | try: 25 | AST.get_or_create().delete_unit( 26 | unit_type, unit_name, variation_name 27 | ) 28 | self.print_wrapper.write( 29 | unit_type.name.capitalize() + " '" + unit_name + \ 30 | "' was successfully deleted." 31 | ) 32 | except KeyError: 33 | self.print_wrapper.write( 34 | unit_type.name.capitalize() + " '" + unit_name + \ 35 | "' was not defined." 36 | ) 37 | self._units_to_delete = [] 38 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/examples_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.definition_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `examples` which generates several (or all) possible examples for a given unit. 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | 9 | from chatette.units.ast import AST 10 | 11 | 12 | class ExamplesCommand(CommandStrategy): 13 | usage_str = 'examples "" []' 14 | def __init__(self, command_str, quiet=False): 15 | super(ExamplesCommand, self).__init__(command_str, quiet) 16 | self.nb_examples = None 17 | 18 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 19 | if self.nb_examples is None: # Caching the parsed number 20 | self.nb_examples = -1 # All examples should be generated 21 | if len(self.command_tokens) > 3: 22 | try: 23 | self.nb_examples = int(self.command_tokens[3]) 24 | except ValueError: 25 | self.print_wrapper.error_log( 26 | "The number of examples to be generated is invalid: " + \ 27 | "it must be an integer (no other characters allowed)." 28 | ) 29 | return 30 | try: 31 | definition = \ 32 | AST.get_or_create()[unit_type][unit_name] 33 | except KeyError: 34 | self.print_wrapper.write( 35 | unit_type.name.capitalize() + " '" + unit_name + \ 36 | "' is not defined." 37 | ) 38 | return 39 | if self.nb_examples != -1: 40 | examples = definition.generate_nb_possibilities( 41 | self.nb_examples, variation_name=variation_name 42 | ) 43 | else: 44 | examples = definition.generate_all(variation_name=variation_name) 45 | self.print_wrapper.write( 46 | "Examples for " + unit_type.name + " '" + unit_name + "':" 47 | ) 48 | for ex in examples: 49 | self.print_wrapper.write(ex) 50 | self.print_wrapper.write("") 51 | 52 | def finish_execution(self): 53 | self.nb_examples = None 54 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/execute_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.execute_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `execute` which opens a file containing a bunch of commands and executes them. 5 | """ 6 | 7 | import io 8 | 9 | from chatette.cli.interactive_commands.command_strategy import \ 10 | CommandStrategy, REDIRECTION_SYM, REDIRECTION_APPEND_SYM 11 | 12 | 13 | class ExecuteCommand(CommandStrategy): 14 | usage_str = 'execute ' 15 | 16 | def execute(self): 17 | """ 18 | Implements the command `execute` which executes each line of a file 19 | as if they were commands (unless they start with a double slash `//`). 20 | """ 21 | if len(self.command_tokens) < 2: 22 | self.print_wrapper.error_log( 23 | "Missing some arguments\nUsage: " + self.usage_str 24 | ) 25 | return 26 | 27 | # TODO there may be a better way to get the file name 28 | with io.open(self.split_exact_unit_name(self.command_tokens[1])[0], 'r') as command_file: 29 | commands = [ 30 | line.rstrip() 31 | for line in command_file.readlines() 32 | if not line.lstrip().startswith("//") 33 | ] 34 | redirection_tuple = self.print_wrapper.get_redirection() 35 | if redirection_tuple is not None: 36 | (_, redirection_filepath) = redirection_tuple 37 | if redirection_filepath is not None: 38 | text_to_append = \ 39 | ' ' + REDIRECTION_APPEND_SYM + ' ' + redirection_filepath 40 | for (i, cmd) in enumerate(commands): 41 | if ( 42 | REDIRECTION_APPEND_SYM not in cmd 43 | and REDIRECTION_SYM not in cmd 44 | ): 45 | commands[i] += text_to_append 46 | return commands 47 | 48 | 49 | # Override abstract methods 50 | def execute_on_unit(self, unit_type, unit_name): 51 | raise NotImplementedError() 52 | def finish_execution(self): 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/exist_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.exist_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `exist` which verifies whether a unit is declared or not. 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | 9 | from chatette.units.ast import AST 10 | 11 | 12 | class ExistCommand(CommandStrategy): 13 | usage_str = 'exist ""' 14 | 15 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 16 | try: 17 | unit = AST.get_or_create()[unit_type][unit_name] 18 | self.print_wrapper.write(unit.short_description()) 19 | if variation_name is not None: 20 | if variation_name in unit._variation_rules: 21 | self.print_wrapper.write( 22 | "Variation '" + variation_name + \ 23 | "' is defined for this " + unit.unit_type.name + "." 24 | ) 25 | else: 26 | self.print_wrapper.write( 27 | "Variation '" + variation_name + \ 28 | "' is not defined for this " + unit.unit_type.name + "." 29 | ) 30 | except KeyError: 31 | self.print_wrapper.write( 32 | unit_type.name.capitalize() + " '" + unit_name + \ 33 | "' is not defined." 34 | ) 35 | self.print_wrapper.write("") 36 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/exit_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.exit_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `exit` which exits the interactive mode. 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | 9 | 10 | class ExitCommand(CommandStrategy): 11 | 12 | def execute(self): 13 | pass 14 | 15 | def should_exit(self): 16 | return True 17 | 18 | 19 | # Override abstract methods 20 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 21 | raise NotImplementedError() 22 | def finish_execution(self): 23 | raise NotImplementedError() 24 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/hide_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.hide_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `hide` which hides unit definitions (storing them somewhere to be able to 5 | unhide them later). 6 | """ 7 | 8 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 9 | 10 | from chatette.units.ast import AST 11 | 12 | 13 | class HideCommand(CommandStrategy): 14 | usage_str = 'hide ""' 15 | stored_units = {"alias": dict(), "slot": dict(), "intent": dict()} 16 | stored_variations = {"alias": dict(), "slot": dict(), "intent": dict()} 17 | def __init__(self, command_str, quiet=False): 18 | super(HideCommand, self).__init__(command_str, quiet) 19 | self._units_to_delete = [] 20 | self._var_to_delete = [] 21 | 22 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 23 | try: 24 | unit = AST.get_or_create()[unit_type][unit_name] 25 | if variation_name is None: 26 | self.stored_units[unit_type.name][unit_name] = unit 27 | self._units_to_delete.append((unit_type, unit_name)) 28 | self.print_wrapper.write( 29 | unit_type.name.capitalize() + " '" + unit_name + \ 30 | "' was successfully hidden." 31 | ) 32 | else: 33 | if variation_name not in unit._variation_rules: 34 | self.print_wrapper.error_log( 35 | "Couldn't find variation '" + variation_name + \ 36 | "' in " + unit_type.name + " '" + unit_name + "'." 37 | ) 38 | return 39 | self._var_to_delete.append((unit_type, unit_name, variation_name)) 40 | rules = unit._variation_rules[variation_name] 41 | if unit_name not in self.stored_variations[unit_type.name]: 42 | self.stored_variations[unit_type.name][unit_name] = \ 43 | {variation_name: rules} 44 | else: 45 | self.stored_variations[unit_type.name][unit_name][variation_name] = \ 46 | rules 47 | self.print_wrapper.write( 48 | "Variation '" + variation_name + "' of " + \ 49 | unit_type.name + " '" + unit_name + \ 50 | "' was successfully hidden." 51 | ) 52 | except KeyError: 53 | self.print_wrapper.write( 54 | unit_type.name.capitalize() + " '" + unit_name + \ 55 | "' was not defined." 56 | ) 57 | 58 | def finish_execution(self): 59 | for (unit_type, unit_name) in self._units_to_delete: 60 | AST.get_or_create().delete_unit(unit_type, unit_name) 61 | self._units_to_delete = [] 62 | 63 | for (unit_type, unit_name, variation_name) in self._var_to_delete: 64 | AST.get_or_create().delete_unit( 65 | unit_type, unit_name, variation_name 66 | ) 67 | self._var_to_delete = [] 68 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/parse_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.parse_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `parse` which parses a new template file. 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | 9 | from chatette.facade import Facade 10 | 11 | 12 | class ParseCommand(CommandStrategy): 13 | 14 | def execute(self): 15 | """ 16 | Implements the command `parse`, 17 | parsing a new template file using the current parser. 18 | """ 19 | if len(self.command_tokens) <= 1: 20 | self.print_wrapper.error_log("Missing template file path\nUsage: " + 21 | "'parse '") 22 | return 23 | file_path = self.command_tokens[1] 24 | 25 | if Facade.was_instantiated(): 26 | facade = Facade.get_or_create() 27 | facade.parse_file(file_path) 28 | else: 29 | facade = Facade(file_path) 30 | facade.run_parsing() 31 | 32 | 33 | # Override abstract methods 34 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 35 | raise NotImplementedError() 36 | def finish_execution(self): 37 | raise NotImplementedError() 38 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/rename_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.rename_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `rename` which changes the name of a unit (if it exists). 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | 9 | from chatette.units.ast import AST 10 | 11 | 12 | class RenameCommand(CommandStrategy): 13 | 14 | def execute(self): 15 | """ 16 | Implements the command `rename` which renames a unit 17 | into something else. Displays an error if the unit wasn't found. 18 | """ 19 | if len(self.command_tokens) < 4: 20 | self.print_wrapper.error_log( 21 | "Missing some arguments\nUsage: " + \ 22 | 'rename "" ' + '""') 23 | return 24 | unit_type = \ 25 | CommandStrategy.get_unit_type_from_str(self.command_tokens[1]) 26 | 27 | if unit_type is None: 28 | self.print_wrapper.error_log( 29 | "Unknown unit type: '" + str(self.command_tokens[1]) + "'." 30 | ) 31 | else: 32 | old_name = CommandStrategy.remove_quotes(self.command_tokens[2]) 33 | new_name = CommandStrategy.remove_quotes(self.command_tokens[3]) 34 | if new_name == "": 35 | self.print_wrapper.error_log( 36 | "An empty name is not a valid " + unit_type.name + " name." 37 | ) 38 | return 39 | 40 | try: 41 | AST.get_or_create().rename_unit(unit_type, old_name, new_name) 42 | self.print_wrapper.write( 43 | unit_type.name.capitalize() + " '" + old_name + \ 44 | "' was successfully renamed to '" + new_name + "'.") 45 | except KeyError: 46 | self.print_wrapper.error_log( 47 | "Couldn't find a unit named '" + str(old_name) + "'." 48 | ) 49 | except ValueError: 50 | self.print_wrapper.error_log( 51 | unit_type.name.capitalize() + " '" + new_name + \ 52 | "' is already in use." 53 | ) 54 | 55 | 56 | # Override abstract methods 57 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 58 | raise NotImplementedError() 59 | def finish_execution(self): 60 | raise NotImplementedError() 61 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/rule_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.rule_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `rule` which generates as many examples as asked that the provided rule 5 | can generate. 6 | """ 7 | 8 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 9 | 10 | from chatette.parsing.parser import Parser 11 | 12 | 13 | class RuleCommand(CommandStrategy): 14 | def execute(self): 15 | """ 16 | Implements the command `rule` which generates a certain number of 17 | examples according to a provided rule. 18 | """ 19 | if len(self.command_tokens) < 2: 20 | self.print_wrapper.error_log("Missing some arguments\nUsage: " + 21 | 'rule "" [= 3: 27 | try: 28 | nb_examples = int(self.command_tokens[2]) 29 | except ValueError: 30 | self.print_wrapper.error_log( 31 | "The number of examples asked (" + \ 32 | self.command_tokens[2] + ") is a valid integer." 33 | ) 34 | 35 | parser = Parser(None) 36 | rule_tokens = parser.lexer.lex("\t" + rule_str) 37 | # pylint: disable=protected-access 38 | rule = parser._parse_rule(rule_tokens[1:]) 39 | 40 | if nb_examples is None: 41 | examples = rule.generate_all() 42 | else: 43 | examples = rule.generate_nb_possibilities(nb_examples) 44 | self.print_wrapper.write("Generated examples:") 45 | for ex in examples: 46 | self.print_wrapper.write(str(ex)) 47 | 48 | 49 | # Override abstract methods 50 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 51 | raise NotImplementedError() 52 | def finish_execution(self): 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/save_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_command.save_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `save` which writes a template file that, when parsed, would make a parser 5 | that is in the state of the current parser. 6 | """ 7 | 8 | from __future__ import print_function 9 | import io 10 | 11 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 12 | from chatette.utils import cast_to_unicode 13 | 14 | from chatette.units.ast import AST 15 | 16 | 17 | class SaveCommand(CommandStrategy): 18 | usage_str = 'save ' 19 | 20 | def execute(self): 21 | if len(self.command_tokens) < 2: 22 | self.print_wrapper.error_log("Missing some arguments\nUsage: " + 23 | self.usage_str) 24 | return 25 | 26 | template_filepath = self.command_tokens[1] 27 | definitions = AST.get_or_create() 28 | with io.open(template_filepath, 'w+') as f: 29 | for intent_name in definitions._intent_definitions: 30 | intent = definitions._intent_definitions[intent_name] 31 | print(cast_to_unicode(intent.as_template_str() + '\n'), file=f) 32 | print(cast_to_unicode(''), file=f) 33 | for alias_name in definitions._alias_definitions: 34 | alias = definitions._alias_definitions[alias_name] 35 | print(cast_to_unicode(alias.as_template_str() + '\n'), file=f) 36 | print(cast_to_unicode(''), file=f) 37 | for slot_name in definitions._slot_definitions: 38 | slot = definitions._slot_definitions[slot_name] 39 | print(cast_to_unicode(slot.as_template_str() + '\n'), file=f) 40 | print(cast_to_unicode(''), file=f) 41 | self.print_wrapper.write("Template file successfully written.") 42 | 43 | 44 | # Override abstract methods 45 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 46 | raise NotImplementedError() 47 | def finish_execution(self): 48 | raise NotImplementedError() 49 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/show_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.show_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `show` which shows information about a unit definition and lists a bunch of 5 | its rules (all if possible). 6 | """ 7 | 8 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 9 | 10 | from chatette.units.ast import AST 11 | 12 | 13 | class ShowCommand(CommandStrategy): 14 | usage_str = 'show ""' 15 | max_nb_rules_to_display = 12 16 | 17 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 18 | try: 19 | unit = AST.get_or_create()[unit_type][unit_name] 20 | self.print_wrapper.write(unit.short_description()) 21 | 22 | if variation_name is None: 23 | self.print_wrapper.write( 24 | "Template rules:\n" + str(unit.as_template_str()) 25 | ) 26 | else: # TODO 27 | pass 28 | except KeyError: 29 | self.print_wrapper.write( 30 | unit_type.name.capitalize() + " '" + unit_name + \ 31 | "' is not defined." 32 | ) 33 | -------------------------------------------------------------------------------- /chatette/cli/interactive_commands/stats_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.cli.interactive_commands.stats_command`. 3 | Contains the strategy class that represents the interactive mode command 4 | `stats` which shows statistics about the parsing. 5 | """ 6 | 7 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 8 | from chatette.statistics import Stats 9 | 10 | 11 | class StatsCommand(CommandStrategy): 12 | 13 | def execute(self): 14 | """Implements the command `stats`, printing parsing statistics.""" 15 | self.print_wrapper.write(str(Stats.get_or_create())) 16 | 17 | 18 | # Override abstract methods 19 | def execute_on_unit(self, unit_type, unit_name, variation_name=None): 20 | raise NotImplementedError() 21 | def finish_execution(self): 22 | raise NotImplementedError() 23 | -------------------------------------------------------------------------------- /chatette/cli/terminal_writer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.terminal_writer`. 3 | Contains a wrapper of the output of commands, that can write to the terminal 4 | (stdout) or to a file. 5 | """ 6 | 7 | from __future__ import print_function 8 | import io 9 | import os.path 10 | from enum import Enum 11 | 12 | 13 | class RedirectionType(Enum): # QUESTION is it possible to merge this with relevant strings? 14 | truncate = 1 15 | append = 2 16 | quiet = 3 17 | 18 | 19 | class TerminalWriter(object): 20 | """Wrapper of `print` that can write to stdout or to a file.""" 21 | def __init__(self, redirection_type=RedirectionType.append, 22 | redirection_file_path=None): 23 | self.redirection_file_path = redirection_file_path 24 | self.buffered_text = None 25 | 26 | self._file_mode = None 27 | self.set_redirection_type(redirection_type) 28 | 29 | def reset(self): 30 | self.redirection_file_path = None 31 | self.buffered_text = None 32 | def set_redirection_type(self, redirection_type): 33 | """ 34 | Sets redirection type. 35 | @pre: `redirection_type` is of type `RedirectionType`. 36 | """ 37 | if redirection_type == RedirectionType.append: 38 | self._file_mode = 'a+' 39 | elif redirection_type == RedirectionType.truncate: 40 | self._file_mode = 'w+' 41 | elif redirection_type == RedirectionType.quiet: 42 | self._file_mode = 'quiet' 43 | 44 | def get_redirection(self): 45 | """ 46 | Returns a 2-tuple containing the type and file path of the redirection. 47 | If this wrapper doesn't redirect to any file (or ignore prints), 48 | returns `None`. 49 | """ 50 | if self._file_mode is None: 51 | return None 52 | if self._file_mode == 'quiet': 53 | return (RedirectionType.quiet, None) 54 | if self._file_mode == 'a+': 55 | return (RedirectionType.append, self.redirection_file_path) 56 | if self._file_mode == 'w+': 57 | return (RedirectionType.truncate, self.redirection_file_path) 58 | return None 59 | 60 | 61 | def write(self, text): 62 | if self.redirection_file_path is None and self._file_mode is None: 63 | print(text) 64 | elif self._file_mode == 'quiet': 65 | return 66 | else: 67 | if self.buffered_text is None: 68 | self.buffered_text = str(text) 69 | else: 70 | self.buffered_text += '\n' + str(text) 71 | 72 | def error_log(self, text): 73 | processed_text = ''.join(['\t' + line + '\n' 74 | for line in text.split('\n')]) 75 | self.write("[ERROR]"+processed_text[:-1]) 76 | 77 | 78 | def flush(self): 79 | """ 80 | Flushes the buffered text to the redirection file 81 | if such a file is provided. 82 | """ 83 | if self.redirection_file_path is not None: 84 | # Create file if it doesn't exist 85 | if not os.path.isfile(self.redirection_file_path): 86 | io.open(self.redirection_file_path, 'w+').close() 87 | # Write to the file if needed 88 | if self.buffered_text is not None: 89 | with io.open(self.redirection_file_path, self._file_mode) as f: 90 | print(self.buffered_text, '\n', sep='', file=f) 91 | self.buffered_text = None 92 | -------------------------------------------------------------------------------- /chatette/configuration.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.configuration` 4 | Contains the singleton storing the current configuration 5 | of the program. 6 | """ 7 | 8 | from chatette.utils import Singleton 9 | from chatette.log import print_warn 10 | 11 | 12 | class Configuration(Singleton): 13 | """ 14 | Singleton containing the current configuration of 15 | the whole program. 16 | """ 17 | _instance = None 18 | def __init__(self): 19 | self.caching_level = 100 # out of 100 20 | 21 | def set_caching_level(self, new_level): 22 | print_warn( 23 | "Setting caching level to " + str(new_level) + \ 24 | " for performance reasons." 25 | ) 26 | if new_level < 0 or new_level > 100: 27 | raise ValueError( 28 | "Tried to set the caching level to an invalid level (" + \ 29 | str(new_level) + ")." 30 | ) 31 | self.caching_level = new_level 32 | -------------------------------------------------------------------------------- /chatette/deprecations.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from warnings import warn 4 | 5 | from chatette.utils import Singleton, cast_to_unicode 6 | from chatette.log import print_warn 7 | from chatette.parsing.utils import \ 8 | OLD_COMMENT_SYM, COMMENT_SYM, \ 9 | OLD_CHOICE_START, OLD_CHOICE_END, CHOICE_START, CHOICE_END 10 | 11 | 12 | class Deprecations(Singleton): 13 | _instance = None 14 | def __init__(self): 15 | self._old_comment_warned = False 16 | self._old_choice_warned = False 17 | 18 | def warn_old_comment(self, filename=None, line_nb=None, line=None): 19 | """ 20 | Warns the user on stderr that one of their files contains semicolons 21 | comments (which are a deprecated way of making comments). 22 | Rather use '//' comments instead of ';' comments. 23 | """ 24 | if not self._old_comment_warned: 25 | self._old_comment_warned = True 26 | message = \ 27 | "Comments starting with a semi-colon '" + \ 28 | OLD_COMMENT_SYM + "' are now deprecated. " + \ 29 | "Please use the new double slash '" + COMMENT_SYM + \ 30 | "' syntax instead." 31 | if filename is not None: 32 | message += \ 33 | "\nThis syntax was found in file '" + \ 34 | cast_to_unicode(filename) + "'" 35 | if line_nb is not None and line is not None: 36 | message += \ 37 | " at line " + str(line_nb) + ": '" + \ 38 | str(line).strip() + "'" 39 | message += '.' 40 | elif line_nb is not None and line is not None: 41 | message += \ 42 | "\nThis syntax was found at line " + str(line_nb) + \ 43 | ": '" + str(line).strip() + "'." 44 | warn(message, DeprecationWarning) 45 | print_warn(message) 46 | 47 | def warn_old_choice(self, filename=None, line_nb=None, line=None): 48 | """ 49 | Warns the user on stderr that one of their files contains semicolons 50 | comments (which are a deprecated way of making comments). 51 | Rather use '//' comments instead of ';' comments. 52 | """ 53 | if not self._old_choice_warned: 54 | self._old_choice_warned = True 55 | message = \ 56 | "Choices starting with '" + OLD_CHOICE_START + \ 57 | "' and ending with '" + OLD_CHOICE_END + \ 58 | "' are now deprecated. Please use the new syntax that " + \ 59 | "starts with '" + CHOICE_START + "' and ends with '" + \ 60 | CHOICE_END + "' instead." 61 | if filename is not None: 62 | message += \ 63 | "\nThis syntax was found in file '" + \ 64 | cast_to_unicode(filename) + "'" 65 | if line_nb is not None and line is not None: 66 | message += \ 67 | " at line " + str(line_nb) + ": '" + \ 68 | str(line).strip() + "'" 69 | message += '.' 70 | elif line_nb is not None and line is not None: 71 | message += \ 72 | "\nThis syntax was found at line " + str(line_nb) + \ 73 | ": '" + str(line).strip() + "'." 74 | warn(message, DeprecationWarning) 75 | print_warn(message) 76 | -------------------------------------------------------------------------------- /chatette/generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | from chatette.configuration import Configuration 5 | from chatette.utils import UnitType 6 | from chatette.log import print_DBG 7 | from chatette.units.ast import AST 8 | 9 | 10 | class Generator(object): 11 | """ 12 | Using the info parsed from the input file, this class will generate 13 | a Rasa NLU dataset and dump it in a JSON file. 14 | If there were inconsistencies in the input file, they are likely to be 15 | detected here. 16 | """ 17 | def __init__(self): 18 | self.ast = AST.get_or_create() 19 | 20 | total_nb_units = len(self.ast[UnitType.intent]) + len(self.ast[UnitType.slot]) + len(self.ast[UnitType.alias]) 21 | if total_nb_units >= 50: 22 | Configuration.get_or_create().set_caching_level(0) 23 | 24 | def generate_train(self): 25 | print_DBG("Generating training examples...") 26 | intent_definitions = self.ast[UnitType.intent] 27 | for intent_name in intent_definitions: 28 | intent = intent_definitions[intent_name] 29 | examples = intent.generate_train() 30 | for example in examples: 31 | yield example 32 | 33 | def generate_test(self, training_examples=None): 34 | should_generate_test_set = False 35 | 36 | intent_definitions = self.ast[UnitType.intent] 37 | for intent_name in intent_definitions: 38 | if ( 39 | intent_definitions[intent_name].get_nb_testing_examples_asked \ 40 | is not None 41 | ): 42 | should_generate_test_set = True 43 | break 44 | 45 | if should_generate_test_set: 46 | print_DBG("Generating testing examples...") 47 | for intent_name in intent_definitions: 48 | intent = intent_definitions[intent_name] 49 | examples = intent.generate_test(training_examples) 50 | for example in examples: 51 | yield example 52 | 53 | 54 | if __name__ == "__main__": 55 | # pylint: disable=wrong-import-position 56 | # pylint: disable=wrong-import-order 57 | import warnings 58 | 59 | warnings.warn("You are running the wrong file ('generator.py')." + 60 | "The file that should be run is '__main__.py'.") 61 | -------------------------------------------------------------------------------- /chatette/log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | """ 4 | Module `chatette.log` 5 | Contains logging functions used throughout the project. 6 | """ 7 | 8 | import sys 9 | 10 | 11 | # pylint: disable=invalid-name 12 | def print_DBG(txt): 13 | """Prints debug information on stdout.""" 14 | print("[DBG] " + txt) 15 | 16 | 17 | def print_warn(txt): 18 | """Warns the user using stdout.""" 19 | print("\n[WARN] " + txt + "\n", file=sys.stderr) 20 | -------------------------------------------------------------------------------- /chatette/modifiers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.modifiers` 3 | Contains everything that is related to modifiers 4 | (at the moment only their representation and not 5 | generation behavior). 6 | """ 7 | -------------------------------------------------------------------------------- /chatette/modifiers/argument.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.modifiers.argument` 4 | Contaisn the different functions that allow to apply 5 | the argument generation modifier to one or several examples. 6 | """ 7 | 8 | from chatette.parsing.utils import ARG_SYM 9 | 10 | 11 | def modify_nb_possibilities(unmodified_nb_possibilities): 12 | """ 13 | Returns the number of possibilities of generation for an item that has 14 | an argument modifier, given the number of possibilities for 15 | the same item without this modifier. 16 | """ 17 | return unmodified_nb_possibilities 18 | 19 | 20 | def modify_example(example, arg_mapping): 21 | """ 22 | Modifies the generated example `example` by applying 23 | the argument modifier with the mapping `arg_mapping` between 24 | the argument names and values. 25 | Returns the modified example. 26 | """ 27 | for arg_name in arg_mapping: 28 | to_replace = ARG_SYM + arg_name 29 | example.text = example.text.replace(to_replace, arg_mapping[arg_name]) 30 | return example 31 | 32 | 33 | def make_all_possibilities(examples, arg_mapping): 34 | """ 35 | Given the list of examples `examples`, constructs and returns a list 36 | of all possible examples after the argument modifier applied 37 | using the mapping `arg_mapping` between the argument names and values. 38 | """ 39 | for ex in examples: 40 | modify_example(ex, arg_mapping) 41 | return examples 42 | -------------------------------------------------------------------------------- /chatette/modifiers/casegen.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.modifiers.casegen` 4 | Contains the different functions that allow to apply 5 | the case generation modifier to one or several examples. 6 | """ 7 | 8 | from random import random 9 | from copy import deepcopy 10 | 11 | 12 | def modify_nb_possibilities(unmodified_nb_possibilities): 13 | """ 14 | Returns the number of possibilities of generation for an item that has 15 | a case generation modifier, given the number of possibilities for 16 | the same item without this modifier. 17 | """ 18 | return 2 * unmodified_nb_possibilities 19 | 20 | 21 | def modify_example(example): 22 | """ 23 | Modifies the generated example `example` by applying 24 | the case generation modifier. 25 | Returns the modified example. 26 | """ 27 | if random() < 0.5: 28 | return with_leading_upper(example) 29 | return with_leading_lower(example) 30 | 31 | 32 | def make_all_possibilities(examples): 33 | """ 34 | Given the list of examples `examples`, constructs and returns a list 35 | of all possible examples after the case generation modifier applied. 36 | """ 37 | result = [] 38 | for ex in examples: 39 | lowercase_ex = with_leading_lower(ex) 40 | result.append(lowercase_ex) 41 | uppercase_ex = with_leading_upper(deepcopy(ex)) 42 | if not uppercase_ex.is_dup(lowercase_ex): 43 | result.append(uppercase_ex) 44 | return result 45 | 46 | ############# Utility functions ############## 47 | def may_change_leading_case(text): 48 | """ 49 | Checks whether the string `text` can 50 | change the letter case of its leading letter. 51 | """ 52 | for c in text: 53 | if c.isalpha(): 54 | return True 55 | if c.isspace(): 56 | continue 57 | return False 58 | return False 59 | 60 | 61 | def with_leading_upper(example): 62 | """ 63 | Changes the leading letter of the text of example `example` to uppercase. 64 | """ 65 | text = example.text 66 | for (i, c) in enumerate(text): 67 | if not c.isspace(): 68 | text = text[:i] + text[i].upper() + text[(i + 1):] 69 | break 70 | example.text = text 71 | return example 72 | 73 | def with_leading_lower(example): 74 | """ 75 | Changes the leading letter of the text of example `example` to uppercase. 76 | """ 77 | text = example.text 78 | for (i, c) in enumerate(text): 79 | if not c.isspace(): 80 | text = text[:i] + text[i].lower() + text[(i + 1):] 81 | break 82 | example.text = text 83 | return example 84 | -------------------------------------------------------------------------------- /chatette/modifiers/representation.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.modifiers.representation` 4 | Contains structures that represent the possible modifiers 5 | that could apply to unit declarations or sub-rules. 6 | """ 7 | 8 | 9 | class ModifiersRepresentation(object): 10 | def __init__(self): 11 | self.casegen = False 12 | 13 | self.variation_name = None # Only for unit references 14 | 15 | self.randgen = RandgenRepresentation() 16 | 17 | self.argument_name = None 18 | self.argument_value = None # Should be an OrderedDict {name -> value} sorted in decreasing length of keys (but should be just the arg value as a str at first for single argument) 19 | 20 | def __repr__(self): 21 | return \ 22 | self.__class__.__name__ + "(casegen: " + str(self.casegen) + \ 23 | " randgen: " + str(self.randgen) + \ 24 | " arg name: " + str(self.argument_name) + " arg value: " + \ 25 | str(self.argument_value) + ")" 26 | def __str__(self): 27 | return self.__repr__() 28 | 29 | def short_description(self): 30 | """ 31 | Returns a short description (as a `str`) that can be displayed to the 32 | user. 33 | """ 34 | at_least_one_modifier = False 35 | desc = "" 36 | if self.casegen: 37 | desc += "- case generation\n" 38 | at_least_one_modifier = True 39 | if self.randgen: 40 | desc += "- random generation" 41 | if self.randgen.name is not None: 42 | desc += ": " + self.randgen.name 43 | if self.randgen.opposite: 44 | desc += " [opposite]" 45 | desc += " (" + str(self.randgen.percentage) + "%)\n" 46 | at_least_one_modifier = True 47 | if self.argument_name is not None: 48 | desc += "- argument name: " + self.argument_name + "\n" 49 | at_least_one_modifier = True 50 | if self.argument_value is not None: 51 | desc += "- argument value: " + self.argument_value + "\n" 52 | at_least_one_modifier = True 53 | 54 | if not at_least_one_modifier: 55 | desc = "No modifiers\n" 56 | else: 57 | desc = "Modifiers:\n" + desc 58 | return desc 59 | 60 | 61 | class RandgenRepresentation(object): 62 | def __init__(self): 63 | self._present = False 64 | self.name = None 65 | self.opposite = False 66 | self.percentage = 50 67 | 68 | def __bool__(self): # For Python 3.x 69 | return self._present 70 | def __nonzero__(self): # For Python 2.7 71 | return self.__bool__() 72 | 73 | def __repr__(self): 74 | if not self._present: 75 | return "No" 76 | result = "Yes" 77 | if self.name is not None: 78 | result += " '" + str(self.name) + "'" 79 | result += " (" 80 | if self.opposite: 81 | result += "opposite, " 82 | result += str(self.percentage) + "%)" 83 | return result 84 | def __str__(self): 85 | return self.__repr__() 86 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/lexer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Module `chatette.parsing` 4 | Contains the lexer used by the parser and the definition of the tokens it uses. 5 | """ 6 | 7 | from chatette.parsing.input_file_manager import InputFileManager 8 | from chatette.parsing.lexing.rule_line import RuleLine 9 | 10 | 11 | class Lexer(object): 12 | """ 13 | This class is intended to transform each string it is provided 14 | into a "lexed" one, that is a list of dicts containing a label 15 | (the type of the terminal) and the token as a str. 16 | """ 17 | def __init__(self): 18 | self._file_manager = InputFileManager.get_or_create() 19 | 20 | 21 | def lex(self, text, parsing_slot_def=False): 22 | """ 23 | Returns a "lexed" version of the str `text`, that is 24 | a list of `LexedItem`s representing each token in `text`. 25 | Those `LexedItem`s contain a `TerminalType` representing 26 | the token's terminal type and a str with the token. 27 | `parsing_slot_def` should be `True` when `text` corresponds to 28 | the contents of a slot definition (its value for the slot declaration 29 | line is not important). 30 | """ 31 | rule = RuleLine(text) 32 | if not rule.matches(parsing_slot_def=parsing_slot_def): 33 | rule.print_error() 34 | else: 35 | tokens = rule.get_lexical_tokens() 36 | for token in tokens: 37 | token.remove_escapement() 38 | return tokens 39 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_arg_assignment.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_arg_assignment` 4 | Contains the class representing the lexing rule meant to tokenize 5 | an argument assignment (inside a unit reference). 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import ARG_SYM, extract_identifier 11 | 12 | 13 | class RuleArgAssignment(LexingRule): 14 | def _apply_strategy(self, **kwargs): 15 | if not self._text.startswith(ARG_SYM, self._next_index): 16 | self.error_msg = \ 17 | "Invalid token. Expected an argument assignment to start " + \ 18 | "here (starting with '" + ARG_SYM + "')." 19 | return False 20 | self._next_index += 1 21 | self._update_furthest_matched_index() 22 | self._tokens.append(LexicalToken(TerminalType.arg_marker, ARG_SYM)) 23 | 24 | arg_value = extract_identifier(self._text, self._next_index) 25 | if arg_value is None: 26 | self.error_msg = \ 27 | "Didn't expect the line to end there. Expected an argument name." 28 | return False 29 | elif len(arg_value) == 0: 30 | self.error_msg = \ 31 | "Couldn't extract the argument name. Arguments must have a name." 32 | return False 33 | self._next_index += len(arg_value) 34 | self._update_furthest_matched_index() 35 | self._tokens.append( 36 | LexicalToken(TerminalType.arg_value, arg_value) 37 | ) 38 | return True 39 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_arg_decl.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_arg_decl` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize the declaration of an argument in a unit declaration. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import ARG_SYM, extract_identifier 11 | 12 | 13 | class RuleArgDecl(LexingRule): 14 | def _apply_strategy(self, **kwargs): 15 | if not self._text.startswith(ARG_SYM, self._next_index): 16 | self.error_msg = \ 17 | "Invalid token. Expected an argument declaration there " + \ 18 | "(starting with '" + ARG_SYM + "')." 19 | return False 20 | self._next_index += 1 21 | self._update_furthest_matched_index() 22 | self._tokens.append( 23 | LexicalToken(TerminalType.arg_marker, ARG_SYM) 24 | ) 25 | 26 | arg_name = extract_identifier(self._text, self._next_index) 27 | if arg_name is None: 28 | self.error_msg = \ 29 | "Didn't expect the line to end there. Expected an argument name." 30 | return False 31 | elif len(arg_name) == 0: 32 | self.error_msg = \ 33 | "Couldn't extract the argument name. Arguments must have a name." 34 | return False 35 | self._next_index += len(arg_name) 36 | self._update_furthest_matched_index() 37 | self._tokens.append(LexicalToken(TerminalType.arg_name, arg_name)) 38 | 39 | return True 40 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_comment.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_comment` 4 | Contains the class representing the lexing rule that applies to comments. 5 | """ 6 | 7 | from chatette.deprecations import Deprecations 8 | from chatette.parsing.input_file_manager import InputFileManager 9 | 10 | from chatette.parsing.lexing.lexing_rule import LexingRule 11 | from chatette.parsing.lexing import LexicalToken, TerminalType 12 | from chatette.parsing.utils import COMMENT_SYM, OLD_COMMENT_SYM 13 | 14 | from chatette.parsing.lexing.rule_whitespaces import RuleWhitespaces 15 | 16 | 17 | class RuleComment(LexingRule): 18 | def _apply_strategy(self, **kwargs): 19 | text = self._text 20 | 21 | whitespaces_rule = RuleWhitespaces(self._text, self._next_index) 22 | if whitespaces_rule.matches(): 23 | self._next_index = whitespaces_rule.get_next_index_to_match() 24 | self._update_furthest_matched_index() 25 | # ignore the tokens it found since this whitespace is not meaningful 26 | if self._next_index >= len(text): 27 | return True 28 | 29 | if ( text.startswith(COMMENT_SYM, self._next_index) 30 | or text.startswith(OLD_COMMENT_SYM, self._next_index) 31 | ): 32 | if text.startswith(OLD_COMMENT_SYM, self._next_index): 33 | Deprecations.get_or_create().warn_old_comment( 34 | *(InputFileManager \ 35 | .get_or_create() \ 36 | .get_current_line_information()) 37 | ) 38 | matched_text = text[self._next_index:] 39 | self._tokens.append(LexicalToken(TerminalType.comment, matched_text)) 40 | self._next_index = len(text) 41 | self._update_furthest_matched_index() 42 | return True 43 | 44 | # No comment found 45 | self.error_msg = \ 46 | "Invalid token. Expected a comment there (starting with '" + \ 47 | COMMENT_SYM + "' or '" + OLD_COMMENT_SYM + "')." 48 | return False 49 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_file_inclusion.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_file_inclusion` 4 | Contains the definition of the class that represents the lexing rule 5 | that has to do with a line that includes a file. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import FILE_INCLUSION_SYM, find_next_comment 11 | 12 | 13 | class RuleFileInclusion(LexingRule): 14 | def _apply_strategy(self, **kwargs): 15 | if not self._text.startswith(FILE_INCLUSION_SYM, self._next_index): 16 | self.error_msg = \ 17 | "Invalid token. Expected a file to be included there " + \ 18 | "(starting with '" + FILE_INCLUSION_SYM + "')." 19 | return False 20 | self._next_index += 1 21 | self._update_furthest_matched_index() 22 | self._tokens.append( 23 | LexicalToken( 24 | TerminalType.file_inclusion_marker, FILE_INCLUSION_SYM 25 | ) 26 | ) 27 | 28 | if self._text[self._next_index].isspace(): 29 | self.error_msg = \ 30 | "Invalid token. Expected a file path here, got a whitespace." 31 | return False 32 | 33 | comment_start = find_next_comment(self._text, self._next_index) 34 | if comment_start is not None: 35 | file_path = self._text[self._next_index:comment_start].rstrip() 36 | else: 37 | file_path = self._text[self._next_index:].rstrip() 38 | 39 | self._next_index += len(file_path) 40 | self._update_furthest_matched_index() 41 | self._tokens.append(LexicalToken(TerminalType.file_path, file_path)) 42 | 43 | return True 44 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_key_value.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_key_value` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a key or a value inside an annotation. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import \ 11 | ANNOTATION_END, ANNOTATION_SEP, KEY_VAL_CONNECTOR, \ 12 | KEY_VAL_ENCLOSERS, \ 13 | find_unescaped 14 | from chatette.utils import min_if_exist 15 | 16 | 17 | class RuleKeyValue(LexingRule): 18 | def _apply_strategy(self, **kwargs): 19 | """ 20 | `kwargs` can contain a value with key `extracting_key`. 21 | `extracting_key` is a boolean that is `True` if this rule should extract 22 | a key and `False` if this rule should extract a value. 23 | If `kwargs` doesn't contain `extracting_key`, defaults to `True`. 24 | """ 25 | extracting_key = kwargs.get("extracting_key", True) 26 | if extracting_key: 27 | terminal_type = TerminalType.key 28 | else: 29 | terminal_type = TerminalType.value 30 | 31 | encloser = None 32 | for current_encloser in KEY_VAL_ENCLOSERS: 33 | if self._text.startswith(current_encloser, self._next_index): 34 | self._next_index += 1 35 | self._update_furthest_matched_index() 36 | encloser = current_encloser 37 | break 38 | 39 | if encloser is not None: 40 | # Enclosed key/value 41 | next_encloser_index = \ 42 | find_unescaped(self._text, encloser, self._next_index) 43 | if next_encloser_index is None: 44 | self.error_msg = \ 45 | "Missing key-value encloser. Expected symbol " + encloser + \ 46 | " instead of end of line." 47 | return False 48 | 49 | extracted_text = self._text[self._start_index+1:next_encloser_index] 50 | self._next_index = next_encloser_index + 1 51 | self._update_furthest_matched_index() 52 | self._tokens.append(LexicalToken(terminal_type, extracted_text)) 53 | return True 54 | else: 55 | # Key/value not enclosed 56 | end_annotation_index = \ 57 | find_unescaped(self._text, ANNOTATION_END, self._next_index) 58 | if extracting_key: 59 | next_connector_index = \ 60 | find_unescaped( 61 | self._text, KEY_VAL_CONNECTOR, self._next_index 62 | ) 63 | end_key_value_index = \ 64 | min_if_exist(next_connector_index, end_annotation_index) 65 | else: # Extracting value 66 | next_key_val_pair_index = \ 67 | find_unescaped( 68 | self._text, ANNOTATION_SEP, self._next_index 69 | ) 70 | end_key_value_index = \ 71 | min_if_exist(next_key_val_pair_index, end_annotation_index) 72 | 73 | if end_key_value_index is None: 74 | self.error_msg = \ 75 | "Couldn't find the end of key/value. " + \ 76 | "Didn't expect the end of the line there." 77 | return False 78 | 79 | extracted_text = \ 80 | self._text[self._start_index:end_key_value_index].rstrip() 81 | self._next_index += len(extracted_text) 82 | self._update_furthest_matched_index() 83 | self._tokens.append(LexicalToken(terminal_type, extracted_text)) 84 | 85 | return True 86 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_line.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_line` 4 | Contains the class representing a lexing rule that applies to a full line. 5 | """ 6 | 7 | from chatette.parsing.lexing.lexing_rule import LexingRule 8 | from chatette.parsing.lexing.rule_comment import RuleComment 9 | from chatette.parsing.lexing.rule_file_inclusion import \ 10 | RuleFileInclusion 11 | from chatette.parsing.lexing.rule_unit_decl_line import \ 12 | RuleUnitDeclLine 13 | from chatette.parsing.lexing.rule_unit_rule import RuleUnitRule 14 | 15 | 16 | class RuleLine(LexingRule): 17 | """Represents the lexing rule for a full line of a template file.""" 18 | _empty_match_allowed = True 19 | def __init__(self, text): 20 | super(RuleLine, self).__init__(text, 0) 21 | 22 | def _apply_strategy(self, **kwargs): 23 | if self._match_one_of( 24 | [RuleComment, RuleFileInclusion, RuleUnitDeclLine, RuleUnitRule], 25 | self._next_index, 26 | **kwargs 27 | ): 28 | if self._next_index < len(self._text): 29 | comment_rule = RuleComment(self._text, self._next_index) 30 | if not comment_rule.matches(): 31 | self.error_msg = "Invalid token. Expected a comment or " + \ 32 | "the end of the line there." 33 | self._update_furthest_matched_index(comment_rule) 34 | return False 35 | self._tokens.extend(comment_rule.get_lexical_tokens()) 36 | self._next_index = comment_rule.get_next_index_to_match() 37 | self._update_furthest_matched_index() 38 | # Comments end the line BY DESIGN 39 | return True 40 | 41 | return True 42 | 43 | return False 44 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_percent_gen.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_percent_gen` 4 | Contains the class representing the lexing rule meant to tokenize 5 | percentage for the random generation modifiers. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | 11 | from chatette.parsing.lexing.rule_whitespaces import RuleWhitespaces 12 | 13 | 14 | class RulePercentGen(LexingRule): 15 | def _apply_strategy(self, **kwargs): 16 | while self._text[self._next_index].isdigit(): 17 | self._next_index += 1 18 | self._update_furthest_matched_index() 19 | percentage = self._text[self._start_index:self._next_index] 20 | 21 | if self._text[self._next_index] != '.': 22 | if len(percentage) == 0: 23 | self.error_msg = \ 24 | "Invalid token. Expected a percentage for the random " + \ 25 | "generation modifier." 26 | return False 27 | else: 28 | percentage += '.' 29 | self._next_index += 1 30 | self._update_furthest_matched_index() 31 | 32 | start_index_non_int_part = self._next_index 33 | while self._text[self._next_index].isdigit(): 34 | self._next_index += 1 35 | self._update_furthest_matched_index() 36 | if self._next_index == start_index_non_int_part: 37 | self.error_msg = \ 38 | "Invalid token. Cannot have a percentage with an empty " + \ 39 | "non-integral part." 40 | return False 41 | percentage += self._text[start_index_non_int_part:self._next_index] 42 | 43 | if not self._try_to_match_rule(RuleWhitespaces): 44 | self.error_msg = None 45 | # Ignore tokens as this whitespace is not meaningful 46 | if self._text[self._next_index] == '%': 47 | self._next_index += 1 48 | self._update_furthest_matched_index() 49 | 50 | self._tokens.append(LexicalToken(TerminalType.percentgen, percentage)) 51 | 52 | return True 53 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_rand_gen.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_rand_gen` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a randgen modifier (inside a unit declaration, reference or 6 | choice). 7 | """ 8 | 9 | from chatette.parsing.lexing.lexing_rule import LexingRule 10 | from chatette.parsing.lexing import LexicalToken, TerminalType 11 | from chatette.parsing.utils import \ 12 | extract_identifier, \ 13 | RAND_GEN_SYM, RAND_GEN_PERCENT_SYM, RAND_GEN_OPPOSITE_SYM 14 | 15 | from chatette.parsing.lexing.rule_percent_gen import RulePercentGen 16 | 17 | 18 | class RuleRandGen(LexingRule): 19 | def _apply_strategy(self, **kwargs): 20 | if not self._text.startswith(RAND_GEN_SYM, self._next_index): 21 | self.error_msg = \ 22 | "Invalid token. Expected a random generation modifier to " + \ 23 | "begin there (starting with '" + RAND_GEN_SYM + "')." 24 | return False 25 | self._next_index += 1 26 | self._update_furthest_matched_index() 27 | self._tokens.append( 28 | LexicalToken(TerminalType.randgen_marker, RAND_GEN_SYM) 29 | ) 30 | 31 | if self._text.startswith(RAND_GEN_OPPOSITE_SYM, self._next_index): 32 | self._next_index += 1 33 | self._update_furthest_matched_index() 34 | self._tokens.append( 35 | LexicalToken( 36 | TerminalType.opposite_randgen_marker, RAND_GEN_OPPOSITE_SYM 37 | ) 38 | ) 39 | 40 | # TODO not sure `extract_identifier` is the best thing to use here 41 | randgen_name = extract_identifier(self._text, self._next_index) 42 | if randgen_name is None: 43 | self.error_msg = \ 44 | "Didn't expect the line to end there. Expected a name for " + \ 45 | "the random generation modifier, a percentage for it or " + \ 46 | "the end of the unit or choice." 47 | return False 48 | if len(randgen_name) > 0: 49 | self._next_index += len(randgen_name) 50 | self._update_furthest_matched_index() 51 | self._tokens.append( 52 | LexicalToken(TerminalType.randgen_name, randgen_name) 53 | ) 54 | 55 | if self._text.startswith(RAND_GEN_PERCENT_SYM, self._next_index): 56 | self._next_index += 1 57 | self._update_furthest_matched_index() 58 | self._tokens.append( 59 | LexicalToken( 60 | TerminalType.percentgen_marker, RAND_GEN_PERCENT_SYM 61 | ) 62 | ) 63 | 64 | if not self._try_to_match_rule(RulePercentGen): 65 | self.error_msg += \ 66 | " Percentage for the random generation is required after " + \ 67 | "its marker character ('" + RAND_GEN_PERCENT_SYM + "')." 68 | return False 69 | 70 | return True 71 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_slot_val.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_slot_val` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a slot value being set within a unit rule (only for a slot). 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import find_next_comment, SLOT_VAL_SYM 11 | 12 | 13 | class RuleSlotVal(LexingRule): 14 | def _apply_strategy(self, **kwargs): 15 | """ 16 | `kwargs` can contain a boolean with key `parsing_slot_def` that is 17 | `True` if the current text is part of a slot definition. 18 | If this boolean is not in `kwargs`, defaults to `False`. 19 | """ 20 | parsing_slot_def = kwargs.get("parsing_slot_def", False) 21 | if parsing_slot_def: 22 | while self._text[self._next_index].isspace(): 23 | self._next_index += 1 24 | self._update_furthest_matched_index() 25 | 26 | if self._text.startswith(SLOT_VAL_SYM, self._next_index): 27 | self._tokens.append( 28 | LexicalToken(TerminalType.slot_val_marker, SLOT_VAL_SYM) 29 | ) 30 | self._next_index += 1 31 | self._update_furthest_matched_index() 32 | 33 | while self._text[self._next_index].isspace(): 34 | self._next_index += 1 35 | self._update_furthest_matched_index() 36 | 37 | comment_sym = find_next_comment(self._text, self._next_index) 38 | if comment_sym is not None: 39 | slot_value = \ 40 | self._text[self._next_index:comment_sym].rstrip() 41 | else: 42 | slot_value = self._text[self._next_index:].rstrip() 43 | 44 | self._tokens.append( 45 | LexicalToken(TerminalType.slot_val, slot_value) 46 | ) 47 | self._next_index += len(slot_value) 48 | self._update_furthest_matched_index() 49 | 50 | return True 51 | 52 | return False 53 | else: 54 | raise ValueError( 55 | "Tried to extract a slot value within a rule that is not " + \ 56 | "part of a slot definition." 57 | ) 58 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_unit_decl.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_unit_decl` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a declaration of a unit (from '[' to ']'). 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import \ 11 | CASE_GEN_SYM, UNIT_END_SYM, extract_identifier 12 | 13 | from chatette.parsing.lexing.rule_unit_start import RuleUnitStart 14 | from chatette.parsing.lexing.rule_arg_decl import RuleArgDecl 15 | from chatette.parsing.lexing.rule_variation import RuleVariation 16 | 17 | 18 | class RuleUnitDecl(LexingRule): 19 | def _apply_strategy(self, **kwargs): 20 | if not self._try_to_match_rule(RuleUnitStart): 21 | return False 22 | 23 | if self._text.startswith(CASE_GEN_SYM, self._next_index): 24 | self._next_index += 1 25 | self._update_furthest_matched_index() 26 | self._tokens.append( 27 | LexicalToken(TerminalType.casegen_marker, CASE_GEN_SYM) 28 | ) 29 | 30 | identifier = extract_identifier(self._text, self._next_index) 31 | if identifier is not None: 32 | self._next_index += len(identifier) 33 | self._update_furthest_matched_index() 34 | self._tokens.append( 35 | LexicalToken(TerminalType.unit_identifier, identifier) 36 | ) 37 | 38 | if not self._match_any_order([None, RuleArgDecl, RuleVariation]): 39 | return False 40 | 41 | if not self._text.startswith(UNIT_END_SYM, self._next_index): 42 | self.error_msg = \ 43 | "Invalid token. Expected the end of the unit declaration " + \ 44 | "there (using symbol '" + UNIT_END_SYM + "')." 45 | return False 46 | 47 | # TODO maybe making a function for this would be useful 48 | if self._tokens[0].type == TerminalType.alias_decl_start: 49 | unit_end_type = TerminalType.alias_decl_end 50 | elif self._tokens[0].type == TerminalType.slot_decl_start: 51 | unit_end_type = TerminalType.slot_decl_end 52 | elif self._tokens[0].type == TerminalType.intent_decl_start: 53 | unit_end_type = TerminalType.intent_decl_end 54 | else: # Should never happen 55 | raise ValueError( 56 | "An unexpected error happened during parsing: tried to " + \ 57 | "parse the end of a unit but couldn't find its start in " + \ 58 | "the previously parsed data.\nData was: " + str(self._tokens) 59 | ) 60 | 61 | self._next_index += 1 62 | self._update_furthest_matched_index() 63 | self._tokens.append(LexicalToken(unit_end_type, UNIT_END_SYM)) 64 | 65 | return True 66 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_unit_decl_line.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_unit_decl_line` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a line that declares a unit. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | 10 | from chatette.parsing.lexing.rule_unit_decl import RuleUnitDecl 11 | from chatette.parsing.lexing.rule_annotation import RuleAnnotation 12 | 13 | 14 | class RuleUnitDeclLine(LexingRule): 15 | def _apply_strategy(self, **kwargs): 16 | if not self._try_to_match_rule(RuleUnitDecl): 17 | return False 18 | 19 | annotation_rule = RuleAnnotation(self._text, self._next_index) 20 | if annotation_rule.matches(): 21 | self._next_index = annotation_rule.get_next_index_to_match() 22 | self._update_furthest_matched_index() 23 | self._tokens.extend(annotation_rule.get_lexical_tokens()) 24 | return True 25 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_unit_ref.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_unit_ref` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a unit reference. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import \ 11 | extract_identifier, \ 12 | CASE_GEN_SYM, UNIT_END_SYM 13 | 14 | from chatette.parsing.lexing.rule_unit_start import RuleUnitStart 15 | from chatette.parsing.lexing.rule_variation import RuleVariation 16 | from chatette.parsing.lexing.rule_rand_gen import RuleRandGen 17 | from chatette.parsing.lexing.rule_arg_assignment import \ 18 | RuleArgAssignment 19 | 20 | 21 | class RuleUnitRef(LexingRule): 22 | def _apply_strategy(self, **kwargs): 23 | unit_start_rule = RuleUnitStart(self._text, self._next_index) 24 | if not unit_start_rule.matches(extracting_decl=False): 25 | self.error_msg = unit_start_rule.error_msg 26 | self._update_furthest_matched_index(unit_start_rule) 27 | return False 28 | self._next_index = unit_start_rule.get_next_index_to_match() 29 | self._update_furthest_matched_index(unit_start_rule) 30 | self._tokens.extend(unit_start_rule.get_lexical_tokens()) 31 | 32 | if self._text.startswith(CASE_GEN_SYM, self._next_index): 33 | self._tokens.append( 34 | LexicalToken(TerminalType.casegen_marker, CASE_GEN_SYM) 35 | ) 36 | self._next_index += 1 37 | self._update_furthest_matched_index() 38 | 39 | identifier = extract_identifier(self._text, self._next_index) 40 | if identifier is not None: 41 | self._tokens.append( 42 | LexicalToken(TerminalType.unit_identifier, identifier) 43 | ) 44 | self._next_index += len(identifier) 45 | self._update_furthest_matched_index() 46 | 47 | if not self._match_any_order( 48 | [None, RuleVariation, RuleRandGen, RuleArgAssignment] 49 | ): 50 | return False 51 | 52 | if not self._text.startswith(UNIT_END_SYM, self._next_index): 53 | self.error_msg = \ 54 | "Invalid token. Expected the unit reference to end here (" + \ 55 | "using character '" + UNIT_END_SYM + "')." 56 | return False 57 | 58 | # TODO maybe making a function for this would be useful 59 | if self._tokens[0].type == TerminalType.alias_ref_start: 60 | unit_end_type = TerminalType.alias_ref_end 61 | elif self._tokens[0].type == TerminalType.slot_ref_start: 62 | unit_end_type = TerminalType.slot_ref_end 63 | elif self._tokens[0].type == TerminalType.intent_ref_start: 64 | unit_end_type = TerminalType.intent_ref_end 65 | else: # Should never happen 66 | raise ValueError( 67 | "An unexpected error happened during parsing: tried to " + \ 68 | "parse the end of a unit but couldn't find its start in " + \ 69 | "the previously parsed data.\nData was: " + str(self._tokens) 70 | ) 71 | 72 | self._next_index += 1 73 | self._update_furthest_matched_index() 74 | self._tokens.append(LexicalToken(unit_end_type, UNIT_END_SYM)) 75 | 76 | return True 77 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_unit_rule.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_unit_rule` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a rule that is part of a unit definition. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | 10 | from chatette.parsing.lexing.rule_whitespaces import RuleWhitespaces 11 | from chatette.parsing.lexing.rule_content_rule_and_choice import \ 12 | RuleContentRule 13 | from chatette.parsing.lexing.rule_comment import RuleComment 14 | from chatette.parsing.lexing.rule_slot_val import RuleSlotVal 15 | 16 | 17 | class RuleUnitRule(LexingRule): 18 | def _apply_strategy(self, **kwargs): 19 | """ 20 | `kwargs` can contain a boolean with key `parsing_slot_def` that is 21 | `True` if the current text is part of a slot definition. 22 | If this boolean is not in `kwargs`, defaults to `False`. 23 | """ 24 | parsing_slot_def = kwargs.get("parsing_slot_def", False) 25 | 26 | if not self._try_to_match_rule( 27 | RuleWhitespaces, parsing_indentation=True 28 | ): 29 | self.error_msg = \ 30 | "Invalid token. Expected indentation within unit definitions." 31 | return False 32 | 33 | while True: 34 | if self._next_index == len(self._text): 35 | return True 36 | content_rule = RuleContentRule(self._text, self._next_index) 37 | if content_rule.matches(**kwargs): 38 | self._tokens.extend(content_rule.get_lexical_tokens()) 39 | self._next_index = content_rule.get_next_index_to_match() 40 | self._update_furthest_matched_index(content_rule) 41 | else: 42 | self._update_furthest_matched_index(content_rule) 43 | self.error_msg = content_rule.error_msg 44 | break 45 | 46 | if parsing_slot_def: 47 | old_error_msg = self.error_msg 48 | if not self._try_to_match_rule(RuleSlotVal, **kwargs): 49 | self.error_msg = old_error_msg 50 | 51 | if self._next_index < len(self._text): 52 | old_error_msg = self.error_msg 53 | if ( 54 | not self._try_to_match_rule(RuleComment) and old_error_msg is not None 55 | ): 56 | self.error_msg = old_error_msg 57 | 58 | if self._next_index < len(self._text): 59 | return False 60 | return True 61 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_unit_start.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_unit_start` 4 | Contains the class that represents a lexing rule to tokenize 5 | the start of a unit definition or reference. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import \ 11 | ALIAS_SYM, SLOT_SYM, INTENT_SYM, UNIT_START_SYM 12 | 13 | 14 | class RuleUnitStart(LexingRule): 15 | def _apply_strategy(self, **kwargs): 16 | """ 17 | `kwargs` can contain a value with key `extracting_decl`. 18 | This is a boolean that should be `True` iff the rule should consider 19 | it is parsing a unit declaration and `False` if it is parsing 20 | a unit reference. 21 | If `kwargs` doesn't contain `extracting_decl`, defaults to `True`. 22 | """ 23 | extracting_decl = kwargs.get("extracting_decl", True) 24 | if self._text.startswith(ALIAS_SYM, self._next_index): 25 | if extracting_decl: 26 | terminal_type = TerminalType.alias_decl_start 27 | else: 28 | terminal_type = TerminalType.alias_ref_start 29 | text_start = ALIAS_SYM 30 | elif self._text.startswith(SLOT_SYM, self._next_index): 31 | if extracting_decl: 32 | terminal_type = TerminalType.slot_decl_start 33 | else: 34 | terminal_type = TerminalType.slot_ref_start 35 | text_start = SLOT_SYM 36 | elif self._text.startswith(INTENT_SYM, self._next_index): 37 | if extracting_decl: 38 | terminal_type = TerminalType.intent_decl_start 39 | else: 40 | terminal_type = TerminalType.intent_ref_start 41 | text_start = INTENT_SYM 42 | else: 43 | self.error_msg = \ 44 | "Invalid token. Expected a unit start here (starting with " + \ 45 | "either '" + ALIAS_SYM + "', '" + SLOT_SYM + "' or '" + \ 46 | INTENT_SYM + "'." 47 | return False 48 | self._next_index += 1 49 | self._update_furthest_matched_index() 50 | 51 | if self._text.startswith(UNIT_START_SYM, self._next_index): 52 | self._tokens.append( 53 | LexicalToken(terminal_type, text_start + UNIT_START_SYM) 54 | ) 55 | self._next_index += 1 56 | self._update_furthest_matched_index() 57 | return True 58 | 59 | self.error_msg = \ 60 | "Invalid token. Expected a start of unit here (starting with '" + \ 61 | UNIT_START_SYM + "'). Did you mean to escape the previous '" + \ 62 | text_start + '?' 63 | return False 64 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_variation.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_variation` 4 | Contains the definition of the class that represents the lexing rule 5 | to tokenize a variation (in a unit declaration or reference). 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | from chatette.parsing.utils import VARIATION_SYM, extract_identifier 11 | 12 | 13 | class RuleVariation(LexingRule): 14 | def _apply_strategy(self, **kwargs): 15 | if not self._text.startswith(VARIATION_SYM, self._next_index): 16 | self.error_msg = \ 17 | "Invalid token. Expected a variation there (starting with '" + \ 18 | VARIATION_SYM + "')." 19 | return False 20 | self._next_index += 1 21 | self._update_furthest_matched_index() 22 | self._tokens.append( 23 | LexicalToken(TerminalType.variation_marker, VARIATION_SYM) 24 | ) 25 | 26 | variation_name = extract_identifier(self._text, self._next_index) 27 | if variation_name is None: 28 | self.error_msg = \ 29 | "Didn't expect an end of line there. Expected a variation name." 30 | return False 31 | elif len(variation_name) == 0: 32 | self.error_msg = \ 33 | "Couldn't extract the name of the variation. Variation names " + \ 34 | "must be at least one character long." 35 | return False 36 | 37 | self._next_index += len(variation_name) 38 | self._update_furthest_matched_index() 39 | self._tokens.append( 40 | LexicalToken(TerminalType.variation_name, variation_name) 41 | ) 42 | 43 | return True 44 | -------------------------------------------------------------------------------- /chatette/parsing/lexing/rule_whitespaces.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.parsing.lexing.rule_whitespaces` 4 | Contains the class representing the lexing rule that applies to whitespaces 5 | and indentation. 6 | """ 7 | 8 | from chatette.parsing.lexing.lexing_rule import LexingRule 9 | from chatette.parsing.lexing import LexicalToken, TerminalType 10 | 11 | 12 | class RuleWhitespaces(LexingRule): 13 | def _apply_strategy(self, **kwargs): 14 | """ 15 | `kwargs` can contain a boolean at key `parsing_indentation` that is 16 | `True` iff the whitespaces currently parsed correspond to 17 | an indentation, and `False` if it corresponds to simple whitespaces. 18 | If `kwargs` doesn't contain this boolean, defaults to `False`. 19 | """ 20 | parsing_indentation = kwargs.get("parsing_indentation", False) 21 | if parsing_indentation: 22 | terminal_type = TerminalType.indentation 23 | else: 24 | terminal_type = TerminalType.whitespace 25 | text = self._text 26 | 27 | while self._next_index < len(text) and text[self._next_index].isspace(): 28 | self._next_index += 1 29 | self._update_furthest_matched_index() 30 | if self._next_index > self._start_index: 31 | matched_text = text[self._start_index:self._next_index] 32 | self._tokens.append(LexicalToken(terminal_type, matched_text)) 33 | return True 34 | 35 | self.error_msg = "Invalid token. Expected at least one whitespace there." 36 | return False 37 | -------------------------------------------------------------------------------- /chatette/parsing/line_count_file_wrapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module `chatette.parsing.line_count_file_wrapper`. 3 | Contains a wrapper of `io.File` that counts on which line it is currently. 4 | """ 5 | 6 | import io 7 | 8 | from chatette.utils import cast_to_unicode 9 | 10 | 11 | class LineCountFileWrapper(object): 12 | """ 13 | A wrapper of `io.File` that keeps track of the line number it is reading. 14 | """ 15 | 16 | def __init__(self, filepath, mode='r'): 17 | self.name = cast_to_unicode(filepath) 18 | self.f = io.open(filepath, mode) 19 | self.line_nb = 0 20 | 21 | def close(self): 22 | return self.f.close() 23 | def closed(self): 24 | return self.f.closed 25 | 26 | def readline(self): 27 | self.line_nb += 1 28 | return self.f.readline() 29 | 30 | # to allow using in 'with' statements 31 | def __enter__(self): 32 | return self 33 | 34 | def __exit__(self, exc_type, exc_val, exc_tb): 35 | self.f.close() 36 | self.close() 37 | 38 | -------------------------------------------------------------------------------- /chatette/prechecks/__init__.py: -------------------------------------------------------------------------------- 1 | from .preconditions import ensure_preconditions 2 | from .deprecations import check_for_deprecations 3 | -------------------------------------------------------------------------------- /chatette/prechecks/deprecations.py: -------------------------------------------------------------------------------- 1 | from .pyversion import _is_deprecated_python_version, _get_python_version_as_str 2 | from chatette.log import print_warn 3 | 4 | 5 | def check_for_deprecations(): 6 | _check_python_version() 7 | 8 | def _check_python_version(): 9 | if _is_deprecated_python_version(): 10 | print_warn( 11 | "Python v" + _get_python_version_as_str() + \ 12 | " will not be supported in the future. " + \ 13 | "Please upgrade Python whenever possible." 14 | ) 15 | -------------------------------------------------------------------------------- /chatette/prechecks/preconditions.py: -------------------------------------------------------------------------------- 1 | from .pyversion import _is_supported_python_version, _get_python_version_as_str 2 | 3 | 4 | class PreconditionsUnmet(Exception): 5 | pass 6 | 7 | 8 | def ensure_preconditions(): 9 | _ensure_python_version_is_supported() 10 | 11 | def _ensure_python_version_is_supported(): 12 | if not _is_supported_python_version(): 13 | print( 14 | "[ERROR] Python v" + _get_python_version_as_str() + \ 15 | " is not supported by Chatette. " + \ 16 | "Please use a version of Python older than v2.7.\n" 17 | ) 18 | raise PreconditionsUnmet() 19 | -------------------------------------------------------------------------------- /chatette/prechecks/pyversion.py: -------------------------------------------------------------------------------- 1 | from sys import version_info 2 | 3 | 4 | def _get_python_version_as_str(): 5 | return str(version_info[0]) + '.' + str(version_info[1]) 6 | 7 | 8 | def _is_supported_python_version(): 9 | return version_info[0] == 3 \ 10 | or version_info[0] == 2 and version_info[1] == 7 11 | 12 | def _is_deprecated_python_version(): 13 | return version_info[0] == 2 \ 14 | or version_info[0] == 3 and version_info[1] < 4 15 | -------------------------------------------------------------------------------- /chatette/units/modifiable/choice.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.units.modifiable.choice` 4 | Contains the class that represents choices (and old word groups). 5 | """ 6 | 7 | from random import choice 8 | 9 | from chatette.units.modifiable import ModifiableItem 10 | from chatette.units import extend_no_dup 11 | 12 | from chatette.parsing.utils import CHOICE_START, CHOICE_END, CHOICE_SEP 13 | from chatette.parsing import utils as putils 14 | 15 | 16 | class Choice(ModifiableItem): 17 | """ 18 | Represents a choice that can choose between rules and generate one of them. 19 | """ 20 | def __init__(self, leading_space, modifiers, rules=None): 21 | super(Choice, self).__init__("", leading_space, modifiers) 22 | self._rules = [] 23 | if rules is not None: 24 | self._rules.extend(rules) 25 | 26 | def _compute_full_name(self): 27 | return "choice" 28 | 29 | def _compute_nb_possibilities(self): 30 | acc = 0 31 | for rule in self._rules: 32 | acc += rule.get_max_nb_possibilities() 33 | return acc 34 | 35 | def add_rule(self, rule): 36 | """Adds the rule `rule` to the list of rules.""" 37 | self._rules.append(rule) 38 | def add_rules(self, rules): 39 | """Adds each of the rules `rule` to the list of rules.""" 40 | self._rules.extend(rules) 41 | 42 | def remove_rule(self, index): 43 | """Removes the rule at `index`th rule.""" 44 | if index < 0 or index >= len(self._rules): 45 | raise ValueError("Tried to remove rule at invalid index.") 46 | del self._rules[index] 47 | 48 | def _choose_rule(self): 49 | """ 50 | Returns a rule at random from the list of rules for this definition. 51 | Returns `None` if there are no rules. 52 | """ 53 | if len(self._rules) == 0: 54 | return None 55 | return choice(self._rules) 56 | 57 | def _generate_random_strategy(self): 58 | rule = self._choose_rule() 59 | if rule is None: 60 | raise SyntaxError( 61 | self.full_name.capitalize() + " does not have any rule to " + \ 62 | "generate." 63 | ) 64 | 65 | return rule.generate_random() 66 | 67 | def _generate_all_strategy(self): 68 | generated_examples = [] 69 | for rule in self._rules: 70 | current_examples = rule.generate_all() 71 | generated_examples = \ 72 | extend_no_dup(generated_examples, current_examples) 73 | return generated_examples 74 | 75 | def as_template_str(self): 76 | result = CHOICE_START 77 | result += putils.get_template_pre_modifiers(self._modifiers_repr) 78 | for i in range(len(self._rules)): 79 | if i != 0: 80 | result += CHOICE_SEP 81 | result += self._rules[i].as_template_str() 82 | i += 1 83 | result += putils.get_template_post_modifiers(self._modifiers_repr) 84 | result += CHOICE_END 85 | if self._leading_space: 86 | result = ' ' + result 87 | return result 88 | -------------------------------------------------------------------------------- /chatette/units/modifiable/definitions/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Module `chatette.units.definitions` 6 | Contains the classes that represent definitions of units. 7 | """ 8 | -------------------------------------------------------------------------------- /chatette/units/modifiable/definitions/alias.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Module `chatette.units.modifiable.definitions.alias` 5 | Contains the class representing an alias definition. 6 | """ 7 | 8 | from chatette.utils import UnitType 9 | from chatette.units.modifiable.definitions.unit_definition import \ 10 | UnitDefinition 11 | 12 | 13 | class AliasDefinition(UnitDefinition): 14 | """Represents an alias definition.""" 15 | unit_type = UnitType.alias 16 | def _compute_full_name(self): 17 | return "alias '" + self._name + "'" 18 | -------------------------------------------------------------------------------- /chatette/units/modifiable/definitions/slot.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Module `chatette.units.modifiable.definitions.slot` 4 | Contains the class representing a slot definition. 5 | """ 6 | 7 | from chatette.utils import UnitType, append_to_list_in_dict, extend_list_in_dict 8 | from chatette.units import Entity 9 | from chatette.units.modifiable.definitions.unit_definition import \ 10 | UnitDefinition 11 | 12 | 13 | class SlotDefinition(UnitDefinition): 14 | """Represents an slot definition.""" 15 | unit_type = UnitType.slot 16 | def __init__(self, *args, **kwargs): 17 | super(SlotDefinition, self).__init__(*args, **kwargs) 18 | self._synonyms = None 19 | 20 | def _compute_full_name(self): 21 | return "slot '" + self._name + "'" 22 | 23 | 24 | def _check_rule_validity(self, rule): 25 | """Override.""" 26 | pass 27 | 28 | 29 | def _generate_random_strategy(self, variation_name=None): 30 | generated_example = \ 31 | super(SlotDefinition, self)._generate_random_strategy( 32 | variation_name=variation_name 33 | ) 34 | 35 | slot_value = generated_example._slot_value 36 | if slot_value is None: 37 | slot_value = generated_example.text 38 | generated_example.entities.append( 39 | Entity(self._name, len(generated_example.text), slot_value) 40 | ) 41 | generated_example._slot_value = None 42 | 43 | return generated_example 44 | 45 | def _generate_all_strategy(self, variation_name=None): 46 | generated_examples = \ 47 | super(SlotDefinition, self)._generate_all_strategy( 48 | variation_name=variation_name 49 | ) 50 | 51 | for ex in generated_examples: 52 | slot_value = ex._slot_value 53 | if slot_value is None: 54 | slot_value = ex.text 55 | ex.entities.append( 56 | Entity(self._name, len(ex.text), slot_value) 57 | ) 58 | ex._slot_value = None 59 | 60 | return generated_examples 61 | 62 | 63 | def get_synonyms_dict(self): 64 | if self._synonyms is None: 65 | self._synonyms = dict() 66 | for rule in self._all_rules: 67 | texts = [ex.text for ex in rule.generate_all()] 68 | if rule.slot_value is None: 69 | for text in texts: 70 | append_to_list_in_dict(self._synonyms, text, text) 71 | else: 72 | extend_list_in_dict(self._synonyms, rule.slot_value, texts) 73 | return self._synonyms 74 | -------------------------------------------------------------------------------- /chatette/units/rule.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Module `chatette.units.rule` 5 | Contains a class representing rules 6 | (as they will be contained in choices and unit definitions). 7 | """ 8 | 9 | from chatette.units.generating_item import GeneratingItem 10 | from chatette.units import Example, sort_by_texts, add_example_no_dup 11 | from chatette.modifiers.randgen import \ 12 | can_concat_examples, concat_examples_with_randgen 13 | 14 | from chatette.parsing.utils import SLOT_VAL_SYM 15 | 16 | 17 | class Rule(GeneratingItem): 18 | """ 19 | Represents a rule (as it will be contained in choices or unit definitions). 20 | """ 21 | def __init__(self, parent_name=None, contents=None, slot_value=None): 22 | self.parent_name = parent_name 23 | super(Rule, self).__init__(None, leading_space=False) 24 | self._contents = contents 25 | 26 | self._max_nb_cached_ex = 0 27 | 28 | self.slot_value = slot_value 29 | 30 | def _compute_full_name(self): 31 | if self.parent_name is not None: 32 | return "rule contained in " + self.parent_name 33 | return "rule not contained in anything" 34 | 35 | def get_max_cache_size(self): 36 | return 0 37 | 38 | def _compute_nb_possibilities(self): 39 | if len(self._contents) == 0: 40 | return 1 41 | acc = None 42 | for content in self._contents: 43 | if acc is None: 44 | acc = content.get_max_nb_possibilities() 45 | else: 46 | acc *= content.get_max_nb_possibilities() 47 | return acc 48 | 49 | def _generate_random_strategy(self): 50 | generated_example = Example() 51 | randgen_mapping = dict() 52 | for content in self._contents: 53 | generated_example.append( 54 | content.generate_random(randgen_mapping=randgen_mapping) 55 | ) 56 | return generated_example 57 | 58 | def _generate_all_strategy(self): 59 | if len(self._contents) == 0: 60 | return [] 61 | generated_examples = None 62 | for content in self._contents: 63 | tmp_buffer = [] 64 | content_examples = content.generate_all() 65 | if generated_examples is None: 66 | generated_examples = content_examples 67 | else: 68 | for ex in generated_examples: 69 | for content_ex in content_examples: 70 | if can_concat_examples(ex, content_ex): 71 | new_example = \ 72 | concat_examples_with_randgen(ex, content_ex) 73 | add_example_no_dup(tmp_buffer, new_example) 74 | generated_examples = tmp_buffer 75 | if generated_examples is None: 76 | return [] 77 | return sort_by_texts(generated_examples) 78 | 79 | 80 | def __str__(self): 81 | result = self.full_name + ": " 82 | if len(self._contents) == 0: 83 | result += "no contents" 84 | else: 85 | for content in self._contents: 86 | result += str(content) + ", " 87 | return '<' + result + '>' 88 | 89 | def as_template_str(self): 90 | result = "" 91 | for content in self._contents: 92 | result += content.as_template_str() 93 | if self.slot_value is not None: 94 | result += ' ' + SLOT_VAL_SYM + ' ' + self.slot_value 95 | return result 96 | -------------------------------------------------------------------------------- /chatette/units/word.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | """ 5 | Module `chatette.units.word` 6 | Contains the definition of words as contents of rules. 7 | """ 8 | 9 | from chatette.units import Example 10 | from chatette.units.generating_item import GeneratingItem 11 | 12 | 13 | class Word(GeneratingItem): 14 | """Represents a word as a content of a rule.""" 15 | def __init__(self, name, leading_space): 16 | super(Word, self).__init__(name, leading_space) 17 | self.word = name 18 | 19 | def _compute_full_name(self): 20 | return "word '" + self._name + "'" 21 | 22 | def get_max_nb_possibilities(self): 23 | return 1 24 | def _compute_nb_possibilities(self): 25 | return 1 26 | 27 | def generate_random(self, **kwargs): 28 | return self._generate_random_strategy() 29 | def _generate_random_strategy(self): 30 | if self._leading_space: 31 | return Example(' ' + self._name) 32 | return Example(self._name) 33 | 34 | def generate_all(self): 35 | return self._generate_all_strategy() 36 | def _generate_all_strategy(self): 37 | if self._leading_space: 38 | return [Example(' ' + self._name)] 39 | return [Example(self._name)] 40 | 41 | def generate_nb_possibilities(self, nb_possibilities): 42 | return self._generate_n_strategy() 43 | def _generate_n_strategy(self, n=None): 44 | if self._leading_space: 45 | return [Example(' ' + self._name)] 46 | return [Example(self._name)] 47 | 48 | def as_template_str(self): 49 | if self._leading_space: 50 | return ' ' + self.word 51 | return self.word 52 | -------------------------------------------------------------------------------- /examples/complex/airport/aliases.chatette: -------------------------------------------------------------------------------- 1 | ~[help#verb] 2 | help 3 | aid 4 | assist 5 | ~[help#noun] 6 | help 7 | aid 8 | 9 | ~[me] 10 | [me|us] 11 | 12 | ~[i need] 13 | [&i]['d| would?] [need|want] 14 | [&i]['d| would] like 15 | 16 | ~[can you] 17 | can you 18 | could you 19 | would you [be so kind and?] 20 | 21 | ~[thing#singular] 22 | [thing|stuff] 23 | ~[thing#plural] 24 | [kind of|type of] [things|stuff] 25 | 26 | 27 | ~[book] 28 | book 29 | register 30 | 31 | ~[from airport] 32 | from @[source-airport] 33 | ~[to airport] 34 | [to go?] to @[source-airport] 35 | 36 | 37 | ~[this] 38 | this 39 | that 40 | it 41 | 42 | ~[be] 43 | is 44 | [will|would] be 45 | 46 | ~[for nb people] 47 | for @[nb-people] 48 | 49 | -------------------------------------------------------------------------------- /examples/complex/airport/master.chatette: -------------------------------------------------------------------------------- 1 | // Flight booking sentences 2 | %[&help]('train': '4', 'test': '2') 3 | ~[i need?] ~[help#noun] [please?] 4 | ~[help#verb] ~[me] [please?] 5 | ~[can you] ~[help#verb] ~[me] [please?][\??] 6 | what ~[thing] ~[can you] [do|help ~[me] with][\??] 7 | 8 | %[&book](training:12, testing:4) 9 | ~[i need?polite] [to?polite] ~[book] a flight ~[from airport?] ~[to airport] @[departure-time?] [please?] 10 | ~[can you] ~[book] a flight [for?for me] ~[me?for me] ~[from airport?] ~[to airport] [please?][\??] 11 | 12 | %[&number-tickets](train : 4) 13 | ~[this] ~[be] for @[nb-people][.?] 14 | 15 | %[&thank](2) 16 | Thank you 17 | Thanks [a lot?] 18 | 19 | |aliases.chatette 20 | |slots/cities.chatette 21 | |slots/departure-times.chatette 22 | |slots/nb-people.chatette 23 | 24 | -------------------------------------------------------------------------------- /examples/complex/airport/slots/cities.chatette: -------------------------------------------------------------------------------- 1 | // Lists of cities that are available as source airports and destination airports 2 | @[source-airport] 3 | Brussels 4 | Paris 5 | Amsterdam 6 | 7 | @[destination-airport] 8 | Paris 9 | Amsterdam 10 | London 11 | Edinburgh 12 | Berlin 13 | 14 | -------------------------------------------------------------------------------- /examples/complex/airport/slots/departure-times.chatette: -------------------------------------------------------------------------------- 1 | // Possible departure times for a flight 2 | @[departure-time] 3 | today 4 | now = today 5 | tomorrow 6 | 7 | -------------------------------------------------------------------------------- /examples/complex/airport/slots/nb-people.chatette: -------------------------------------------------------------------------------- 1 | // Slot for the number of people to book a flight for 2 | // This slot uses arguments 3 | @[nb-people] 4 | [~[nb$1]|~[nb$one]] person = 1 5 | [~[nb$2]|~[nb$two]] people = 2 6 | [~[nb$3]|~[nb$three]] people = 3 7 | [~[nb$4]|~[nb$four]] people = 4 8 | [~[nb$5]|~[nb$five]] people = 5 9 | [~[nb$6]|~[nb$six]] people = 6 10 | 11 | ~[nb$NB] 12 | exactly $NB 13 | precisely $NB 14 | $NB 15 | 16 | -------------------------------------------------------------------------------- /examples/complex/metal-work/client_info.chatette: -------------------------------------------------------------------------------- 1 | // This file shouldn't be fed directly to the Chatette generator 2 | // (it will be included inside the master file) 3 | // Contains information about the client 4 | 5 | ~[client factory name] 6 | [&La] [&Louviere] 7 | //[&La] [&Louvière] // Non-ascii characters may be a problem with python 2 8 | 9 | ~[partner factory name] 10 | [&Strasbourg] 11 | -------------------------------------------------------------------------------- /examples/complex/metal-work/master.chatette: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////// 2 | // File to feed to the Chatette generator (https:||github.com|SimGus|Chatette) // 3 | // Contains templates to generate datasets for Rasa NLU // 4 | // Master file // 5 | ///////////////////////////////////////////////////////////////////////////////// 6 | 7 | //=============== Includes =================== 8 | |client_info.chatette 9 | |aliases.chatette 10 | |slots.chatette 11 | 12 | //=============== Intents definitions ====================== 13 | //-------------- Intermediate answers ----------------- 14 | %[affirm](20) 15 | ~[agree] 16 | %[deny](15) 17 | ~[disagree] 18 | 19 | %[&inform_production_line](200) 20 | [~[yes],|~[no],?] ~[i said?] @[production_line#long] @[line_number#long?][.?] 21 | [~[yes],|~[no],?] ~[i said?] @[production_line#acronym] @[line_number#short?][.?] 22 | %[&inform_line_number](70) 23 | [~[yes],|~[no],?] ~[i said?] @[line_number] 24 | 25 | %[&inform_filter_time](120) 26 | [~[yes],|~[no],?] ~[i said?] @[filter_time] 27 | // Missing completion here 28 | %[&inform_utilization](180) 29 | [~[yes],|~[no],?] ~[i said?] @[utilization#number only] 30 | 31 | //--------------- Queries ---------------------- 32 | %[&query_machine_planning](1300) 33 | ~[what is#singular] the ~[planning of] @[production_line#long] @[line_number#long?]~[question mark?] 34 | ~[what is#singular] the ~[planning of] @[production_line#acronym] @[line_number#short?]~[question mark?] 35 | ~[ask?] the ~[planning of] @[production_line#long] @[line_number#long?][ please?]~[question mark?] 36 | ~[ask?] the ~[planning of] @[production_line#acronym] @[line_number#short?][ please?]~[question mark?] 37 | 38 | %[&query_filter_orders_time](900) 39 | ~[which] ~[order#plural] ~[are#no elision] @[filter_time]~[question mark?] 40 | ~[ask?] [all?] the ~[order#plural] ~[such that are] @[filter_time][ please?]~[question mark?] 41 | %[&query_filter_orders_completion](1200) 42 | ~[which] ~[order#plural] ~[are#no elision] @[filter_completion]~[question mark?] 43 | ~[ask?] [all?] the ~[order#plural] ~[such that are] @[filter_completion][ please?]~[question mark?] 44 | 45 | 46 | //--------------- Analysis ---------------------- 47 | %[&why_machine_utilization](1500) 48 | ~[why] is @[production_line#long] @[line_number#long?] [@[utilization#positive]|@[utilization#negative]]~[question mark?] 49 | ~[why] is @[production_line#acronym] @[line_number#short?] [@[utilization#positive]|@[utilization#negative]]~[question mark?] 50 | ~[why] ~[auxiliary#no elision] @[production_line#long] @[line_number#long?] be [@[utilization#positive]|@[utilization#negative]]~[question mark?] 51 | ~[why] ~[auxiliary#no elision] @[production_line#acronym] @[line_number#short?] be [@[utilization#positive]|@[utilization#negative]]~[question mark?] 52 | ~[ask] ~[why] @[production_line#long] @[line_number#long?] ~[is#no elision] @[utilization#positive]~[question mark?] 53 | ~[ask] ~[why] @[production_line#acronym] @[line_number#short?] ~[is#no elision] @[utilization#positive]~[question mark?] 54 | ~[what caused] @[production_line#long] @[line_number#long?] to be [@[utilization#positive]|@[utilization#negative]]~[question mark?] 55 | ~[what caused] @[production_line#acronym] @[line_number#short?] to be [@[utilization#positive]|@[utilization#negative]]~[question mark?] 56 | -------------------------------------------------------------------------------- /examples/complex/metal-work/prod_lines.chatette: -------------------------------------------------------------------------------- 1 | // This file shouldn't be fed directly to the Chatette generator 2 | // (it will be included inside the master file) 3 | // Contains the definitions of the production lines 4 | 5 | ~[prodline$ACRONYM] 6 | ~[production line] $ACRONYM 7 | the ~[production line] ~[named] $ACRONYM 8 | the $ACRONYM ~[production line] 9 | 10 | ~[GLO_SSP#long] 11 | ~[prodline$GLO_SSP] 12 | ~[prodline$glo_ssp] 13 | ~[LAL_HSM#long] 14 | ~[prodline$LAL_HSM] 15 | ~[prodline$lal_hsm] 16 | [our|the [internal?]] ~[line name#hot strip mill] ~[production line?] 17 | the ~[line name#hot strip mill] ~[production line?] of ~[client factory name] 18 | ~[LAL_SKP#long] 19 | ~[prodline$LAL_SKP] 20 | ~[prodline$lal_skp] 21 | ~[EXT_SKP#long] 22 | ~[prodline$EXT_SKP] 23 | ~[prodline$ext_skp] 24 | ~[LAL_PCK#long] 25 | ~[prodline$LAL_PCK] 26 | ~[prodline$lal_pck] 27 | [our|the internal] ~[line name#pickling] ~[production line?] 28 | the ~[line name#pickling] ~[production line?] of ~[client factory name] 29 | ~[EXT_PCK#long] 30 | ~[prodline$EXT_PCK] 31 | ~[prodline$ext_pck] 32 | the external ~[line name#pickling] ~[production line?] 33 | ~[LAL_CRM#long] 34 | ~[prodline$LAL_CRM] 35 | ~[prodline$lal_crm] 36 | [our|the internal] ~[line name#cold rolling mill] ~[production line?] 37 | the ~[line name#cold rolling mill] ~[production line?] of ~[client factory name] 38 | ~[EXT_CRM#long] 39 | ~[prodline$EXT_CRM] 40 | ~[prodline$ext_crm] 41 | the external ~[line name#cold rolling mill] ~[production line?] 42 | ~[LAL_PAC#long] 43 | ~[prodline$LAL_PAC] 44 | ~[prodline$lal_pac] 45 | [our|the internal] ~[line name#packing] ~[production line?] 46 | the ~[line name#packing] ~[production line?] of ~[client factory name] 47 | ~[LAL_SHP#long] 48 | ~[prodline$LAL_SHP] 49 | ~[prodline$lal_shp] 50 | [our|the internal] ~[line name#shipping] ~[production line?] 51 | the ~[line name#shipping] ~[production line?] of ~[client factory name] 52 | ~[STR_GLV#long] 53 | ~[prodline$STR_GLV] 54 | ~[prodline$str_glv] 55 | the ~[line name#galvanization] ~[production line?] 56 | ~[EXT_GLV#long] 57 | ~[prodline$EXT_GLV] 58 | ~[prodline$ext_glv] 59 | the external ~[line name#galvanization] ~[production line?] 60 | ~[STR_PNT#long] 61 | ~[prodline$STR_PNT] 62 | ~[prodline$str_pnt] 63 | the ~[line name#paint] ~[production line?] of ~[partner factory name] 64 | ~[EXT_PNT#long] 65 | ~[prodline$EXT_PNT] 66 | ~[prodline$ext_pnt] 67 | ~[STR_SLT#long] 68 | ~[prodline$STR_SLT] 69 | ~[prodline$str_slt] 70 | the ~[line name#slitting] ~[production line?] of ~[partner factory name] 71 | ~[STR_PAC#long] 72 | ~[prodline$STR_PAC] 73 | ~[prodline$str_pac] 74 | the ~[line name#packing] ~[production line?] of ~[partner factory name] 75 | ~[STR_SHP#long] 76 | ~[prodline$STR_SHP] 77 | ~[prodline$str_shp] 78 | the ~[line name#shipping] ~[production line?] of ~[partner factory name] 79 | ~[GLO_CLI#long] 80 | ~[prodline$GLO_CLI] 81 | ~[prodline$glo_cli] 82 | the ~[line name#client] ~[production line] 83 | 84 | 85 | ~[GLO_SSP#acronym] 86 | GLO_SSP 87 | glo_ssp 88 | ~[LAL_HSM#acronym] 89 | LAL_HSM 90 | lal_hsm 91 | ~[LAL_SKP#acronym] 92 | LAL_SKP 93 | lal_skp 94 | ~[EXT_SKP#acronym] 95 | EXT_SKP 96 | ext_skp 97 | ~[LAL_PCK#acronym] 98 | LAL_PCK 99 | lal_pck 100 | ~[EXT_PCK#acronym] 101 | EXT_PCK 102 | ext_pck 103 | ~[LAL_CRM#acronym] 104 | LAL_CRM 105 | lal_crm 106 | ~[EXT_CRM#acronym] 107 | EXT_CRM 108 | ext_crm 109 | ~[LAL_PAC#acronym] 110 | LAL_PAC 111 | lal_pac 112 | ~[LAL_SHP#acronym] 113 | LAL_SHP 114 | lal_shp 115 | ~[STR_GLV#acronym] 116 | STR_GLV 117 | str_glv 118 | ~[EXT_GLV#acronym] 119 | EXT_GLV 120 | ext_glv 121 | ~[STR_PNT#acronym] 122 | STR_PNT 123 | str_pnt 124 | ~[EXT_PNT#acronym] 125 | EXT_PNT 126 | ext_pnt 127 | ~[STR_SLT#acronym] 128 | STR_SLT 129 | str_slt 130 | ~[STR_PAC#acronym] 131 | STR_PAC 132 | str_pac 133 | ~[STR_SHP#acronym] 134 | STR_SHP 135 | str_shp 136 | ~[GLO_CLI#acronym] 137 | GLO_CLI 138 | glo_cli 139 | -------------------------------------------------------------------------------- /examples/complex/metal-work/slots.chatette: -------------------------------------------------------------------------------- 1 | // This file shouldn't be fed directly to the Chatette generator 2 | // (it will be included inside the master file) 3 | // Contains the slots definitions 4 | 5 | //=============== Slots definitions ===================== 6 | //--------------- Production lines --------------------- 7 | @[production_line#long] 8 | ~[GLO_SSP#long] = / 9 | ~[LAL_HSM#long] = / 10 | ~[LAL_SKP#long] = / 11 | ~[EXT_SKP#long] = / 12 | ~[LAL_PCK#long] = / 13 | ~[EXT_PCK#long] = / 14 | ~[LAL_CRM#long] = / 15 | ~[EXT_CRM#long] = / 16 | ~[LAL_PAC#long] = / 17 | ~[LAL_SHP#long] = / 18 | ~[STR_GLV#long] = / 19 | ~[EXT_GLV#long] = / 20 | ~[STR_PNT#long] = / 21 | ~[EXT_PNT#long] = / 22 | ~[STR_SLT#long] = / 23 | ~[STR_PAC#long] = / 24 | ~[STR_SHP#long] = / 25 | ~[GLO_CLI#long] = / 26 | @[production_line#acronym] 27 | ~[GLO_SSP#acronym] = / 28 | ~[LAL_HSM#acronym] = / 29 | ~[LAL_SKP#acronym] = / 30 | ~[EXT_SKP#acronym] = / 31 | ~[LAL_PCK#acronym] = / 32 | ~[EXT_PCK#acronym] = / 33 | ~[LAL_CRM#acronym] = / 34 | ~[EXT_CRM#acronym] = / 35 | ~[LAL_PAC#acronym] = / 36 | ~[LAL_SHP#acronym] = / 37 | ~[STR_GLV#acronym] = / 38 | ~[EXT_GLV#acronym] = / 39 | ~[STR_PNT#acronym] = / 40 | ~[EXT_PNT#acronym] = / 41 | ~[STR_SLT#acronym] = / 42 | ~[STR_PAC#acronym] = / 43 | ~[STR_SHP#acronym] = / 44 | ~[GLO_CLI#acronym] = / 45 | 46 | @[line_number#long] 47 | [[number ]|\#]1 = 1 48 | [[number ]|\#]2 = 2 49 | [[number ]|\#]3 = 3 50 | [[number ]|\#]4 = 4 51 | [[number ]|\#]5 = 5 52 | [[number ]|\#]6 = 6 53 | [[number ]|\#]7 = 7 54 | [[number ]|\#]8 = 8 55 | [[number ]|\#]9 = 9 56 | @[line_number#short] 57 | 1 58 | 2 59 | 3 60 | 4 61 | 5 62 | 6 63 | 7 64 | 8 65 | 9 66 | 67 | //----------------- Time ---------------------- 68 | @[filter_time] 69 | ~[late] 70 | ~[on time] 71 | 72 | //---------------- Completion ------------------ 73 | @[filter_completion] 74 | ~[fully planned] = 1.0 75 | ~[planned at$1.0] = 1.0 76 | ~[planned at$0.9] = 0.9 77 | ~[planned at$0.8] = 0.8 78 | ~[planned at$0.7] = 0.7 79 | ~[planned at$0.6] = 0.6 80 | ~[planned at$0.5] = 0.5 81 | ~[planned at$0.4] = 0.4 82 | ~[planned at$0.3] = 0.3 83 | ~[planned at$0.2] = 0.2 84 | ~[planned at$0.1] = 0.1 85 | ~[planned at$0.0] = 0.0 86 | ~[not planned] = 0.0 87 | 88 | //------------------ Utilization ------------------ 89 | @[utilization#positive] 90 | ~[fully used] = 1.0 91 | ~[used at$100] = 1.0 92 | ~[used at$90] = 0.9 93 | ~[used at$80] = 0.8 94 | ~[used at$70] = 0.7 95 | ~[used at$60] = 0.6 96 | ~[used at$50] = 0.5 97 | ~[used at$40] = 0.4 98 | ~[used at$30] = 0.3 99 | ~[used at$20] = 0.2 100 | ~[used at$10] = 0.1 101 | ~[used at$0] = 0.0 102 | ~[not used] = 0.0 103 | @[utilization#negative] 104 | not ~[fully used] = not fully 105 | @[utilization#number only] 106 | ~[fully used] = 1.0 107 | ~[utilization$100] = 1.0 108 | ~[utilization$90] = 0.9 109 | ~[utilization$80] = 0.8 110 | ~[utilization$70] = 0.7 111 | ~[utilization$60] = 0.6 112 | ~[utilization$50] = 0.5 113 | ~[utilization$40] = 0.4 114 | ~[utilization$30] = 0.3 115 | ~[utilization$20] = 0.2 116 | ~[utilization$10] = 0.1 117 | ~[utilization$0] = 0.0 118 | ~[not used] = 0.0 119 | -------------------------------------------------------------------------------- /examples/simple/airport/aliases.chatette: -------------------------------------------------------------------------------- 1 | ~[help#verb] 2 | help 3 | aid 4 | assist 5 | ~[help#noun] 6 | help 7 | aid 8 | 9 | ~[me] 10 | [me|us] 11 | 12 | ~[i need] 13 | [&i]['d| would?] [need|want] 14 | [&i]['d| would] like 15 | 16 | ~[can you] 17 | can you 18 | could you 19 | would you [be so kind and?] 20 | 21 | ~[book] 22 | book 23 | register 24 | 25 | ~[from airport] 26 | from @[source-airport] 27 | ~[to airport] 28 | [to go?] to @[source-airport] 29 | -------------------------------------------------------------------------------- /examples/simple/airport/master.chatette: -------------------------------------------------------------------------------- 1 | // Flight booking sentences 2 | %[&help]('train': '4', 'test': '2') 3 | ~[i need?] ~[help#noun] [please?] 4 | ~[help#verb] ~[me] [please?] 5 | ~[can you] ~[help#verb] ~[me] [please?][\??] 6 | 7 | %[&book](training:12, testing:4) 8 | ~[i need?polite] [to?polite] ~[book] a flight ~[from airport?] ~[to airport] @[departure-time?] [please?] 9 | 10 | %[&thank](2) 11 | Thank you 12 | Thanks [a lot?] 13 | 14 | |aliases.chatette 15 | |slots/cities.chatette 16 | |slots/departure-times.chatette 17 | 18 | -------------------------------------------------------------------------------- /examples/simple/airport/slots/cities.chatette: -------------------------------------------------------------------------------- 1 | // Lists of cities that are available as source airports and destination airports 2 | @[source-airport] 3 | Brussels 4 | Paris 5 | Amsterdam 6 | 7 | @[destination-airport] 8 | Paris 9 | Amsterdam 10 | London 11 | Edinburgh 12 | Berlin 13 | 14 | -------------------------------------------------------------------------------- /examples/simple/airport/slots/departure-times.chatette: -------------------------------------------------------------------------------- 1 | // Possible departure times for a flight 2 | @[departure-time] 3 | today 4 | now = today 5 | tomorrow 6 | 7 | -------------------------------------------------------------------------------- /examples/simple/gif/gif.chatette: -------------------------------------------------------------------------------- 1 | // This file contains the templates that were used to 2 | // make the gif image that is present on the readme file 3 | // of the repository. 4 | 5 | %[&hello](train:3) 6 | hello! 7 | hi 8 | hey ~[guys?] 9 | 10 | %[query mood](train:2) 11 | how are you ~[guys?]? 12 | are you ~[guys?] alright? 13 | %[query identity](train:2) 14 | who are you ~[guys?]? 15 | what is your name? 16 | 17 | %[&ask identity](train:2) 18 | are you @[name]? 19 | 20 | ~[guys] 21 | all 22 | guys 23 | ~[UNUSED] 24 | nothing 25 | 26 | @[name] 27 | John 28 | Matt 29 | Ringo 30 | 31 | -------------------------------------------------------------------------------- /examples/simple/mood/aliases_and_slots.chatette: -------------------------------------------------------------------------------- 1 | // File defining the aliases and slots 2 | ~[hello] 3 | hello 4 | hi 5 | 6 | ~[am#elision] 7 | 'm 8 | 've been 9 | ~[am#no elision] 10 | am 11 | have been 12 | 13 | ~[bye] 14 | bye 15 | goodbye 16 | see you 17 | 18 | @[mood#good] 19 | good 20 | fine = good 21 | great = good 22 | @[mood#bad] 23 | bad = / 24 | not so good = bad 25 | terrible = bad 26 | -------------------------------------------------------------------------------- /examples/simple/mood/master.chatette: -------------------------------------------------------------------------------- 1 | // Master file defining the intents 2 | %[&greet](2) 3 | ~[hello][!?] 4 | 5 | %[&inform-mood](5) 6 | [well?] [&i][~[am#elision]| ~[am#no elision]] @[mood#good][!?] [:)?] 7 | [well?] [&i][~[am#elision]| ~[am#no elision]] @[mood#bad][...?] 8 | [&i] feel @[mood#good][!?] [:)?] 9 | [&i] feel @[mood#bad][...?] 10 | 11 | %[&goodbye](2) 12 | ~[bye][!?] 13 | 14 | |aliases_and_slots.chatette 15 | -------------------------------------------------------------------------------- /examples/simple/restaurant/included.chatette: -------------------------------------------------------------------------------- 1 | // This file will be included in another file that calls it as: 2 | // |included.chatette 3 | 4 | @[cuisine_type] 5 | ~[italian] // if this is generated, this slot will have the value named `italian` 6 | ~[french] // if this is generated, this slot will have the value named `french` 7 | ~[mexican (nom nom)] = mexican // if this is generated, this slot will have the value named `mexican` 8 | ~[homemade] = / // if this is generated, this slot will have the same value as the generated text 9 | -------------------------------------------------------------------------------- /examples/simple/restaurant/master.chatette: -------------------------------------------------------------------------------- 1 | // Fed this file, Chatette will generate a dataset of sentences with two intents: 2 | // greeting the interlocutor and telling them what kind of cuisine the speaker 3 | // would want 4 | 5 | //================== Intent definitions ======================= 6 | %[greet] 7 | ~[&greet] 8 | 9 | %[restaurant_choice](3) // Will generate three utterances 10 | ~[&greet?] ~[i want to] eat @[cuisine_type] cuisine[ today?]. 11 | 12 | //================== Slot definitions ========================= 13 | |included.chatette // the slot definition is in the file `included.chatette` which will be included here 14 | 15 | //================== Alias definitions ======================= 16 | ~[greet] 17 | [hi|hello] 18 | 19 | ~[i want to] 20 | [&i] ~[want#no elision] 21 | [&I]~[want#elision] 22 | 23 | ~[want#no elision] 24 | want to 25 | would [really?] like to // either `would really like to` or `would like to` will be generated 26 | ~[want#elision] 27 | 'd like to 28 | 29 | ~[italian] 30 | italian 31 | 32 | ~[french] 33 | french 34 | francaise 35 | 36 | ~[mexican (nom nom)] 37 | mexican 38 | 39 | ~[homemade] 40 | homemade 41 | mom's 42 | -------------------------------------------------------------------------------- /examples/simple/toilets/toilets.chatette: -------------------------------------------------------------------------------- 1 | // This template defines different ways to ask for the location of toilets 2 | %[&ask_toilet](3) 3 | ~[sorry?] ~[tell me] where the [@[toilet#singular] is|@[toilet#plural] are] [please?]? 4 | 5 | ~[sorry] 6 | sorry 7 | excuse me 8 | 9 | ~[tell me] 10 | ~[can you?] [tell|show] me 11 | ~[can you] 12 | [can|could|would] you 13 | 14 | @[toilet#singular] 15 | toilet 16 | loo 17 | @[toilet#plural] 18 | toilets 19 | -------------------------------------------------------------------------------- /public/images/chatette-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/images/chatette-logo.png -------------------------------------------------------------------------------- /public/images/interactive-mode-long.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/images/interactive-mode-long.gif -------------------------------------------------------------------------------- /public/images/interactive-mode-short.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/images/interactive-mode-short.gif -------------------------------------------------------------------------------- /public/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/images/preview.png -------------------------------------------------------------------------------- /public/uml/1.3.2/classes_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.3.2/classes_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.3.2/packages_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.3.2/packages_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.4.0/classes_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.4.0/classes_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.4.0/packages_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.4.0/packages_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.5.0/classes_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.5.0/classes_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.5.0/packages_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.5.0/packages_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.6.0/classes_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.6.0/classes_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.6.0/packages_No_Name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.6.0/packages_No_Name.png -------------------------------------------------------------------------------- /public/uml/1.6.1/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.6.1/classes.png -------------------------------------------------------------------------------- /public/uml/1.6.1/packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.6.1/packages.png -------------------------------------------------------------------------------- /public/uml/1.6.2/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.6.2/classes.png -------------------------------------------------------------------------------- /public/uml/1.6.2/packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimGus/Chatette/fd22b6c2e4a27b222071c93772c2ae99387aa5c3/public/uml/1.6.2/packages.png -------------------------------------------------------------------------------- /requirements/common.txt: -------------------------------------------------------------------------------- 1 | enum-compat 2 | future 3 | six 4 | -------------------------------------------------------------------------------- /requirements/develop.txt: -------------------------------------------------------------------------------- 1 | -r test.txt 2 | tox 3 | pylint 4 | 5 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r common.txt 2 | pytest>=3.6 3 | pytest-cov 4 | codecov 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | from os.path import dirname, join 5 | 6 | from setuptools import find_packages, setup 7 | 8 | # Manual: 9 | # https://the-hitchhikers-guide-to-packaging.readthedocs.io/en/latest/quickstart.html 10 | 11 | setup( 12 | name="chatette", 13 | version="1.6.3", 14 | description="A dataset generator for Rasa NLU", 15 | author="SimGus", 16 | license="MIT", 17 | url="https://github.com/SimGus/Chatette", 18 | packages=find_packages(), 19 | long_description=open(join(dirname(__file__), "README.md")).read(), 20 | long_description_content_type="text/markdown", 21 | classifiers=[ 22 | "Programming Language :: Python :: 2.7", 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | "Environment :: Console", 27 | "Intended Audience :: Developers", 28 | "Topic :: Utilities", 29 | "Topic :: Text Processing", 30 | "Topic :: Communications", 31 | ], 32 | install_requires=[ 33 | "enum-compat", 34 | "future", 35 | "six", 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /tests/notes.md: -------------------------------------------------------------------------------- 1 | # Description of the module 2 | This module contains the tests for the `chatette` package (located in `../chatette`). All tests are intended to be executed using `pytest`. 3 | 4 | Note that as the package is supposed to work for both *Python 2.7* and *Python 3.x*, you should run tests for using both `python2 -m pytest` and `python3 -m pytest`. 5 | 6 | `unit-testing` contains the unit tests for all the modules in `chatette`; `system-testing` contains the system test (for `chatette` taken as a black-box). 7 | 8 | # Problems with `pytest`? 9 | If you get an `ImportError` (no module named `chatette.`), you should install the package as editable. To do this, go to the directory `..` (containing `setup.py`) and run `pip install --editable .`. The package will change when you change the code in `chatette`. 10 | 11 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/alias.chatette: -------------------------------------------------------------------------------- 1 | %[single] 2 | ~[&alias] 3 | ~[complex alias] 4 | ~[cascading] 5 | ~[variation] 6 | 7 | %[multiple] 8 | word ~[alias?] 9 | ~[cascading] ~[cascading] 10 | ~[variation#one] => ~[variation#two?/60] 11 | 12 | %[arg] 13 | ~[arged#1$ARGUMENT] 14 | ~[arged#2$other argument] 15 | 16 | ~[alias] 17 | alias 18 | ~[complex alias] 19 | more complex alias 20 | several words and a [&group] 21 | ~[&cascading] 22 | cascading ~[alias] 23 | ~[variation#one] 24 | variation one 25 | ~[variation#two] 26 | variation two 27 | ~[arged#1$ARG] 28 | arged ($ARG) 29 | ~[arged#2$ARG] 30 | second arg: $ARG 31 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/alias.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single>>>alias 3 | single>>>Alias 4 | single>>>more complex alias 5 | single>>>several words and a group 6 | single>>>several words and a Group 7 | single>>>cascading alias 8 | single>>>Cascading alias 9 | single>>>variation one 10 | single>>>variation two 11 | multiple>>>word alias 12 | multiple>>>word 13 | multiple>>>cascading alias cascading alias 14 | multiple>>>Cascading alias cascading alias 15 | multiple>>>cascading alias Cascading alias 16 | multiple>>>Cascading alias Cascading alias 17 | multiple>>>variation one => 18 | multiple>>>variation one => variation two 19 | arg>>>arged (ARGUMENT) 20 | arg>>>second arg: other argument 21 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/include.chatette: -------------------------------------------------------------------------------- 1 | %[include] 2 | ~[alias] 3 | ~[second alias] 4 | 5 | |included.chatette 6 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/include.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | include>>>alias 3 | include>>>other alias 4 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/included.chatette: -------------------------------------------------------------------------------- 1 | ~[alias] 2 | alias 3 | ~[second alias] 4 | other alias 5 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/only-words.chatette: -------------------------------------------------------------------------------- 1 | %[single word] 2 | Hello 3 | Hi 4 | hola 5 | 6 | %[several words] 7 | Two words 8 | another set of several words 9 | words with (special) symbols! :D 10 | 11 | %[&casegen intent] 12 | word 13 | casegened word 14 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/only-words.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single word>>>Hello 3 | single word>>>Hi 4 | single word>>>hola 5 | several words>>>Two words 6 | several words>>>another set of several words 7 | several words>>>words with (special) symbols! :D 8 | casegen intent>>>word 9 | casegen intent>>>Word 10 | casegen intent>>>casegened word 11 | casegen intent>>>Casegened word 12 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/simplest.chatette: -------------------------------------------------------------------------------- 1 | %[intent] 2 | test 3 | %[intent two] 4 | other test 5 | 6 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/simplest.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | intent>>>test 3 | intent two>>>other test 4 | 5 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/slot.chatette: -------------------------------------------------------------------------------- 1 | %[single] 2 | @[&slot] 3 | 4 | %[multiple] 5 | @[slot] @[other] 6 | @[slot] @[slot?] 7 | 8 | @[slot] 9 | slot one = slot1 10 | slot1 11 | @[&other] 12 | other = other slot 13 | other slot = other slot 14 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/slot.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single>>>slot one 3 | single>>>slot1 4 | single>>>Slot one 5 | single>>>Slot1 6 | multiple>>>slot one other 7 | multiple>>>slot1 other 8 | multiple>>>slot one other slot 9 | multiple>>>slot1 other slot 10 | multiple>>>slot one Other 11 | multiple>>>slot1 Other 12 | multiple>>>slot one Other slot 13 | multiple>>>slot1 Other slot 14 | 15 | multiple>>>slot one 16 | multiple>>>slot1 17 | multiple>>>slot one slot one 18 | multiple>>>slot one slot1 19 | multiple>>>slot1 slot one 20 | multiple>>>slot1 slot1 21 | 22 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/slot.syn: -------------------------------------------------------------------------------- 1 | # Contains all synonyms for the Chatette template file with the same name. 2 | slot1===slot1 3 | slot1===slot one 4 | other slot===other slot 5 | other slot===other 6 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/words-and-groups.chatette: -------------------------------------------------------------------------------- 1 | %[single word group] 2 | [a word group] 3 | 4 | %[words and word groups] 5 | a word [a group] 6 | [a group] a word 7 | word [group] word [group] [group] 8 | 9 | %[casegen groups] 10 | [&casegened!] 11 | word [&casegened] [&another] 12 | 13 | %[&casegen intent] 14 | [word group] 15 | word [word group] 16 | 17 | %[randgen] 18 | word [maybe group?] 19 | [group] [maybe group?] 20 | 21 | %[both] 22 | word [&maybe casegened group?] 23 | [group] [&maybe casegened group?] 24 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-all/words-and-groups.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single word group>>>a word group 3 | words and word groups>>>a word a group 4 | words and word groups>>>a group a word 5 | words and word groups>>>word group word group group 6 | casegen groups>>>casegened! 7 | casegen groups>>>Casegened! 8 | casegen groups>>>word casegened another 9 | casegen groups>>>word Casegened another 10 | casegen groups>>>word casegened Another 11 | casegen groups>>>word Casegened Another 12 | casegen intent>>>word group 13 | casegen intent>>>Word group 14 | casegen intent>>>word word group 15 | casegen intent>>>Word word group 16 | randgen>>>word maybe group 17 | randgen>>>word 18 | randgen>>>group maybe group 19 | randgen>>>group 20 | both>>>word 21 | both>>>word maybe casegened group 22 | both>>>word Maybe casegened group 23 | both>>>group 24 | both>>>group maybe casegened group 25 | both>>>group Maybe casegened group 26 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/alias.chatette: -------------------------------------------------------------------------------- 1 | %[single](9) 2 | ~[&alias] 3 | ~[complex alias] 4 | ~[cascading] 5 | ~[variation] 6 | 7 | %[multiple]('training': '6') 8 | word ~[alias?] 9 | ~[cascading] ~[cascading] 10 | ~[variation#one] => ~[variation#two?/60] 11 | 12 | %[arg](train: 3) 13 | ~[arged#1$ARGUMENT] 14 | ~[arged#2$other argument] 15 | 16 | ~[alias] 17 | alias 18 | ~[complex alias] 19 | more complex alias 20 | several words and a [&group] 21 | ~[&cascading] 22 | cascading ~[alias] 23 | ~[variation#one] 24 | variation one 25 | ~[variation#two] 26 | variation two 27 | ~[arged#1$ARG] 28 | arged ($ARG) 29 | ~[arged#2$ARG] 30 | second arg: $ARG 31 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/alias.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single>>>alias 3 | single>>>Alias 4 | single>>>more complex alias 5 | single>>>several words and a group 6 | single>>>several words and a Group 7 | single>>>cascading alias 8 | single>>>Cascading alias 9 | single>>>variation one 10 | single>>>variation two 11 | multiple>>>word alias 12 | multiple>>>word 13 | multiple>>>cascading alias cascading alias 14 | multiple>>>Cascading alias cascading alias 15 | multiple>>>cascading alias Cascading alias 16 | multiple>>>Cascading alias Cascading alias 17 | multiple>>>variation one => 18 | multiple>>>variation one => variation two 19 | arg>>>arged (ARGUMENT) 20 | arg>>>second arg: other argument 21 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/bugfixes/bug-22-slot-position.chatette: -------------------------------------------------------------------------------- 1 | %[askPopulation]('training': '5') 2 | ~[greet?] ~[population] @[city] 3 | 4 | @[city] 5 | new york 6 | tokyo 7 | 8 | ~[greet] 9 | hi 10 | hello 11 | 12 | ~[population] 13 | what is the population of 14 | how many people are there in 15 | 16 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/bugfixes/bug-22-slot-position.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | askPopulation>>>how many people are there in new york 3 | askPopulation>>>how many people are there in tokyo 4 | askPopulation>>>what is the population of new york 5 | askPopulation>>>what is the population of tokyo 6 | askPopulation>>>hello how many people are there in new york 7 | askPopulation>>>hello how many people are there in tokyo 8 | askPopulation>>>hello what is the population of new york 9 | askPopulation>>>hello what is the population of tokyo 10 | askPopulation>>>hi how many people are there in new york 11 | askPopulation>>>hi how many people are there in tokyo 12 | askPopulation>>>hi what is the population of new york 13 | askPopulation>>>hi what is the population of tokyo 14 | 15 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/include.chatette: -------------------------------------------------------------------------------- 1 | %[include]('train': 1) 2 | ~[alias] 3 | ~[second alias] 4 | 5 | |included.chatette 6 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/include.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | include>>>alias 3 | include>>>other alias 4 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/included.chatette: -------------------------------------------------------------------------------- 1 | ~[alias] 2 | alias 3 | ~[second alias] 4 | other alias 5 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/one-ex.chatette: -------------------------------------------------------------------------------- 1 | %[only intent](train:'1') 2 | test 3 | other test 4 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/only-words.chatette: -------------------------------------------------------------------------------- 1 | %[single word](2) 2 | Hello 3 | Hi 4 | hola 5 | 6 | %[several words](training : 2) 7 | Two words 8 | another set of several words 9 | words with (special) symbols! :D 10 | 11 | %[&casegen intent](train: '1') 12 | word 13 | casegened word 14 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/only-words.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single word>>>Hello 3 | single word>>>Hi 4 | single word>>>hola 5 | several words>>>Two words 6 | several words>>>another set of several words 7 | several words>>>words with (special) symbols! :D 8 | casegen intent>>>word 9 | casegen intent>>>Word 10 | casegen intent>>>casegened word 11 | casegen intent>>>Casegened word 12 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/slot.chatette: -------------------------------------------------------------------------------- 1 | %[single](training: '1') 2 | @[&slot] 3 | 4 | %[multiple]('train' : '2') 5 | @[slot] @[other] 6 | @[slot] @[slot?] 7 | 8 | @[slot] 9 | slot one = slot1 10 | slot1 11 | @[&other] 12 | other = other slot 13 | other slot = other slot 14 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/slot.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single>>>slot one 3 | single>>>slot1 4 | single>>>Slot one 5 | single>>>Slot1 6 | multiple>>>slot one other 7 | multiple>>>slot1 other 8 | multiple>>>slot one other slot 9 | multiple>>>slot1 other slot 10 | multiple>>>slot one Other 11 | multiple>>>slot1 Other 12 | multiple>>>slot one Other slot 13 | multiple>>>slot1 Other slot 14 | 15 | multiple>>>slot one 16 | multiple>>>slot1 17 | multiple>>>slot one slot one 18 | multiple>>>slot one slot1 19 | multiple>>>slot1 slot one 20 | multiple>>>slot1 slot1 21 | multiple>>>slot one Slot one 22 | multiple>>>slot one Slot1 23 | multiple>>>slot1 Slot one 24 | multiple>>>slot1 Slot1 25 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/slot.syn: -------------------------------------------------------------------------------- 1 | # Contains all synonyms for the Chatette template file with the same name. 2 | slot1===slot1 3 | slot1===slot one 4 | other slot===other slot 5 | other slot===other 6 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/words-and-groups.chatette: -------------------------------------------------------------------------------- 1 | %[single word group](1) 2 | [a word group] 3 | 4 | %[words and word groups](train:'2') 5 | a word [a group] 6 | [a group] a word 7 | word [group] word [group] [group] 8 | 9 | %[casegen groups]('train':2) 10 | [&casegened!] 11 | word [&casegened] [&another] 12 | 13 | %[&casegen intent](training:'1') 14 | [word group] 15 | word [word group] 16 | 17 | %[randgen](train:3) 18 | word [maybe group?] 19 | [group] [maybe group?] 20 | 21 | %[both](training:4) 22 | word [&maybe casegened group?] 23 | [group] [&maybe casegened group?] 24 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/words-and-groups.solution: -------------------------------------------------------------------------------- 1 | # Contains all possible examples for the Chatette template file with the same name. 2 | single word group>>>a word group 3 | words and word groups>>>a word a group 4 | words and word groups>>>a group a word 5 | words and word groups>>>word group word group group 6 | casegen groups>>>casegened! 7 | casegen groups>>>Casegened! 8 | casegen groups>>>word casegened another 9 | casegen groups>>>word Casegened another 10 | casegen groups>>>word casegened Another 11 | casegen groups>>>word Casegened Another 12 | casegen intent>>>word group 13 | casegen intent>>>Word group 14 | casegen intent>>>word word group 15 | casegen intent>>>Word word group 16 | randgen>>>word maybe group 17 | randgen>>>word 18 | randgen>>>group maybe group 19 | randgen>>>group 20 | both>>>word 21 | both>>>word maybe casegened group 22 | both>>>word Maybe casegened group 23 | both>>>group 24 | both>>>group maybe casegened group 25 | both>>>group Maybe casegened group 26 | -------------------------------------------------------------------------------- /tests/system-testing/inputs/generate-nb/training-only/zero-ex.chatette: -------------------------------------------------------------------------------- 1 | %[intent](0) 2 | test 3 | other test 4 | 5 | %[other intent]('train':'0') 6 | [test?] test? 7 | [yet?] another test 8 | 9 | %[last intent](training : 0) 10 | [test|another] 11 | a third [\??] 12 | -------------------------------------------------------------------------------- /tests/unit-testing/adapter/test_base.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities that are present in module 5 | `chatette.adapters._base` 6 | """ 7 | 8 | import pytest 9 | 10 | from chatette.adapters._base import Batch, Adapter 11 | 12 | 13 | class TestBatch(object): 14 | def test_constructor(self): 15 | batch = Batch(2, [], []) 16 | assert batch.index == 2 17 | assert len(batch.examples) == 0 18 | assert len(batch.synonyms) == 0 19 | 20 | 21 | class TestAdapter(object): 22 | def test_abstract(self): 23 | with pytest.raises(TypeError): 24 | Adapter() 25 | 26 | def test_generate_batch(self): 27 | for batch in Adapter._Adapter__generate_batch(["ex"], [], None): 28 | assert batch.index == 0 29 | assert len(batch.examples) == 1 30 | assert batch.examples[0] == "ex" 31 | assert len(batch.synonyms) == 0 32 | 33 | for batch in Adapter._Adapter__generate_batch(["a", "b"], [], 1): 34 | assert batch.index in (0, 1) 35 | assert len(batch.examples) == 1 36 | assert batch.examples[0] in ("a", "b") 37 | assert len(batch.synonyms) == 0 38 | -------------------------------------------------------------------------------- /tests/unit-testing/adapter/test_factory.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities that are present in module 5 | `chatette.adapters.factory`. 6 | """ 7 | 8 | import pytest 9 | 10 | from chatette.adapters.factory import create_adapter 11 | from chatette.adapters.jsonl import JsonListAdapter 12 | from chatette.adapters.rasa import RasaAdapter 13 | from chatette.adapters.rasa_md import RasaMdAdapter 14 | 15 | 16 | def test_valid(): 17 | assert isinstance(create_adapter("jsonl"), JsonListAdapter) 18 | assert isinstance(create_adapter("rasa"), RasaAdapter) 19 | assert isinstance(create_adapter("rasa-md"), RasaMdAdapter) 20 | assert isinstance(create_adapter("rasamd"), RasaMdAdapter) 21 | 22 | def test_invalid(): 23 | assert create_adapter(None) is None 24 | with pytest.raises(ValueError): 25 | create_adapter("no adapter") 26 | -------------------------------------------------------------------------------- /tests/unit-testing/adapter/test_jsonl.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities present in module 5 | `chatette.adapters.jsonl`. 6 | """ 7 | 8 | from chatette.adapters.jsonl import JsonListAdapter 9 | 10 | 11 | class TestJsonListAdapter(object): 12 | def test_constructor(self): 13 | adapter = JsonListAdapter() 14 | assert adapter._batch_size == 10000 15 | assert adapter._base_filepath is None 16 | 17 | adapter = JsonListAdapter("path", 10) 18 | assert adapter._batch_size == 10 19 | assert adapter._base_filepath == "path" 20 | 21 | def test_file_extension(self): 22 | assert JsonListAdapter._get_file_extension() == "jsonl" 23 | 24 | def test_synonym_format(self): 25 | synonyms = { 26 | u'Edinburgh': [u'Edinburgh'], 27 | u'Paris': [u'Paris'], u'Berlin': [u'Berlin'], 28 | u'London': [u'London'], u'Amsterdam': [u'Amsterdam'], 29 | u'Brussels': [u'Brussels'], u'tomorrow': [u'tomorrow'], 30 | u'today': [u'now', u'today'] 31 | } 32 | formatted_synonyms = \ 33 | JsonListAdapter._JsonListAdapter__format_synonyms(synonyms) 34 | assert formatted_synonyms == {u'today': [u'now', u'today']} 35 | -------------------------------------------------------------------------------- /tests/unit-testing/adapter/test_rasa.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities present in module 5 | `chatette.adapters.rasa`. 6 | """ 7 | 8 | from chatette.adapters.rasa import RasaAdapter 9 | 10 | 11 | class TestRasaAdapter(object): 12 | def test_constructor(self): 13 | adapter = RasaAdapter() 14 | assert adapter._batch_size == 10000 15 | assert adapter._base_filepath is None 16 | 17 | adapter = RasaAdapter("path", 10) 18 | assert adapter._batch_size == 10 19 | assert adapter._base_filepath == "path" 20 | 21 | def test_get_file_extension(self): 22 | assert RasaAdapter._get_file_extension() == "json" 23 | 24 | def test_format_synonyms(self): 25 | synonyms = { 26 | u'Edinburgh': [u'Edinburgh'], 27 | u'Paris': [u'Paris'], u'Berlin': [u'Berlin'], 28 | u'London': [u'London'], u'Amsterdam': [u'Amsterdam'], 29 | u'Brussels': [u'Brussels'], u'tomorrow': [u'tomorrow'], 30 | u'today': [u'now', u'today'] 31 | } 32 | formatted_synonyms = \ 33 | RasaAdapter._RasaAdapter__format_synonyms(synonyms) 34 | assert \ 35 | formatted_synonyms == [ 36 | {"value": u'today', "synonyms": [u'now', u'today']} 37 | ] 38 | -------------------------------------------------------------------------------- /tests/unit-testing/adapter/test_rasa_md.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities present in module 5 | `chatette.adapters.rasa_md`. 6 | """ 7 | 8 | from chatette.adapters.rasa_md import RasaMdAdapter 9 | 10 | 11 | class TestRasaMdAdapter(object): 12 | def test_constructor(self): 13 | adapter = RasaMdAdapter() 14 | assert adapter._batch_size is None 15 | assert adapter._base_filepath is None 16 | 17 | adapter = RasaMdAdapter("path") 18 | assert adapter._batch_size is None 19 | assert adapter._base_filepath == "path" 20 | 21 | def test_get_file_extension(self): 22 | assert RasaMdAdapter._get_file_extension() == "md" 23 | 24 | def test_format_synonyms(self): 25 | synonyms = { 26 | u'Edinburgh': [u'Edinburgh'], 27 | u'Paris': [u'Paris'], u'Berlin': [u'Berlin'], 28 | u'London': [u'London'], u'Amsterdam': [u'Amsterdam'], 29 | u'Brussels': [u'Brussels'], u'tomorrow': [u'tomorrow'], 30 | u'today': [u'now', u'today'] 31 | } 32 | formatted_synonyms = \ 33 | RasaMdAdapter._RasaMdAdapter__format_synonyms(synonyms) 34 | assert formatted_synonyms == "## synonym:today\n- now\n\n" 35 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/other.chatette: -------------------------------------------------------------------------------- 1 | // This template defines different ways to ask for the location of toilets 2 | %[&hello](3) 3 | ~[greetings] [people|guys?] 4 | 5 | ~[greetings] 6 | greetings 7 | hello 8 | hey 9 | hi 10 | 11 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_declare_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functionalities present in module 4 | `chatette.cli.interactive_commands.decalre_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from test_command_strategy import new_facade 10 | 11 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 12 | from chatette.cli.interactive_commands.declare_command import DeclareCommand 13 | 14 | from chatette.utils import UnitType 15 | from chatette.units.ast import AST 16 | 17 | 18 | def test_obj(): 19 | cmd = DeclareCommand("") 20 | assert cmd.command_tokens == [] 21 | assert isinstance(cmd, CommandStrategy) 22 | 23 | cmd = DeclareCommand("declare alias /a/") 24 | assert cmd.command_tokens == ["declare", "alias", "/a/"] 25 | assert isinstance(cmd, CommandStrategy) 26 | 27 | 28 | def test_err(capsys): 29 | new_facade() 30 | 31 | cmd = DeclareCommand("declare") 32 | cmd.execute() 33 | captured = capsys.readouterr() 34 | assert "[ERROR]\tMissing some arguments\n\tUsage: " + \ 35 | 'declare ""' in captured.out 36 | 37 | cmd = DeclareCommand("declare nothing /a/") 38 | cmd.execute() 39 | captured = capsys.readouterr() 40 | assert "[ERROR]\tUnknown unit type: 'nothing'." in captured.out 41 | 42 | cmd = DeclareCommand('declare alias "var#a#b"') 43 | cmd.execute() 44 | captured = capsys.readouterr() 45 | assert "[ERROR]\tUnit identifier couldn't be interpreted. " + \ 46 | "Did you mean to escape some hashtags '#'?" in captured.out 47 | 48 | cmd = DeclareCommand('declare alias "a#var"') 49 | cmd.execute() 50 | captured = capsys.readouterr() 51 | assert "[ERROR]\tVariation name detected, " + \ 52 | "while units cannot be declared with a variation. " + \ 53 | "Did you mean to escape some hashtags '#'?" in captured.out 54 | 55 | cmd = DeclareCommand('declare alias "var"') 56 | cmd.execute() 57 | capsys.readouterr() 58 | assert "Alias 'var' is already defined." 59 | 60 | 61 | def test_execute(capsys): 62 | new_facade() 63 | cmd = DeclareCommand('declare alias "machin"') 64 | cmd.execute() 65 | try: 66 | unit = AST.get_or_create()[UnitType.alias]["machin"] 67 | assert len(unit._variation_rules) == 0 68 | assert len(unit._all_rules) == 0 69 | except (KeyError, ValueError): 70 | pytest.fail("Unexpected error, 'declare' command didn't work.") 71 | 72 | captured = capsys.readouterr() 73 | assert "Alias 'machin' was successfully declared." in captured.out 74 | 75 | print("AAAAAAAAAAAA") 76 | new_facade() 77 | cmd = DeclareCommand('declare slot "machin"') 78 | cmd.execute() 79 | try: 80 | unit = AST.get_or_create()[UnitType.slot]["machin"] 81 | assert len(unit._variation_rules) == 0 82 | assert len(unit._all_rules) == 0 83 | except (KeyError, ValueError): 84 | pytest.fail("Unexpected error, 'declare' command didn't work.") 85 | 86 | captured = capsys.readouterr() 87 | assert "Slot 'machin' was successfully declared." in captured.out 88 | 89 | print("AAAAAAAAAAAA") 90 | new_facade() 91 | cmd = DeclareCommand('declare intent "machin"') 92 | cmd.execute() 93 | try: 94 | unit = AST.get_or_create()[UnitType.intent]["machin"] 95 | assert len(unit._variation_rules) == 0 96 | assert len(unit._all_rules) == 0 97 | except (KeyError, ValueError): 98 | pytest.fail("Unexpected error, 'declare' command didn't work.") 99 | 100 | captured = capsys.readouterr() 101 | assert "Intent 'machin' was successfully declared." in captured.out 102 | 103 | 104 | def test_abstract_methods(): 105 | cmd = DeclareCommand('declare alias "a"') 106 | with pytest.raises(NotImplementedError): 107 | cmd.execute_on_unit(None, None, None) 108 | with pytest.raises(NotImplementedError): 109 | cmd.finish_execution() 110 | 111 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_delete_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functionalities present in module 4 | `chatette.cli.interactive_commands.delete_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from test_command_strategy import new_facade 10 | 11 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 12 | from chatette.cli.interactive_commands.delete_command import DeleteCommand 13 | from chatette.utils import UnitType 14 | 15 | from chatette.units.ast import AST 16 | 17 | 18 | def test_obj(): 19 | cmd = DeleteCommand("") 20 | assert cmd.command_tokens == [] 21 | assert isinstance(cmd, CommandStrategy) 22 | 23 | 24 | def test_execute(capsys): 25 | new_facade() 26 | 27 | cmd = DeleteCommand('delete alias "inexistant"') 28 | cmd.execute() 29 | captured = capsys.readouterr() 30 | assert "Alias 'inexistant' was not defined." in captured.out 31 | 32 | cmd = DeleteCommand('delete alias "tell me"') 33 | new_facade() 34 | try: 35 | AST.get_or_create()[UnitType.alias]["tell me"] 36 | except KeyError: 37 | pytest.fail("Unexpected KeyError. Alias 'tell me' doesn't exist " + \ 38 | "in the parser.") 39 | cmd.execute() 40 | with pytest.raises(KeyError): 41 | AST.get_or_create()[UnitType.alias]["tell me"] 42 | captured = capsys.readouterr() 43 | assert "Alias 'tell me' was successfully deleted." in captured.out 44 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_exist_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module 3 | Tests the functionalities present in module 4 | `chatette.cli.interactive_commands.exist_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 10 | from chatette.cli.interactive_commands.exist_command import ExistCommand 11 | 12 | from test_command_strategy import get_facade 13 | 14 | 15 | def test_obj(): 16 | cmd = ExistCommand("") 17 | assert isinstance(cmd, CommandStrategy) 18 | assert cmd.command_tokens == [] 19 | 20 | cmd = ExistCommand("exist ~ /a/") 21 | assert isinstance(cmd, CommandStrategy) 22 | assert cmd.command_tokens == ["exist", "~", "/a/"] 23 | 24 | 25 | def test_err(capsys): 26 | cmd = ExistCommand("nothing") 27 | assert cmd.command_tokens == ["nothing"] 28 | cmd.execute() 29 | captured = capsys.readouterr() 30 | assert "[ERROR]\tMissing some arguments\n\tUsage: " + \ 31 | 'exist ""' in captured.out 32 | 33 | 34 | def test_execute(capsys): 35 | cmd = ExistCommand('exist alias "sorry"') 36 | assert cmd.command_tokens == ["exist", "alias", '"sorry"'] 37 | get_facade() 38 | cmd.execute() 39 | captured = capsys.readouterr() 40 | assert "alias 'sorry'\nNo modifiers\n0 variation(s)" in captured.out 41 | 42 | cmd = ExistCommand('exist ~ /o/g') 43 | assert cmd.command_tokens == ["exist", "~", "/o/g"] 44 | cmd.execute() 45 | captured = capsys.readouterr() 46 | assert "alias 'can you'\nNo modifiers\n0 variation(s)" in captured.out 47 | assert "alias 'sorry'\nNo modifiers\n0 variation(s)" in captured.out 48 | 49 | cmd = ExistCommand('exist slot "INEXISTANT"') 50 | assert cmd.command_tokens == ["exist", "slot", '"INEXISTANT"'] 51 | cmd.execute() 52 | captured = capsys.readouterr() 53 | assert "Slot 'INEXISTANT' is not defined." in captured.out 54 | 55 | def test_variations(capsys): 56 | cmd = ExistCommand('exist alias "var#one"') 57 | assert cmd.command_tokens == ["exist", "alias", '"var#one"'] 58 | get_facade() 59 | cmd.execute() 60 | captured = capsys.readouterr() 61 | assert "alias 'var'\nNo modifiers\n2 variation(s):" in captured.out 62 | assert "\t- one\n" in captured.out 63 | assert "\t- two with space\n" in captured.out 64 | assert "Variation 'one' is defined for this alias." in captured.out 65 | 66 | cmd = ExistCommand('exist ~ "var#two with space"') 67 | assert cmd.command_tokens == ["exist", "~", '"var#two with space"'] 68 | get_facade() 69 | cmd.execute() 70 | captured = capsys.readouterr() 71 | assert "alias 'var'\nNo modifiers\n2 variation(s):" in captured.out 72 | assert "\t- two with space\n" in captured.out 73 | assert "\t- one\n" in captured.out 74 | assert "Variation 'two with space' is defined for this alias." in captured.out 75 | 76 | cmd = ExistCommand('exist alias "var#no var"') 77 | assert cmd.command_tokens == ["exist", "alias", '"var#no var"'] 78 | get_facade() 79 | cmd.execute() 80 | captured = capsys.readouterr() 81 | assert "alias 'var'\nNo modifiers\n2 variation(s):" in captured.out 82 | assert "Variation 'no var' is not defined for this alias." in captured.out 83 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_exit_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functionalities in module 4 | `chatette.cli.interactive_commands.exit_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from chatette.cli.interactive_commands.exit_command import * 10 | 11 | 12 | def test_obj(): 13 | cmd = ExitCommand('this is "a test"') 14 | assert isinstance(cmd, CommandStrategy) 15 | assert cmd.command_tokens == ["this", "is", '"a test"'] 16 | 17 | cmd = ExitCommand("exit > redirection") 18 | assert cmd.command_tokens == ["exit"] 19 | assert cmd.print_wrapper.redirection_file_path == "redirection" 20 | 21 | def test_execute(capsys): 22 | cmd = ExitCommand("") 23 | cmd.execute() 24 | captured = capsys.readouterr() 25 | assert captured.out == "" 26 | 27 | def test_should_exit(): 28 | cmd = ExitCommand("") 29 | assert cmd.should_exit() 30 | 31 | 32 | def test_abstract_methods(): 33 | cmd = ExitCommand('exit') 34 | with pytest.raises(NotImplementedError): 35 | cmd.execute_on_unit(None, None, None) 36 | with pytest.raises(NotImplementedError): 37 | cmd.finish_execution() 38 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_parse_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functionalities present in module 4 | `chatette.cli.interactive_commands.parse_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 10 | from chatette.cli.interactive_commands.parse_command import ParseCommand 11 | 12 | from test_command_strategy import new_facade 13 | 14 | 15 | def test_obj(): 16 | cmd = ParseCommand("") 17 | assert isinstance(cmd, CommandStrategy) 18 | assert cmd.command_tokens == [] 19 | 20 | cmd = ParseCommand("parse file") 21 | assert isinstance(cmd, CommandStrategy) 22 | assert cmd.command_tokens == ["parse", "file"] 23 | 24 | 25 | def test_err(capsys): 26 | new_facade() 27 | 28 | cmd = ParseCommand("error") 29 | assert cmd.command_tokens == ["error"] 30 | cmd.execute() 31 | captured = capsys.readouterr() 32 | assert "[ERROR]\tMissing template file path\n" + \ 33 | "\tUsage: 'parse '" in captured.out 34 | 35 | 36 | def test_execute(capsys): 37 | cmd = \ 38 | ParseCommand( 39 | "parse tests/unit-testing/cli/interactive_commands/other.chatette" 40 | ) 41 | assert cmd.command_tokens == [ 42 | "parse", "tests/unit-testing/cli/interactive_commands/other.chatette" 43 | ] 44 | cmd.execute() 45 | captured = capsys.readouterr() 46 | assert "tests/unit-testing/cli/interactive_commands/other.chatette" in captured.out 47 | 48 | 49 | def test_abstract_methods(): 50 | cmd = ParseCommand('exit') 51 | with pytest.raises(NotImplementedError): 52 | cmd.execute_on_unit(None, None) 53 | with pytest.raises(NotImplementedError): 54 | cmd.finish_execution() 55 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_rename_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functionalities in module 4 | `chatette.cli.interactive_commands.rename_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from test_command_strategy import new_facade, get_facade 10 | 11 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 12 | from chatette.cli.interactive_commands.rename_command import RenameCommand 13 | from chatette.utils import UnitType 14 | from chatette.units.ast import AST 15 | 16 | 17 | def test_obj(): 18 | cmd = RenameCommand("") 19 | assert cmd.command_tokens == [] 20 | assert isinstance(cmd, CommandStrategy) 21 | 22 | cmd = RenameCommand('rename ~ "old" "new"') 23 | assert cmd.command_tokens == ["rename", "~", '"old"', '"new"'] 24 | assert isinstance(cmd, CommandStrategy) 25 | 26 | 27 | def test_err(capsys): 28 | get_facade() 29 | cmd = RenameCommand('rename NOTHING "a" "b"') 30 | assert cmd.command_tokens == ["rename", "NOTHING", '"a"', '"b"'] 31 | cmd.execute() 32 | captured = capsys.readouterr() 33 | assert "[ERROR]\tUnknown unit type: 'NOTHING'." in captured.out 34 | 35 | cmd = RenameCommand("a") 36 | cmd.execute() 37 | captured = capsys.readouterr() 38 | assert "[ERROR]\tMissing some arguments\n\tUsage: " + \ 39 | 'rename "" ""' in captured.out 40 | 41 | cmd = RenameCommand('rename alias "a" "b"') 42 | cmd.execute() 43 | captured = capsys.readouterr() 44 | assert "[ERROR]\tCouldn't find a unit named 'a'." in captured.out 45 | 46 | cmd = RenameCommand('rename alias "can you" ""') 47 | cmd.execute() 48 | captured = capsys.readouterr() 49 | assert "[ERROR]\tAn empty name is not a valid alias name." in captured.out 50 | 51 | cmd = RenameCommand('rename alias "can you" "tell me"') 52 | cmd.execute() 53 | captured = capsys.readouterr() 54 | assert "[ERROR]\tAlias 'tell me' is already in use." in captured.out 55 | 56 | 57 | def test_execute(): 58 | cmd = RenameCommand('rename alias "can you" "could you"') 59 | assert cmd.command_tokens == ["rename", "alias", '"can you"', '"could you"'] 60 | new_facade() 61 | cmd.execute() 62 | with pytest.raises(KeyError): 63 | AST.get_or_create()[UnitType.alias]["can you"] 64 | try: 65 | AST.get_or_create()[UnitType.alias]["could you"] 66 | except KeyError: 67 | pytest.fail("Unexpected KeyError exception. Renaming didn't properly work.") 68 | 69 | cmd = RenameCommand('rename ~ "tell me" "a"') 70 | assert cmd.command_tokens == ["rename", "~", '"tell me"', '"a"'] 71 | new_facade() 72 | cmd.execute() 73 | with pytest.raises(KeyError): 74 | AST.get_or_create()[UnitType.alias]["tell me"] 75 | try: 76 | AST.get_or_create()[UnitType.alias]["a"] 77 | except KeyError: 78 | pytest.fail("Unexpected KeyError exception. Renaming didn't properly work.") 79 | 80 | 81 | def test_abstract_methods(): 82 | cmd = RenameCommand('rename alias "a" "b"') 83 | with pytest.raises(NotImplementedError): 84 | cmd.execute_on_unit(None, None, None) 85 | with pytest.raises(NotImplementedError): 86 | cmd.finish_execution() 87 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_show_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module 3 | Tests the functionalities contained in module 4 | `chatette.cli.interactive_commands.show_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from test_command_strategy import new_facade 10 | 11 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 12 | from chatette.cli.interactive_commands.show_command import ShowCommand 13 | 14 | 15 | def test_obj(): 16 | cmd = ShowCommand("") 17 | assert isinstance(cmd, CommandStrategy) 18 | assert cmd.command_tokens == [] 19 | 20 | cmd = ShowCommand("show ~ /a/") 21 | assert isinstance(cmd, CommandStrategy) 22 | assert cmd.command_tokens == ["show", "~", "/a/"] 23 | 24 | 25 | def test_err(capsys): 26 | cmd = ShowCommand("nothing") 27 | assert cmd.command_tokens == ["nothing"] 28 | cmd.execute() 29 | captured = capsys.readouterr() 30 | assert "[ERROR]\tMissing some arguments\n\tUsage: " + \ 31 | 'show ""' in captured.out 32 | 33 | 34 | def test_execute(capsys): 35 | new_facade() 36 | cmd = ShowCommand('show alias "sorry"') 37 | assert cmd.command_tokens == ["show", "alias", '"sorry"'] 38 | cmd.execute() 39 | captured = capsys.readouterr() 40 | assert \ 41 | "alias 'sorry'\nNo modifiers\n0 variation(s)" + \ 42 | "\nTemplate rules:\n~[sorry]\n sorry\n excuse me\n" in captured.out 43 | 44 | cmd = ShowCommand('show ~ /o/g') 45 | assert cmd.command_tokens == ["show", "~", "/o/g"] 46 | cmd.execute() 47 | captured = capsys.readouterr() 48 | assert "alias 'can you'\nNo modifiers\n0 variation(s)" in captured.out 49 | assert \ 50 | "Template rules:\n~[lots of rules]\n\trule 0\n\trule 1\n\trule 2" + \ 51 | "\n\trule 3\n\trule 4\n\trule 5\n\trule 6\n\trule 7\n\trule 8" + \ 52 | "\n\trule 9\n\trule 10\n\trule 11\n\trule 12" + \ 53 | "\n\trule 13\n" in captured.out 54 | assert \ 55 | "alias 'sorry'\nNo modifiers\n0 variation(s)" + \ 56 | "\nTemplate rules:\n~[sorry]\n sorry\n excuse me\n" in captured.out 57 | 58 | cmd = ShowCommand('show slot "INEXISTANT"') 59 | assert cmd.command_tokens == ["show", "slot", '"INEXISTANT"'] 60 | cmd.execute() 61 | captured = capsys.readouterr() 62 | assert "Slot 'INEXISTANT' is not defined." in captured.out 63 | 64 | cmd = ShowCommand('show ~ "lots of rules"') 65 | assert cmd.command_tokens == ["show", "~", '"lots of rules"'] 66 | cmd.execute() 67 | captured = capsys.readouterr() 68 | assert "alias 'lots of rules'\nNo modifiers\n0 variation(s)" in captured.out 69 | assert "rule 0" in captured.out 70 | assert "rule 11" in captured.out 71 | assert "rule 12" in captured.out 72 | 73 | # def test_variation(capsys): 74 | # new_facade() 75 | # cmd = ShowCommand('show alias "var#one"') 76 | # assert cmd.command_tokens == ["show", "alias", '"var#one"'] 77 | # cmd.execute() 78 | # captured = capsys.readouterr() 79 | # assert "alias 'var'\nNo modifiers\n2 variation(s):" in captured.out 80 | # assert "\t- one\n" in captured.out 81 | # assert "\t- two with space\n" in captured.out 82 | # assert "Rules for variation 'one':\n\tone" in captured.out 83 | 84 | # cmd = ShowCommand('show alias "var#two with space"') 85 | # assert cmd.command_tokens == ["show", "alias", '"var#two with space"'] 86 | # cmd.execute() 87 | # captured = capsys.readouterr() 88 | # assert "alias: 'var'\nmodifiers:\n\tNone\n2 variations:\n" in captured.out 89 | # assert "\t- two with space\n" in captured.out 90 | # assert "\t- one\n" in captured.out 91 | # assert "Rules for variation 'two with space':\n\ttwo\n\t2" in captured.out 92 | 93 | # cmd = ShowCommand('show alias "var#no var"') 94 | # assert cmd.command_tokens == ["show", "alias", '"var#no var"'] 95 | # cmd.execute() 96 | # captured = capsys.readouterr() 97 | # assert "alias: 'var'\nmodifiers:\n\tNone\n2 variations:\n" in captured.out 98 | # assert "[ERROR]\tVariation 'no var' is not defined in alias var." in captured.out 99 | 100 | 101 | def test_abstract_methods(): 102 | cmd = ShowCommand('show ~ "test"') 103 | try: 104 | cmd.finish_execution() 105 | except NotImplementedError: 106 | pytest.fail( 107 | "Method 'finish_execution' shouldn't have raised " + \ 108 | "a 'NotImplementedError'." 109 | ) 110 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/test_stats_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module 3 | Tests the functionalities that are contained in module 4 | `chatette.cli.interactive_commands.stats_command`. 5 | """ 6 | 7 | import pytest 8 | 9 | from test_command_strategy import new_facade 10 | 11 | from chatette.cli.interactive_commands.command_strategy import CommandStrategy 12 | from chatette.cli.interactive_commands.stats_command import StatsCommand 13 | 14 | 15 | def test_obj(): 16 | new_facade() 17 | cmd = StatsCommand("") 18 | assert isinstance(cmd, CommandStrategy) 19 | assert cmd.command_tokens == [] 20 | 21 | cmd = StatsCommand("stats") 22 | assert isinstance(cmd, CommandStrategy) 23 | assert cmd.command_tokens == ["stats"] 24 | 25 | 26 | def test_execute(capsys): 27 | new_facade() 28 | cmd = StatsCommand('stats') 29 | assert cmd.command_tokens == ["stats"] 30 | cmd.execute() 31 | captured = capsys.readouterr() 32 | assert "Statistics:" in captured.out 33 | assert "Parsed files: 1" in captured.out 34 | assert "Declared units: 7 (9 variations)" in captured.out 35 | assert "Declared slots: 1 (2 variations)" in captured.out 36 | assert "Declared aliases: 5 (6 variations)" in captured.out 37 | assert "Parsed rules: 25" in captured.out 38 | 39 | 40 | def test_abstract_methods(): 41 | new_facade() 42 | cmd = StatsCommand('stats') 43 | with pytest.raises(NotImplementedError): 44 | cmd.execute_on_unit(None, None) 45 | with pytest.raises(NotImplementedError): 46 | cmd.finish_execution() 47 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/interactive_commands/toilets.chatette: -------------------------------------------------------------------------------- 1 | // This template defines different ways to ask for the location of toilets 2 | %[&ask_toilet](3) 3 | ~[sorry?] ~[tell me] where the [@[toilet#singular] is|@[toilet#plural] are] [please?]? 4 | 5 | ~[sorry] 6 | sorry 7 | excuse me 8 | 9 | ~[tell me] 10 | ~[can you?] [tell|show] me 11 | ~[can you] 12 | [can|could|would] you 13 | 14 | @[toilet#singular] 15 | toilet 16 | loo 17 | @[toilet#plural] 18 | toilets 19 | 20 | ~[lots of rules] 21 | rule 0 22 | rule 1 23 | rule 2 24 | rule 3 25 | rule 4 26 | rule 5 27 | rule 6 28 | rule 7 29 | rule 8 30 | rule 9 31 | rule 10 32 | rule 11 33 | rule 12 34 | rule 13 35 | 36 | ~[var#one] 37 | one 38 | ~[var#two with space] 39 | two 40 | 2 41 | 42 | -------------------------------------------------------------------------------- /tests/unit-testing/cli/test_terminal_writer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functions and methods in module `chatette.cli.terminal_writer`. 4 | """ 5 | 6 | from chatette.cli.terminal_writer import RedirectionType, TerminalWriter 7 | 8 | 9 | class TestInitTerminalWriter(object): 10 | def test(self): 11 | obj = TerminalWriter() 12 | assert obj.redirection_file_path is None 13 | assert obj.buffered_text is None 14 | assert obj._file_mode == 'a+' 15 | 16 | obj = TerminalWriter(RedirectionType.append) 17 | assert obj.redirection_file_path is None 18 | assert obj.buffered_text is None 19 | assert obj._file_mode == 'a+' 20 | 21 | obj = TerminalWriter(RedirectionType.truncate) 22 | assert obj.redirection_file_path is None 23 | assert obj.buffered_text is None 24 | assert obj._file_mode == 'w+' 25 | 26 | obj = TerminalWriter(RedirectionType.quiet) 27 | assert obj.redirection_file_path is None 28 | assert obj.buffered_text is None 29 | assert obj._file_mode == 'quiet' 30 | 31 | obj = TerminalWriter(None) 32 | assert obj.redirection_file_path is None 33 | assert obj.buffered_text is None 34 | assert obj._file_mode is None 35 | 36 | obj = TerminalWriter(redirection_file_path="test") 37 | assert obj.redirection_file_path == "test" 38 | assert obj.buffered_text is None 39 | assert obj._file_mode == 'a+' 40 | 41 | 42 | class TestReset(object): 43 | def test(self): 44 | obj = TerminalWriter(redirection_file_path="test") 45 | obj.reset() 46 | assert obj.redirection_file_path is None 47 | assert obj.buffered_text is None 48 | 49 | 50 | class TestGetRedirection(object): 51 | def test(self): 52 | obj = TerminalWriter() 53 | assert obj.get_redirection() == (RedirectionType.append, None) 54 | 55 | obj = TerminalWriter(RedirectionType.append) 56 | assert obj.get_redirection() == (RedirectionType.append, None) 57 | 58 | obj = TerminalWriter(RedirectionType.truncate) 59 | assert obj.get_redirection() == (RedirectionType.truncate, None) 60 | 61 | obj = TerminalWriter(RedirectionType.quiet) 62 | assert obj.get_redirection() == (RedirectionType.quiet, None) 63 | 64 | obj = TerminalWriter(None) 65 | assert obj.get_redirection() is None 66 | 67 | obj = TerminalWriter(redirection_file_path="test") 68 | assert obj.get_redirection() == (RedirectionType.append, "test") 69 | 70 | def test_incorrect_state(self): 71 | obj = TerminalWriter() 72 | obj._file_mode = "incorrect" 73 | assert obj.get_redirection() is None 74 | 75 | 76 | class TestWrite(object): 77 | def test_print(self, capsys): 78 | obj = TerminalWriter(None) 79 | obj.write("this is a test") 80 | captured = capsys.readouterr() 81 | assert captured.out == "this is a test\n" 82 | assert obj.buffered_text is None 83 | 84 | def test_quiet(self): 85 | obj = TerminalWriter(RedirectionType.quiet) 86 | obj.write("something") 87 | assert obj.buffered_text is None 88 | 89 | def test_not_quiet(self): 90 | obj = TerminalWriter(RedirectionType.append) 91 | obj.write("something") 92 | assert obj.buffered_text == "something" 93 | obj.write("other line") 94 | assert obj.buffered_text == "something\nother line" 95 | 96 | 97 | class TestErrorLog(object): 98 | def test_print(self, capsys): 99 | obj = TerminalWriter(None) 100 | obj.error_log("this is a test") 101 | captured = capsys.readouterr() 102 | assert captured.out == "[ERROR]\tthis is a test\n" 103 | assert obj.buffered_text is None 104 | 105 | def test_quiet(self): 106 | obj = TerminalWriter(RedirectionType.quiet) 107 | obj.error_log("something") 108 | assert obj.buffered_text is None 109 | 110 | def test_not_quiet(self): 111 | obj = TerminalWriter(RedirectionType.append) 112 | obj.error_log("something") 113 | assert obj.buffered_text == "[ERROR]\tsomething" 114 | obj.error_log("other line") 115 | assert obj.buffered_text == "[ERROR]\tsomething\n[ERROR]\tother line" 116 | 117 | 118 | class TestFlush(object): 119 | pass # TODO 120 | -------------------------------------------------------------------------------- /tests/unit-testing/modifiers/test_argument.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities that are present in module 5 | `chatette.modifiers.argument` 6 | """ 7 | 8 | from copy import deepcopy as clone 9 | 10 | from chatette.modifiers.argument import * 11 | from chatette.units import Example 12 | 13 | 14 | class TestModifyNbPossibilities(object): 15 | def test_modify_nb_possibitilies(self): 16 | for i in range(10): 17 | assert i == modify_nb_possibilities(i) 18 | 19 | 20 | class TestModifyExample(object): 21 | def test_no_mapping(self): 22 | example = Example() 23 | assert modify_example(clone(example), dict()) == example 24 | 25 | example = Example("test") 26 | assert modify_example(clone(example), dict()) == example 27 | 28 | example = Example("test with $ARG") 29 | assert modify_example(clone(example), dict()) == example 30 | 31 | def test_no_replacement(self): 32 | mapping = {"argument": "replaced"} 33 | 34 | example = Example() 35 | assert modify_example(clone(example), mapping) == example 36 | 37 | example = Example("test") 38 | assert modify_example(clone(example), mapping) == example 39 | 40 | example = Example("test with $ARG") 41 | assert modify_example(clone(example), mapping) == example 42 | 43 | def test_replacement(self): 44 | mapping = {"test": "TEST", "replace": "argument"} 45 | 46 | example = Example("replace $test by uppercase") 47 | modify_example(example, mapping) 48 | assert example.text == "replace TEST by uppercase" 49 | 50 | example = Example("is this $replace?") 51 | modify_example(example, mapping) 52 | assert example.text == "is this argument?" 53 | 54 | example = Example("The $replace is $test") 55 | modify_example(example, mapping) 56 | assert example.text == "The argument is TEST" 57 | 58 | 59 | class TestMakeAllPossibilities(object): 60 | def test_no_mapping(self): 61 | examples = [Example(), Example("test1"), Example("$ARG")] 62 | 63 | for (original, modified) in zip(examples, make_all_possibilities(clone(examples), dict())): 64 | assert original == modified 65 | 66 | def test_no_replacement(self): 67 | mapping = {"argument": "replaced"} 68 | examples = [Example(), Example("test1"), Example("$ARG")] 69 | 70 | for (original, modified) in zip(examples, make_all_possibilities(clone(examples), mapping)): 71 | assert original == modified 72 | -------------------------------------------------------------------------------- /tests/unit-testing/modifiers/test_casegen.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities that are present in module 5 | `chatette.modifiers.casegen` 6 | """ 7 | 8 | from chatette.modifiers.casegen import * 9 | from chatette.units import Example 10 | 11 | 12 | class TestModifyNbPossibilities(object): 13 | def test_modify_nb_possibilities(self): 14 | assert modify_nb_possibilities(0) == 0 15 | assert modify_nb_possibilities(1) == 2 16 | assert modify_nb_possibilities(100) == 200 17 | 18 | 19 | class TestModifyExample(object): 20 | def test_modify_example(self): 21 | example = Example("text") 22 | for _ in range(5): 23 | modify_example(example) 24 | assert example.text in ("text", "Text") 25 | 26 | example = Example(" \talinea") 27 | for _ in range(5): 28 | modify_example(example) 29 | assert example.text in (" \talinea", " \tAlinea") 30 | 31 | 32 | class TestMakeAllPossibilities(object): 33 | def test_make_all_pssibilities(self): 34 | examples = [Example("test"), Example(" alinea")] 35 | for ex in make_all_possibilities(examples): 36 | assert ex.text in ("test", "Test", " alinea", " Alinea") 37 | 38 | 39 | class TestMayChangeLeadingCase(object): 40 | def test_empty(self): 41 | assert not may_change_leading_case("") 42 | 43 | def test_other(self): 44 | assert not may_change_leading_case(" ") 45 | assert not may_change_leading_case("\t ") 46 | assert not may_change_leading_case("123") 47 | assert may_change_leading_case("test") 48 | assert may_change_leading_case("\ttest") 49 | assert may_change_leading_case("TEST") 50 | 51 | 52 | class TestWithLeadingUpper(object): 53 | def test_empty(self): 54 | example = Example() 55 | example = with_leading_upper(example) 56 | assert example.text == "" 57 | 58 | def test_other(self): 59 | example = Example("test") 60 | example = with_leading_upper(example) 61 | assert example.text == "Test" 62 | 63 | example = Example(" \talinea") 64 | example = with_leading_upper(example) 65 | assert example.text == " \tAlinea" 66 | 67 | 68 | class TestWithLeadingLower(object): 69 | def test_empty(self): 70 | example = Example() 71 | example = with_leading_lower(example) 72 | assert example.text == "" 73 | 74 | def test_other(self): 75 | example = Example("test") 76 | example = with_leading_lower(example) 77 | assert example.text == "test" 78 | 79 | example = Example(" \talinea") 80 | example = with_leading_lower(example) 81 | assert example.text == " \talinea" 82 | -------------------------------------------------------------------------------- /tests/unit-testing/modifiers/test_representation.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Test module. 4 | Tests the functionalities that are present in module 5 | `chatette.modifiers.representation` 6 | """ 7 | 8 | 9 | from chatette.modifiers.representation import \ 10 | ModifiersRepresentation, RandgenRepresentation 11 | 12 | 13 | class TestModifiersRepr(object): 14 | def test_default(self): 15 | representation = ModifiersRepresentation() 16 | assert not representation.casegen 17 | assert representation.variation_name is None 18 | assert not representation.randgen 19 | assert representation.argument_name is None 20 | assert representation.argument_value is None 21 | 22 | def test_str(self): 23 | representation = ModifiersRepresentation() 24 | assert \ 25 | str(representation) == \ 26 | "ModifiersRepresentation(casegen: False randgen: No " + \ 27 | "arg name: None arg value: None)" 28 | 29 | def test_short_desc(self): 30 | representation = ModifiersRepresentation() 31 | assert representation.short_description() == "No modifiers\n" 32 | 33 | representation.casegen = True 34 | assert representation.short_description() == "Modifiers:\n- case generation\n" 35 | 36 | representation.casegen = False 37 | representation.randgen = RandgenRepresentation() 38 | representation.randgen._present = True 39 | representation.randgen.name = "test" 40 | assert \ 41 | representation.short_description() == \ 42 | "Modifiers:\n- random generation: test (50%)\n" 43 | 44 | representation.randgen._present = False 45 | representation.argument_name = "test" 46 | assert \ 47 | representation.short_description() == \ 48 | "Modifiers:\n- argument name: test\n" 49 | 50 | representation.argument_name = None 51 | representation.argument_value = "test" 52 | assert \ 53 | representation.short_description() == "Modifiers:\n- argument value: test\n" 54 | 55 | 56 | class TestRandgenRepr(object): 57 | def test_default(self): 58 | representation = RandgenRepresentation() 59 | assert not representation 60 | assert not representation._present 61 | assert representation.name is None 62 | assert not representation.opposite 63 | assert representation.percentage == 50 64 | 65 | def test_bool(self): 66 | representation = RandgenRepresentation() 67 | assert not representation 68 | representation._present = True 69 | assert bool(representation) 70 | 71 | def test_str(self): 72 | representation = RandgenRepresentation() 73 | assert str(representation) == "No" 74 | 75 | representation._present = True 76 | representation.name = "name" 77 | assert str(representation) == "Yes 'name' (50%)" 78 | 79 | representation.opposite = True 80 | assert str(representation) == "Yes 'name' (opposite, 50%)" 81 | -------------------------------------------------------------------------------- /tests/unit-testing/prechecks.py/test_pyversion.py: -------------------------------------------------------------------------------- 1 | # TODO find a way to mock python's version in sys module 2 | -------------------------------------------------------------------------------- /tests/unit-testing/test_deprecations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test module. 3 | Tests the functions in module 'chatette.utils'. 4 | """ 5 | 6 | from chatette.deprecations import Deprecations 7 | 8 | 9 | class TestDeprecations(object): 10 | def test_new(self): 11 | instance = Deprecations() 12 | same = Deprecations.get_or_create() 13 | assert instance == same 14 | assert not instance._old_comment_warned 15 | assert not instance._old_choice_warned 16 | 17 | def test_warn_old_comment(self, capsys): 18 | instance = Deprecations.reset_instance() 19 | assert not instance._old_comment_warned 20 | assert not instance._old_choice_warned 21 | 22 | instance.warn_old_comment() 23 | assert instance._old_comment_warned 24 | assert not instance._old_choice_warned 25 | 26 | captured = capsys.readouterr() 27 | assert "Comments starting with a semi-colon" in captured.err 28 | 29 | instance = Deprecations.reset_instance() 30 | instance.warn_old_comment("filename") 31 | 32 | captured = capsys.readouterr() 33 | assert "This syntax was found in file 'filename'." in captured.err 34 | 35 | instance = Deprecations.reset_instance() 36 | instance.warn_old_comment("filename", 12, "line") 37 | 38 | captured = capsys.readouterr() 39 | assert \ 40 | "This syntax was found in file 'filename'" + \ 41 | " at line 12: 'line'." in captured.err 42 | 43 | instance = Deprecations.reset_instance() 44 | instance.warn_old_comment(line_nb=12, line="line") 45 | 46 | captured = capsys.readouterr() 47 | assert \ 48 | "This syntax was found" + \ 49 | " at line 12: 'line'." in captured.err 50 | 51 | def test_warn_old_choice(self, capsys): 52 | instance = Deprecations.reset_instance() 53 | assert not instance._old_comment_warned 54 | assert not instance._old_choice_warned 55 | 56 | instance.warn_old_choice() 57 | assert not instance._old_comment_warned 58 | assert instance._old_choice_warned 59 | 60 | captured = capsys.readouterr() 61 | assert "Choices starting with " in captured.err 62 | 63 | instance = Deprecations.reset_instance() 64 | instance.warn_old_choice("filename") 65 | 66 | captured = capsys.readouterr() 67 | assert "This syntax was found in file 'filename'." in captured.err 68 | 69 | instance = Deprecations.reset_instance() 70 | instance.warn_old_choice("filename", 12, "line") 71 | 72 | captured = capsys.readouterr() 73 | assert \ 74 | "This syntax was found in file 'filename'" + \ 75 | " at line 12: 'line'." in captured.err 76 | 77 | instance = Deprecations.reset_instance() 78 | instance.warn_old_choice(line_nb=12, line="line") 79 | 80 | captured = capsys.readouterr() 81 | assert \ 82 | "This syntax was found" + \ 83 | " at line 12: 'line'." in captured.err 84 | 85 | -------------------------------------------------------------------------------- /tests/unit-testing/test_log.py: -------------------------------------------------------------------------------- 1 | # codin: utf-8 2 | """ 3 | Test module. 4 | Tests the functions in module `chatette.log`. 5 | """ 6 | 7 | from chatette import log 8 | 9 | 10 | class TestPrints(object): 11 | def test_existences(self): 12 | assert "print_DBG" in dir(log) 13 | assert "print_warn" in dir(log) 14 | 15 | def test_no_return(self): 16 | assert log.print_DBG("Test") is None 17 | assert log.print_warn("Test") is None 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py35 3 | 4 | [testenv:pytest] 5 | deps = 6 | -r{toxinidir}/requirements/test.txt 7 | pytest 8 | pytest-cov 9 | commands = pytest {posargs} --cov=chatette 10 | 11 | [testenv:pycodestyle] 12 | deps = 13 | -r{toxinidir}/requirements/test.txt 14 | pycodestyle 15 | max-line-length = 120 16 | exclude = .git,tox,.tox,__pycache__,venv*,.venv*,.eggs 17 | 18 | [testenv:pylint] 19 | deps = 20 | -r{toxinidir}/requirements/test.txt 21 | pylint 22 | commands = pylint --rcfile={toxinidir}/.pylintrc chatette 23 | 24 | [testenv:codecov] 25 | deps = 26 | -r{toxinidir}/requirements/test.txt 27 | codecov>=1.4.0 28 | commands = codecov -e TOXENV 29 | passenv = TOXENV CI TRAVIS TRAVIS_* 30 | --------------------------------------------------------------------------------