├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pics
├── bulk_edit.png
├── bulk_edit_filled.png
├── intent_ui.png
└── utterances.png
├── requirements.txt
├── setup.py
├── test
├── test_files
│ ├── alexa_test.csv
│ ├── csv_test.csv
│ ├── file_override_test.txt
│ └── txt_test.txt
└── test_utter_more.py
└── utter_more
├── __init__.py
└── utter_more.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Directories
2 | __pycache__
3 | .cache
4 | .pytest_cache
5 | dist
6 | build
7 | *.egg-info
8 | conda_uploading
9 |
10 | # Conda directory for executables/yaml
11 | utter-more
12 |
13 | # Files
14 | *.pyc
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.6"
4 | install:
5 | - pip install -r requirements.txt
6 | script:
7 | - pytest
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jacob Scott
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Utter More
2 | To customize Amazon's Alexa, you make what is called a skill. To do something in the skill, you make an intent. To run the intent, you make an utterance. When that utterance is uttered, the intent is run. Since language is complex, there may be many different ways to say the same thing and you may want Alexa to pick up on all of those ways. Furthermore, you may have many variables for the utterances (called intent slots). Being verbose enough to cover every case can be tedious, so this takes care of that.
3 |
4 | ## Installing Package
5 | Currently, this package is not PyPI, so just git clone the repo
6 |
7 | ## Creating Utterances
8 | Below are some examples to show its functionality.
9 | ### Formatting
10 | You can use the following in your templates:
11 | 1) OR statement `(a|b|c|...)` - Used if you want to allow multiple interchangeable words. For example, if `photo`, `picture` and `painting` are interchangeable in your utterances, then write `(photo|picture|painting)` in the place where it would be. The number of words to OR is arbitrary and single curly keywords like `{intent_slot}` can be used in this.
12 | 3) Conditional OR statement `(a*tag1|b) (c^tag1|d)` - Used if you want the appearance of a phrase in an OR statement to be dependent on another phrase. Here, `a` is the master and `c` is the follower; utterances with `c` will only appear if it also contains `a`. Another functionality of this is as follows. If you have `(It*s|They*p) (is^s|are^p) (close^s|far^p)`, `is` and `close` will only show if `It` also shows and, conversely, `are` and `far` will only show if `They` shows. This is how you can do a conditional AND with this function.
13 | 2) Optional Intent Slot `{{slot}}` - Used if the existence of an intent slot in your utterance is optional. For example, if you have an optional adverb you may write `I {adverb} love it` or just `I love it`. Instead you can write `I {{adverb}} love it` to capture both.
14 |
15 | ### Running the Code
16 | Now with the formatting down, lets create some templates for the utterances. Something like:
17 | ```
18 | "What (is*s|are*p) (that^s|those^p) {{descriptor}} (photo|picture)(^s|s^p) (of|from)"
19 | ```
20 | and
21 | ```
22 | "Download the (photo|picture) {{to_file_path}}"
23 | ```
24 | To do this, we run the following:
25 | ``` python
26 | from pprint import pprint
27 | from utter_more import UtterMore
28 |
29 | um = UtterMore("What (is*s|are*p) (that^s|those^p) {{descriptor}} (photo|picture)(^s|s^p) (of|from)",
30 | "Download the (photo|picture) {{to_file_path}}")
31 | um.iter_build_utterances()
32 |
33 | pprint(um.utterances)
34 | ```
35 | And this will display:
36 | ``` python
37 | [['What is that {descriptor} photo of',
38 | 'What is that {descriptor} photo from',
39 | 'What is that {descriptor} picture of',
40 | 'What is that {descriptor} picture from',
41 | 'What is that photo of',
42 | 'What is that photo from',
43 | 'What is that picture of',
44 | 'What is that picture from',
45 | 'What are those {descriptor} photos of',
46 | 'What are those {descriptor} photos from',
47 | 'What are those {descriptor} pictures of',
48 | 'What are those {descriptor} pictures from',
49 | 'What are those photos of',
50 | 'What are those photos from',
51 | 'What are those pictures of',
52 | 'What are those pictures from'],
53 | ['Download the photo {to_file_path}',
54 | 'Download the photo',
55 | 'Download the picture {to_file_path}',
56 | 'Download the picture']]
57 | ```
58 | Here we can easily follow the grammatical rules of plurality. If we want to save the utterances so that we can upload them to our Alexa skill, we simply do:
59 | ``` python
60 | um.save_for_alexa(PATH_TO_DIRECTORY, FILE_NAME)
61 | ```
62 | Here we will find the CSV file properly formatted for uploading.
63 |
64 | ## Uploading the Utterances
65 | 1) After going to the tab for the intended intent, click on "Bulk Edit" in the top right corner of the page.
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 2) Browse for or drag and drop the previously made CSV and it will populate the text field.
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 3) Press "Submit" and the utterances field will be filled.
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | And that's it, no need to manually type in potentially hundreds or thousands of annoyingly similar phrases.
93 |
94 | ## Other Features
95 | * You can add utterance templates after making the class like so:
96 | ``` python
97 | from utter_more import UtterMore
98 |
99 | um = UtterMore()
100 | um.add_utterance_template("What (is*s|are*p) (that^s|those^p) {{descriptor}} (photo|picture)(^s|s^p) (of|from)")
101 | um.add_utterance_template("Download the (photo|picture) {{to_file_path}}")
102 | um.iter_build_utterances()
103 | um.save_for_alexa(PATH_TO_DIRECTORY, FILE_NAME)
104 | ```
105 | This will produce the same CSV as above
106 | * Continuing with the above code, you can then save these utterances normally as either a regular CSV or a text file like so:
107 | ``` python
108 | # Saves as utterances.txt with new line separators
109 | um.save_utterances(PATH_TO_DIRECTORY, 'utterances', 'txt')
110 | # Saves as utterances.csv as actual comma-separated values
111 | um.save_utterances(PATH_TO_DIRECTORY, 'utterances', 'csv')
112 | ```
113 | * Utterances can be created from a template without adding it to the UtterMore object so
114 | ``` python
115 | from pprint import pprint
116 | from utter_more import UtterMore
117 |
118 | um = UtterMore()
119 | utterances = um.build_utterance("What (is*s|are*p) (that^s|those^p) {{descriptor}} (photo|picture)(^s|s^p) (of|from)")
120 | pprint(utterances)
121 | ```
122 | will output:
123 | ``` python
124 | [['What is that {descriptor} photo of',
125 | 'What is that {descriptor} photo from',
126 | 'What is that {descriptor} picture of',
127 | 'What is that {descriptor} picture from',
128 | 'What is that photo of',
129 | 'What is that photo from',
130 | 'What is that picture of',
131 | 'What is that picture from',
132 | 'What are those {descriptor} photos of',
133 | 'What are those {descriptor} photos from',
134 | 'What are those {descriptor} pictures of',
135 | 'What are those {descriptor} pictures from',
136 | 'What are those photos of',
137 | 'What are those photos from',
138 | 'What are those pictures of',
139 | 'What are those pictures from']]
140 | ```
141 |
--------------------------------------------------------------------------------
/pics/bulk_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crumpstrr33/Utter-More/840b637ac4da0533be22ca43bdb81e6fc98c80bc/pics/bulk_edit.png
--------------------------------------------------------------------------------
/pics/bulk_edit_filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crumpstrr33/Utter-More/840b637ac4da0533be22ca43bdb81e6fc98c80bc/pics/bulk_edit_filled.png
--------------------------------------------------------------------------------
/pics/intent_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crumpstrr33/Utter-More/840b637ac4da0533be22ca43bdb81e6fc98c80bc/pics/intent_ui.png
--------------------------------------------------------------------------------
/pics/utterances.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crumpstrr33/Utter-More/840b637ac4da0533be22ca43bdb81e6fc98c80bc/pics/utterances.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crumpstrr33/Utter-More/840b637ac4da0533be22ca43bdb81e6fc98c80bc/requirements.txt
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | from os import path
3 | import re
4 |
5 | with open('README.md', 'r') as f:
6 | long_description = f.read()
7 |
8 | with open(path.join('utter_more', '__init__.py'), 'r') as f:
9 | setup_file = f.read()
10 | version = re.findall(r'__version__ = \'(.*)\'', setup_file)[0]
11 | name = re.findall(r'__name__ = \'(.*)\'', setup_file)[0]
12 |
13 | setup(
14 | name=name,
15 | version=version,
16 | author='Jacob Scott',
17 | description='Creates utterances for Amazon\'s Alexa.',
18 | license='MIT',
19 | url='https://github.com/crumpstrr33/Utter-More',
20 | packages=find_packages(exclude=['test*']),
21 | python_requires='>=3',
22 | long_description=long_description,
23 | long_description_content_type='text/markdown',
24 | classifiers=[
25 | 'Intended Audience :: Developers',
26 | 'License :: OSI Approved :: MIT License',
27 | 'Programming Language :: Python :: 3 :: Only'
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/test/test_files/alexa_test.csv:
--------------------------------------------------------------------------------
1 | {beginning}{middle}{end}
2 | {beginning}{middle}
3 | {beginning}{end}
4 | {beginning}
5 | {middle}{end}
6 | {middle}
7 | {end}
8 |
9 | {beginning}{middle}{end}
10 | bemiden
11 | bemidd
12 | bedleen
13 | bedled
14 | ginmiden
15 | ginmidd
16 | gindleen
17 | gindled
18 | ningmiden
19 | ningmidd
20 | ningdleen
21 | ningdled
--------------------------------------------------------------------------------
/test/test_files/csv_test.csv:
--------------------------------------------------------------------------------
1 | {beginning}{middle}{end},{beginning}{middle},{beginning}{end},{beginning},{middle}{end},{middle},{end},,{beginning}{middle}{end},bemiden,bemidd,bedleen,bedled,ginmiden,ginmidd,gindleen,gindled,ningmiden,ningmidd,ningdleen,ningdled
2 |
--------------------------------------------------------------------------------
/test/test_files/file_override_test.txt:
--------------------------------------------------------------------------------
1 | {beginning}{middle}{end},{beginning}{middle},{beginning}{end},{beginning},{middle}{end},{middle},{end},,{beginning}{middle}{end},bemiden,bemidd,bedleen,bedled,ginmiden,ginmidd,gindleen,gindled,ningmiden,ningmidd,ningdleen,ningdled
2 |
--------------------------------------------------------------------------------
/test/test_files/txt_test.txt:
--------------------------------------------------------------------------------
1 | {beginning}{middle}{end}
2 | {beginning}{middle}
3 | {beginning}{end}
4 | {beginning}
5 | {middle}{end}
6 | {middle}
7 | {end}
8 |
9 | {beginning}{middle}{end}
10 | bemiden
11 | bemidd
12 | bedleen
13 | bedled
14 | ginmiden
15 | ginmidd
16 | gindleen
17 | gindled
18 | ningmiden
19 | ningmidd
20 | ningdleen
21 | ningdled
--------------------------------------------------------------------------------
/test/test_utter_more.py:
--------------------------------------------------------------------------------
1 | import filecmp
2 | from inspect import getsourcefile
3 | import os.path as path, sys
4 | cur_dir = path.dirname(path.abspath(getsourcefile(lambda: 0)))
5 | sys.path.insert(0, path.join(cur_dir[:cur_dir.rfind(path.sep)], 'utter_more'))
6 |
7 | import pytest
8 |
9 | from utter_more import UtterMore
10 | sys.path.pop(0)
11 |
12 |
13 | DOUBLE_CURLY = '{{beginning}}{{middle}}{{end}}'
14 | DC_ANS = ['{beginning}{middle}{end}', '{beginning}{middle}', '{beginning}{end}',
15 | '{beginning}', '{middle}{end}', '{middle}', '{end}', '']
16 |
17 | SINGLE_CURLY = '{beginning}{middle}{end}'
18 | SC_ANS = ['{beginning}{middle}{end}']
19 |
20 | OR_CURLY = '(be|gin|ning)(mid|dle)(en|d)'
21 | OC_ANS = ['bemiden', 'bemidd', 'bedleen', 'bedled',
22 | 'ginmiden', 'ginmidd', 'gindleen', 'gindled',
23 | 'ningmiden', 'ningmidd', 'ningdleen', 'ningdled']
24 |
25 | COND_OR_CURLY = '(be^1|gin^2|ning)(mid*1|dle*2)(en*1|d*2)'
26 | COC_ANS = ['bemiden', 'bemidd', 'bedleen', 'ginmidd', 'gindleen',
27 | 'gindled', 'ningmiden', 'ningmidd', 'ningdleen', 'ningdled']
28 |
29 | COND_AND_CURLY = '(be*1|gin*2|ning)(mid^1|dle^2)(en^1|d^2)'
30 | CAC_ANS = ['bemiden', 'gindled']
31 |
32 |
33 | @pytest.fixture()
34 | def local_um():
35 | # A fresh and empty UtterMore every time
36 | um = UtterMore()
37 | return um
38 | @pytest.fixture(scope='module')
39 | def global_um():
40 | # Retains the utterances added to it
41 | um = UtterMore()
42 | return um
43 |
44 |
45 | @pytest.mark.parametrize('template, utterances', [
46 | (DOUBLE_CURLY, DC_ANS),
47 | (SINGLE_CURLY, SC_ANS),
48 | (OR_CURLY, OC_ANS),
49 | (COND_OR_CURLY, COC_ANS),
50 | (COND_AND_CURLY, CAC_ANS)
51 | ])
52 | def test_build_utterances(local_um, template, utterances):
53 | """
54 | Test edge case utterance templates
55 | """
56 | assert local_um.build_utterances(template) == utterances
57 |
58 |
59 | def test_ibu_aut(global_um):
60 | """
61 | Test methods following UtterMore methods:
62 | - iter_build_utterances
63 | - add_utterance_template
64 | """
65 | global_um.add_utterance_template(DOUBLE_CURLY)
66 | global_um.add_utterance_template(SINGLE_CURLY)
67 | global_um.add_utterance_template(OR_CURLY)
68 | global_um.iter_build_utterances()
69 | assert global_um.utterances == [DC_ANS, SC_ANS, OC_ANS]
70 | global_um.utterance_templates.clear()
71 | global_um.utterances.clear()
72 |
73 |
74 | @pytest.mark.parametrize('fname, saved_as, written_as', [
75 | ('alexa_test', 'csv', None),
76 | ('csv_test', 'csv', None),
77 | ('txt_test', 'txt', None),
78 | ('file_override_test', 'txt', 'csv')
79 | ])
80 | def test_saving_utterances(global_um, tmpdir, fname, saved_as, written_as):
81 | """
82 | Test the saving methods of UtterMore
83 | """
84 | written_as = written_as or saved_as
85 | test_dir = path.join(path.dirname(path.realpath(__file__)), 'test_files')
86 |
87 | if fname == 'alexa_test':
88 | global_um.save_for_alexa(tmpdir, fname)
89 | else:
90 | global_um.save_utterances(tmpdir, fname, saved_as, written_as=written_as)
91 |
92 | file_name = fname + '.' + saved_as
93 | assert filecmp.cmp(path.join(test_dir, file_name),
94 | tmpdir.join(file_name))
95 |
--------------------------------------------------------------------------------
/utter_more/__init__.py:
--------------------------------------------------------------------------------
1 | from .utter_more import UtterMore
2 |
3 | __version__ = '1.0.1'
4 | __name__ = 'utter-more'
5 |
--------------------------------------------------------------------------------
/utter_more/utter_more.py:
--------------------------------------------------------------------------------
1 | from itertools import product, chain
2 | from csv import writer
3 | from os import path
4 | import re
5 |
6 |
7 | class UtterMore:
8 |
9 | def __init__(self, *utterance_templates):
10 | """
11 | A class to create utterances for a custom skill for Amazon's Alexa. It
12 | can be a tedious process if verbosity is desired because language is so
13 | flexible. So this will automatically creates all the utterances you want
14 | based on (a) given utterance template(s).
15 |
16 | There are three ways to format a template and they are as follows:
17 |
18 | (a|b|c|...) - [OR] Used if you want to allow multiple interchangeable
19 | words. For example, if photo, picture and painting are
20 | interchangeable in your utterances, then write
21 | (photo|picture|painting) in the place where it would be. The number
22 | of words to OR is arbitrary and single curly keywords like
23 | {intent_slot} can be used in this.
24 | (a*1|b*2) (c^1|d^2) - [CONDITIONAL OR] The * defines a master with a tag
25 | of whatever follows the * while the ^ defines a follower of the tag
26 | of whatever follows the ^. So utterances with a word tagged with
27 | ^sample will only be returned if the utterance also has a word
28 | tagged with *sample. The above will display 'a c' OR 'b d'.
29 | {{slot}} - [OPTIONAL INTENT SLOT] Used if the existence of an intent
30 | slot in your utterance is optional. For example, if you have an
31 | optional adverb you may write I {adverb} love it or just I love it.
32 | Instead you can write I {{adverb}} love it to capture both.
33 |
34 | For example, the template
35 |
36 | "What (is*singular|are*plural) (that^singular|those^plural) {{things}}"
37 | will return the following utterances:
38 |
39 | ['What is that {things}',
40 | 'What is that',
41 | 'What are those {things}',
42 | 'What are those']
43 |
44 | An arbitrary number of utterance templates can be passed to the class.
45 | Or utterance templates can be passed as a solo argument to
46 | self.build_utterances.
47 |
48 | Parameters:
49 | utterance_templates - Arbitrary number of utterance templates. Their
50 | respective utterance can be created by running
51 | self.iter_build_utterances which will save them
52 | in self.utterances
53 | """
54 | # Handle a combination of lists and strings being passed
55 | self.utterance_templates = list(chain.from_iterable(
56 | [[template] if isinstance(template, str) else template
57 | for template in utterance_templates]))
58 | self.utterances = []
59 |
60 | def iter_build_utterances(self):
61 | """
62 | Iteratively runs self.build_utterances for every utterance template
63 | given in the initialization (in self.utterance_templates) and stores
64 | the resulting utterances in self.utterances as a two-dimensional list
65 | where each list element is a list of all the utterances for a single
66 | template.
67 | """
68 | for utterance_template in self.utterance_templates:
69 | self.utterances.append(self.build_utterances(utterance_template))
70 |
71 | @staticmethod
72 | def _order_curlies(*curlies):
73 | """
74 | Orders the curlies in a list based on where they should appear in the
75 | template and prepares it for adding to template.
76 | """
77 | # Create dictionary mapping where the above occur to the occurance
78 | all_curlies = chain.from_iterable(curlies)
79 | indexed_curlies = {curly.start(0): curly.group(0) for curly in all_curlies}
80 |
81 | ordered_curlies = []
82 | for ind in sorted(indexed_curlies.keys()):
83 | curly = indexed_curlies[ind]
84 | # Double curlies are either single curlies or nothing
85 | if curly.startswith('{{'):
86 | ordered_curlies.append([curly[1:-1], ''])
87 | # These are a choice of the words separated by the pip
88 | elif curly.startswith('('):
89 | ordered_curlies.append(curly[1:-1].split('|'))
90 |
91 | return ordered_curlies
92 |
93 | @staticmethod
94 | def _fill_in_template(template, ordered_curlies):
95 | """
96 | Given a template to fill and an ordered list of curlies created by
97 | self._order_curlies to fill it, it does just that.
98 | """
99 | # Fill in template with every combination
100 | utterances = []
101 | for edit in product(*ordered_curlies):
102 | skip_edit = False
103 |
104 | # First get the masters (OR keywords with the *)
105 | masters = set()
106 | for kw in edit:
107 | # Will be empty list if find no * followed by the tag
108 | found_master = re.findall(r'\*(\w+)', kw)
109 | # If not empty list, add that to masters set
110 | if found_master:
111 | masters.add(found_master[0])
112 |
113 | # Find the followers and see if they match up with the masters
114 | for kw in edit:
115 | # Same idea as with finding the masters/a master
116 | found_follower = re.findall(r'\^(\w+)', kw)
117 | # Don't add this edit to utterances if it has a follower that
118 | # isn't in the masters set
119 | if found_follower and found_follower[0] not in masters:
120 | skip_edit = True
121 | continue
122 |
123 | # If all good, add it!
124 | if not skip_edit:
125 | # Remove the OR conditional stuff to clean it
126 | cleaned_edit = [x.split('*')[0].split('^')[0] for x in edit]
127 | # The join/split nonsense removes excess whitespace
128 | utterances.append(' '.join(template.format(*cleaned_edit).split()))
129 |
130 | return utterances
131 |
132 | def build_utterances(self, utterance_template):
133 | """
134 | Returns the made utterances given an utterance template. It supports
135 | the following substitutions:
136 |
137 | (a|b|c|...) - [OR] This wil place a OR b OR c OR etc. in its place
138 | {{slot}} - [OPTIONAL SLOT] This will place the slot {slot} or nothing
139 | in its place.
140 | (a*1|b*2) (c^1|d^2) - [CONDITIONAL OR] The * defines a master with a tag
141 | of whatever follows the * while the ^ defines a follower of the tag
142 | of whatever follows the ^. So utterances with a word tagged with
143 | ^sample will only be returned if the utterance also has a word
144 | tagged with *sample. The above will display 'a c' OR 'b d'. If we
145 | have multiple masters, then the follower(s) will appear if at least
146 | one master is present. And alternatively, you can treat multiple
147 | followers as a CONDITIONAL AND.
148 |
149 | For example, the template
150 |
151 | "What (is*singular|are*plural) (that^singular|those^plural) {{things}}"
152 | will return the following utterances:
153 |
154 | ['What is that {things}',
155 | 'What is that',
156 | 'What are those {things}',
157 | 'What are those']
158 |
159 | Parameters:
160 | utterance_template - The template the utterances are created from
161 | """
162 | # Find every double bracketed keyword
163 | double_curlies = re.finditer(r'({{[^{}]*}})', utterance_template)
164 | # Find every single parenthesis keyword
165 | or_curlies = re.finditer(r'(\([^()]*\))', utterance_template)
166 | # Below turns "What (is|are) (that|those) {{things}} {place}?" into:
167 | # "What {} {} {} {{place}}?"
168 | # Finds the above keywords and replaces with {} for formatting
169 | template = re.sub(r'{{[^{}]*}}|\([^()]*\)', '{}', utterance_template)
170 | # Turns {...} into {{...}} to literalize the curlies
171 | template = re.sub(r'\{[\w]+\}', lambda x: '{' + x.group(0) + '}', template)
172 |
173 | # Creates ordered list of curlies based on their appearance in template
174 | ordered_curlies = self._order_curlies(double_curlies, or_curlies)
175 |
176 | # Fills in template based on logic given by utterance template
177 | return self._fill_in_template(template, ordered_curlies)
178 |
179 | def add_utterance_template(self, utterance_template):
180 | """
181 | Adds another utterance template to the current list of them.
182 |
183 | Parameters:
184 | utterance_template - Template to add to current list of templates
185 | """
186 | self.utterance_templates.append(utterance_template)
187 |
188 | def save_utterances(self, fpath, name, saved_as, force=False, written_as=None):
189 | """
190 | Saves the current utterances to a file.
191 |
192 | Parameters:
193 | fpath - Path to the directory in which to save the file
194 | name - Name of the to be saved file
195 | saved_as - File type, file extension to save as (e.g. 'txt' or 'csv')
196 | force - (default False) If True, will automatically make the file. If a
197 | file of the same name exists, it will overwrite it. If False,
198 | it will throw an error of the file already exists.
199 | written_as - (default None) What type of file to be written as. If no
200 | argument is given, then it will be written as what it is
201 | saved as. For example, if we put saved_as='txt' and
202 | written_as='csv', then the save file will have a .txt
203 | extension but will be written as comma-separated values
204 | like a CSV. Amazon's Alexa requires a CSV file but with
205 | line separated values, so self.save_for_alexa uses
206 | saved_as='csv' and written_as='txt'
207 | """
208 | # Allows saving with one file extension but as another
209 | written_as = written_as or saved_as
210 |
211 | # Create full path name
212 | full_path = path.join(fpath, name + '.' + saved_as)
213 |
214 | # Check if file already exists
215 | if path.exists(full_path) and not force:
216 | raise Exception('File already exists and force=False. ' +
217 | 'Set force=True to overwrite file.')
218 | # Check if unsupported file type
219 | if saved_as not in ['csv', 'txt']:
220 | raise Exception("File type '{}' is not supported.".format(ftype_or))
221 |
222 | # Open file and add every utterance
223 | with open(full_path, 'w', newline='') as f:
224 | if written_as == 'txt':
225 | first_line = True
226 | for utterance in chain.from_iterable(self.utterances):
227 | # To write '\n'-separated but without one at start or end
228 | if first_line:
229 | first_line = False
230 | f.write('{}'.format(utterance.strip()))
231 | else:
232 | f.write('\n{}'.format(utterance.strip()))
233 | elif written_as == 'csv':
234 | csv_writer = writer(f)
235 | csv_writer.writerow(chain.from_iterable(self.utterances))
236 |
237 | def save_for_alexa(self, fpath, name, force=False):
238 | """
239 | Creates CSV in the format that Alexa needs (instead of comma-separated,
240 | it's new line-separated otherwise it won't upload correctly).
241 |
242 | Parameters:
243 | fpath - Path to the directory in which to save the file
244 | name - Name of the to be saved file
245 | force - (default False) If True, will automatically make the file. If a
246 | file of the same name exists, it will overwrite it. If False,
247 | it will throw an error of the file already exists.
248 | """
249 | self.save_utterances(fpath, name, 'csv', force=force, written_as='txt')
250 |
251 | def read_utterance_templates_from_file(self, fpath, sep='\n'):
252 | """
253 | Reads in utterance templates from a file.
254 |
255 | Paramters:
256 | fpath - Path to the file with the templates
257 | sep - (default '\n') The separator for each template. If each template
258 | is on a new line, then use default
259 | """
260 | # Read in data
261 | with open(fpath, 'r') as f:
262 | file_data = f.read()
263 |
264 | for utterance_template in file_data.split(sep):
265 | # Skip if an empty string
266 | if utterance_template:
267 | self.add_utterance_template(utterance_template)
268 |
269 |
270 | if __name__ == "__main__":
271 | from sys import argv
272 | utter_more = UtterMore(*argv[1:])
273 | utter_more.iter_build_utterances()
274 |
275 | from pprint import pprint
276 | pprint(utter_more.utterances)
277 |
--------------------------------------------------------------------------------