├── .appveyor.yml
├── .coveragerc
├── .flake8
├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── Pipfile
├── Pipfile.lock
├── README.md
├── apertium
├── __init__.py
├── analysis
│ └── __init__.py
├── generation
│ └── __init__.py
├── installer.py
├── iso639.py
├── mode_search.py
├── py.typed
├── tagger
│ └── __init__.py
├── translation
│ └── __init__.py
└── utils.py
├── docs
├── Makefile
└── source
│ ├── about.md
│ ├── conf.py
│ ├── index.md
│ ├── install.md
│ └── usage.md
├── setup.py
└── tests
└── __init__.py
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | clone_depth: 50
2 | cache:
3 | - '%LOCALAPPDATA%\pip\Cache'
4 | environment:
5 | matrix:
6 | - PYTHON: C:\\Python36
7 | PYTHON_VERSION: 3.6
8 | PYTHON_ARCH: 32
9 | - PYTHON: C:\\Python36-x64
10 | PYTHON_VERSION: 3.6
11 | PYTHON_ARCH: 64
12 | - PYTHON: C:\\Python37
13 | PYTHON_VERSION: 3.7
14 | PYTHON_ARCH: 32
15 | - PYTHON: C:\\Python37-x64
16 | PYTHON_VERSION: 3.7
17 | PYTHON_ARCH: 64
18 | - PYTHON: C:\\Python38
19 | PYTHON_VERSION: 3.8
20 | PYTHON_ARCH: 32
21 | - PYTHON: C:\\Python38-x64
22 | PYTHON_VERSION: 3.8
23 | PYTHON_ARCH: 64
24 | init:
25 | - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
26 | - "ECHO \"%APPVEYOR_SCHEDULED_BUILD%\""
27 | - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
28 | https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
29 | Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
30 | raise "There are newer queued builds for this pull request, skipping build."
31 | } # credits: JuliaLang developers
32 | install:
33 | - SET PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
34 | - python --version
35 | - python -c "import sys,platform,struct; print(sys.platform, platform.machine(), struct.calcsize('P') * 8, )"
36 | - pip install pipenv
37 | - pipenv install --dev --system
38 | - python setup.py install
39 | build: false
40 | test_script:
41 | - coverage run -m unittest --verbose --buffer tests
42 | - coverage report --show-missing --fail-under 70 --include 'apertium/**'
43 | artifacts:
44 | - path: dist\*
45 | notifications:
46 | - provider: Email
47 | on_build_success: false
48 | on_build_failure: true
49 | on_build_status_changed: true
50 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit=apertium/installer.py
3 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = W504
3 | max-line-length = 160
4 | import-order-style = google
5 | exclude = docs/source/conf.py
6 | application-import-names = apertium
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .mypy_coverage/
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 |
51 | # Translations
52 | *.mo
53 | *.pot
54 |
55 | # Django stuff:
56 | *.log
57 | local_settings.py
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # dotenv
85 | .env
86 |
87 | # virtualenv
88 | .venv
89 | venv/
90 | ENV/
91 |
92 | # Spyder project settings
93 | .spyderproject
94 | .spyproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
99 | # mkdocs documentation
100 | /site
101 |
102 | # mypy
103 | .mypy_cache/
104 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | dist: bionic
3 | cache:
4 | pip: true
5 | directories:
6 | - /usr/local/bin
7 | - /usr/local/lib/python$TRAVIS_PYTHON_VERSION/site-packages
8 | python:
9 | - '3.6'
10 | - '3.7'
11 | - '3.8'
12 | - 'nightly'
13 | install:
14 | - pip install pipenv
15 | - python3 setup.py install
16 | - pipenv install --dev --system
17 | script:
18 | - flake8 --verbose apertium
19 | - mypy apertium --strict --any-exprs-report .mypy_coverage --ignore-missing-imports;
20 | cat .mypy_coverage/any-exprs.txt;
21 | coverage=$(tail -1 .mypy_coverage/any-exprs.txt | grep -Eo '[0-9\.]+%' | sed 's/%$//');
22 | if (( $(echo "$coverage < 95" | bc -l) )); then
23 | exit 1;
24 | fi;
25 | - coverage run -m unittest --verbose --buffer tests
26 | - coverage report --show-missing --fail-under 90 --include 'apertium/**'
27 | after_success:
28 | - coveralls
29 | notifications:
30 | on_failure: change
31 | on_success: change
32 | matrix:
33 | allow_failures:
34 | - python: nightly
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 |
3 | dist:
4 | git clean -xfd
5 | ./setup.py sdist
6 |
7 | release: dist
8 | twine upload --sign dist/*
9 |
10 | test-release: dist
11 | twine upload --sign --repository-url https://test.pypi.org/legacy/ dist/*
12 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 | coverage = "*"
8 | coveralls = "*"
9 | flake8 = "*"
10 | flake8-bugbear = "*"
11 | flake8-builtins = "*"
12 | flake8-commas = "*"
13 | flake8-comprehensions = "*"
14 | flake8-eradicate = {version = "*",markers = "python_version > '3.5'"}
15 | flake8-import-order = "*"
16 | flake8-quotes = "*"
17 | mypy = "*"
18 | pep8-naming = "*"
19 | sphinx = "*"
20 | twine = "*"
21 | recommonmark = "*"
22 |
23 | [packages]
24 | apertium-streamparser = "*"
25 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "b6374fdee852c76c04687a0a448a27e74a2af1a82468b3c7ec60086d6cd7e81d"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {},
8 | "sources": [
9 | {
10 | "name": "pypi",
11 | "url": "https://pypi.org/simple",
12 | "verify_ssl": true
13 | }
14 | ]
15 | },
16 | "default": {
17 | "apertium-streamparser": {
18 | "hashes": [
19 | "sha256:e14e99f9a682725b6f8c0955f86d79319d7786d2e43b1dcaa50f4151b0410771",
20 | "sha256:fd83d3d573d23c54b34339865cdd40cded3687311c18629d2d39c4e8ad1da597"
21 | ],
22 | "index": "pypi",
23 | "version": "==5.0.2"
24 | }
25 | },
26 | "develop": {
27 | "alabaster": {
28 | "hashes": [
29 | "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
30 | "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
31 | ],
32 | "version": "==0.7.12"
33 | },
34 | "attrs": {
35 | "hashes": [
36 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
37 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
38 | ],
39 | "version": "==19.3.0"
40 | },
41 | "babel": {
42 | "hashes": [
43 | "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
44 | "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
45 | ],
46 | "version": "==2.9.0"
47 | },
48 | "bleach": {
49 | "hashes": [
50 | "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125",
51 | "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"
52 | ],
53 | "version": "==3.3.0"
54 | },
55 | "certifi": {
56 | "hashes": [
57 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
58 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
59 | ],
60 | "version": "==2020.12.5"
61 | },
62 | "chardet": {
63 | "hashes": [
64 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
65 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
66 | ],
67 | "version": "==4.0.0"
68 | },
69 | "commonmark": {
70 | "hashes": [
71 | "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60",
72 | "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"
73 | ],
74 | "version": "==0.9.1"
75 | },
76 | "coverage": {
77 | "hashes": [
78 | "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
79 | "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
80 | "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
81 | "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
82 | "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
83 | "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
84 | "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
85 | "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
86 | "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
87 | "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
88 | "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
89 | "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
90 | "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
91 | "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
92 | "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
93 | "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
94 | "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
95 | "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
96 | "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
97 | "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
98 | "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
99 | "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
100 | "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
101 | "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
102 | "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
103 | "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
104 | "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
105 | "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
106 | "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
107 | "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
108 | "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
109 | "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
110 | ],
111 | "index": "pypi",
112 | "version": "==4.5.4"
113 | },
114 | "coveralls": {
115 | "hashes": [
116 | "sha256:9bc5a1f92682eef59f688a8f280207190d9a6afb84cef8f567fa47631a784060",
117 | "sha256:fb51cddef4bc458de347274116df15d641a735d3f0a580a9472174e2e62f408c"
118 | ],
119 | "index": "pypi",
120 | "version": "==1.8.2"
121 | },
122 | "docopt": {
123 | "hashes": [
124 | "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
125 | ],
126 | "version": "==0.6.2"
127 | },
128 | "docutils": {
129 | "hashes": [
130 | "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
131 | "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
132 | ],
133 | "version": "==0.16"
134 | },
135 | "entrypoints": {
136 | "hashes": [
137 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
138 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
139 | ],
140 | "version": "==0.3"
141 | },
142 | "eradicate": {
143 | "hashes": [
144 | "sha256:4ffda82aae6fd49dfffa777a857cb758d77502a1f2e0f54c9ac5155a39d2d01a"
145 | ],
146 | "version": "==1.0"
147 | },
148 | "flake8": {
149 | "hashes": [
150 | "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548",
151 | "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"
152 | ],
153 | "index": "pypi",
154 | "version": "==3.7.8"
155 | },
156 | "flake8-bugbear": {
157 | "hashes": [
158 | "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571",
159 | "sha256:ded4d282778969b5ab5530ceba7aa1a9f1b86fa7618fc96a19a1d512331640f8"
160 | ],
161 | "index": "pypi",
162 | "version": "==19.8.0"
163 | },
164 | "flake8-builtins": {
165 | "hashes": [
166 | "sha256:8d806360767947c0035feada4ddef3ede32f0a586ef457e62d811b8456ad9a51",
167 | "sha256:cd7b1b7fec4905386a3643b59f9ca8e305768da14a49a7efb31fe9364f33cd04"
168 | ],
169 | "index": "pypi",
170 | "version": "==1.4.1"
171 | },
172 | "flake8-commas": {
173 | "hashes": [
174 | "sha256:d3005899466f51380387df7151fb59afec666a0f4f4a2c6a8995b975de0f44b7",
175 | "sha256:ee2141a3495ef9789a3894ed8802d03eff1eaaf98ce6d8653a7c573ef101935e"
176 | ],
177 | "index": "pypi",
178 | "version": "==2.0.0"
179 | },
180 | "flake8-comprehensions": {
181 | "hashes": [
182 | "sha256:7b174ded3d7e73edf587e942458b6c1a7c3456d512d9c435deae367236b9562c",
183 | "sha256:e36fc12bd3833e0b34fe0639b7a817d32c86238987f532078c57eafdc7a8a219"
184 | ],
185 | "index": "pypi",
186 | "version": "==2.2.0"
187 | },
188 | "flake8-eradicate": {
189 | "hashes": [
190 | "sha256:86804c682f9805a689379307939f350140fe9c015c3e600baff37fb23f7f21cc",
191 | "sha256:cc2c3300a6643f8347988cc828478c347975f7bf9c8fc1f8a7027da41ab9bdbd"
192 | ],
193 | "index": "pypi",
194 | "markers": "python_version > '3.5'",
195 | "version": "==0.2.1"
196 | },
197 | "flake8-import-order": {
198 | "hashes": [
199 | "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543",
200 | "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"
201 | ],
202 | "index": "pypi",
203 | "version": "==0.18.1"
204 | },
205 | "flake8-polyfill": {
206 | "hashes": [
207 | "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9",
208 | "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"
209 | ],
210 | "version": "==1.0.2"
211 | },
212 | "flake8-quotes": {
213 | "hashes": [
214 | "sha256:5dbaf668887873f28346fb87943d6da2e4b9f77ce9f2169cff21764a0a4934ed"
215 | ],
216 | "index": "pypi",
217 | "version": "==2.1.0"
218 | },
219 | "idna": {
220 | "hashes": [
221 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
222 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
223 | ],
224 | "version": "==2.10"
225 | },
226 | "imagesize": {
227 | "hashes": [
228 | "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
229 | "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
230 | ],
231 | "version": "==1.2.0"
232 | },
233 | "jinja2": {
234 | "hashes": [
235 | "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
236 | "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
237 | ],
238 | "index": "pypi",
239 | "version": "==2.11.3"
240 | },
241 | "markupsafe": {
242 | "hashes": [
243 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
244 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
245 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
246 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
247 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
248 | "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
249 | "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
250 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
251 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
252 | "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
253 | "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
254 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
255 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
256 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
257 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
258 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
259 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
260 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
261 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
262 | "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
263 | "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
264 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
265 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
266 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
267 | "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
268 | "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
269 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
270 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
271 | "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
272 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
273 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
274 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
275 | "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
276 | "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
277 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
278 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
279 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
280 | "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
281 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
282 | "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
283 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
284 | "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
285 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
286 | "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
287 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
288 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
289 | "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
290 | "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
291 | "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
292 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
293 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
294 | "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
295 | ],
296 | "version": "==1.1.1"
297 | },
298 | "mccabe": {
299 | "hashes": [
300 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
301 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
302 | ],
303 | "version": "==0.6.1"
304 | },
305 | "mypy": {
306 | "hashes": [
307 | "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a",
308 | "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4",
309 | "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0",
310 | "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae",
311 | "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339",
312 | "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76",
313 | "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498",
314 | "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4",
315 | "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd",
316 | "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba",
317 | "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"
318 | ],
319 | "index": "pypi",
320 | "version": "==0.720"
321 | },
322 | "mypy-extensions": {
323 | "hashes": [
324 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
325 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
326 | ],
327 | "version": "==0.4.3"
328 | },
329 | "packaging": {
330 | "hashes": [
331 | "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
332 | "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
333 | ],
334 | "version": "==20.9"
335 | },
336 | "pep8-naming": {
337 | "hashes": [
338 | "sha256:01cb1dab2f3ce9045133d08449f1b6b93531dceacb9ef04f67087c11c723cea9",
339 | "sha256:0ec891e59eea766efd3059c3d81f1da304d858220678bdc351aab73c533f2fbb"
340 | ],
341 | "index": "pypi",
342 | "version": "==0.8.2"
343 | },
344 | "pkginfo": {
345 | "hashes": [
346 | "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4",
347 | "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"
348 | ],
349 | "version": "==1.7.0"
350 | },
351 | "pycodestyle": {
352 | "hashes": [
353 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
354 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
355 | ],
356 | "version": "==2.5.0"
357 | },
358 | "pyflakes": {
359 | "hashes": [
360 | "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
361 | "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
362 | ],
363 | "version": "==2.1.1"
364 | },
365 | "pygments": {
366 | "hashes": [
367 | "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94",
368 | "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"
369 | ],
370 | "version": "==2.8.1"
371 | },
372 | "pyparsing": {
373 | "hashes": [
374 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
375 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
376 | ],
377 | "version": "==2.4.7"
378 | },
379 | "pytz": {
380 | "hashes": [
381 | "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
382 | "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
383 | ],
384 | "version": "==2021.1"
385 | },
386 | "readme-renderer": {
387 | "hashes": [
388 | "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c",
389 | "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"
390 | ],
391 | "version": "==29.0"
392 | },
393 | "recommonmark": {
394 | "hashes": [
395 | "sha256:29cd4faeb6c5268c633634f2d69aef9431e0f4d347f90659fd0aab20e541efeb",
396 | "sha256:2ec4207a574289355d5b6ae4ae4abb29043346ca12cdd5f07d374dc5987d2852"
397 | ],
398 | "index": "pypi",
399 | "version": "==0.6.0"
400 | },
401 | "requests": {
402 | "hashes": [
403 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
404 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
405 | ],
406 | "version": "==2.25.1"
407 | },
408 | "requests-toolbelt": {
409 | "hashes": [
410 | "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
411 | "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
412 | ],
413 | "version": "==0.9.1"
414 | },
415 | "six": {
416 | "hashes": [
417 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
418 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
419 | ],
420 | "version": "==1.15.0"
421 | },
422 | "snowballstemmer": {
423 | "hashes": [
424 | "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
425 | "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
426 | ],
427 | "version": "==2.1.0"
428 | },
429 | "sphinx": {
430 | "hashes": [
431 | "sha256:0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845",
432 | "sha256:839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069"
433 | ],
434 | "index": "pypi",
435 | "version": "==2.2.0"
436 | },
437 | "sphinxcontrib-applehelp": {
438 | "hashes": [
439 | "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
440 | "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
441 | ],
442 | "version": "==1.0.2"
443 | },
444 | "sphinxcontrib-devhelp": {
445 | "hashes": [
446 | "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
447 | "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
448 | ],
449 | "version": "==1.0.2"
450 | },
451 | "sphinxcontrib-htmlhelp": {
452 | "hashes": [
453 | "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
454 | "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
455 | ],
456 | "version": "==1.0.3"
457 | },
458 | "sphinxcontrib-jsmath": {
459 | "hashes": [
460 | "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
461 | "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
462 | ],
463 | "version": "==1.0.1"
464 | },
465 | "sphinxcontrib-qthelp": {
466 | "hashes": [
467 | "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
468 | "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
469 | ],
470 | "version": "==1.0.3"
471 | },
472 | "sphinxcontrib-serializinghtml": {
473 | "hashes": [
474 | "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
475 | "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
476 | ],
477 | "version": "==1.1.4"
478 | },
479 | "tqdm": {
480 | "hashes": [
481 | "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7",
482 | "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33"
483 | ],
484 | "version": "==4.59.0"
485 | },
486 | "twine": {
487 | "hashes": [
488 | "sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446",
489 | "sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"
490 | ],
491 | "index": "pypi",
492 | "version": "==1.13.0"
493 | },
494 | "typed-ast": {
495 | "hashes": [
496 | "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
497 | "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
498 | "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
499 | "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
500 | "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
501 | "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
502 | "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
503 | "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
504 | "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
505 | "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
506 | "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
507 | "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
508 | "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
509 | "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
510 | "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
511 | "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
512 | "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
513 | "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
514 | "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
515 | "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
516 | "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
517 | "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
518 | "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
519 | "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
520 | "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
521 | "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
522 | "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
523 | "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
524 | "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
525 | "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
526 | ],
527 | "version": "==1.4.2"
528 | },
529 | "typing-extensions": {
530 | "hashes": [
531 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
532 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
533 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
534 | ],
535 | "version": "==3.7.4.3"
536 | },
537 | "urllib3": {
538 | "hashes": [
539 | "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
540 | "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
541 | ],
542 | "version": "==1.26.4"
543 | },
544 | "webencodings": {
545 | "hashes": [
546 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
547 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
548 | ],
549 | "version": "==0.5.1"
550 | }
551 | }
552 | }
553 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apertium + Python
2 |
3 | [](https://travis-ci.com/apertium/apertium-python)
4 | [](https://ci.appveyor.com/project/apertium/apertium-python/branch/master)
5 | [](https://readthedocs.org/projects/apertium-python)
6 | [](https://coveralls.io/github/apertium/apertium-python?branch=master)
7 | []((https://pypi.org/project/apertium/))
8 |
9 | ## Introduction
10 |
11 | - The codebase is in development for the GSoC '19 project called **Apertium API in Python**
12 | - The Apertium core modules are written in C++.
13 | - This project makes the Apertium modules available in Python, which because of its simplicity is more appealing to users.
14 |
15 | ## About the Exisiting Code Base
16 |
17 | - The existing codebase has `Subprocess` and [SWIG](http://www.swig.org/) wrapper implementations of the higher level functions of Apertium and CG modules.
18 |
19 | ## Installation
20 |
21 | - Installation on Debian/Ubuntu and Windows is natively supported:
22 |
23 | ```
24 | pip install apertium
25 | ```
26 |
27 | - For developers, `pipenv` can be used to install the development dependencies and enter a shell with them:
28 |
29 | ```
30 | pip install pipenv
31 | pipenv install --dev
32 | pipenv shell
33 | ```
34 |
35 | - Apertium packages can be installed from Python interpreter as well.
36 | - Install `apertium-all-dev` by calling `apertium.installer.install_apertium()`
37 | - Install language packages with `apertium.installer.install_module(language_name)`. For example `apertium-eng` can be installed by executing `apertium.installer.install_module('eng')`
38 |
39 | ## Usage
40 |
41 | - For multiple invocations `Method 1` is more performant, as the dictionary needs to be loaded only once.
42 |
43 | ### Analysis
44 |
45 | Performing Morphological Analysis
46 |
47 | Method 1: Create an `Analyzer` object and call its `analyze` method.
48 | ```python
49 | In [1]: import apertium
50 | In [2]: a = apertium.Analyzer('en')
51 | In [3]: a.analyze('cats')
52 | Out[3]: [cats/cat, ./.]
53 | ```
54 |
55 | Method 2: Calling `analyze()` directly.
56 | ```python
57 | In [1]: import apertium
58 | In [2]: apertium.analyze('en', 'cats')
59 | Out[2]: cats/cat
60 | ```
61 |
62 | ### Generation
63 |
64 | Performing Morphological Generation
65 |
66 | Method 1: Create a `Generator` object and call its `generate` method.
67 | ```python
68 | In [1]: import apertium
69 | In [2]: g = apertium.Generator('en')
70 | In [3]: g.generate('^cat$')
71 | Out[3]: 'cats'
72 | ```
73 |
74 | Method 2: Calling `generate()` directly.
75 | ```python
76 | In [1]: import apertium
77 | In [2]: apertium.generate('en', '^cat$')
78 | Out[2]: 'cats'
79 | ```
80 |
81 | ### Tagger
82 |
83 | Method 1: Create a `Tagger` object and call its `tag` method.
84 | ```python
85 | In [1]: import apertium
86 | In [2]: tagger = apertium.Tagger('eng')
87 | In [3]: tagger.tag('cats')
88 | Out[3]: [cats/cat]
89 | ```
90 |
91 | Method 2: Calling `tag()` directly.
92 | ```python
93 | In [1]: import apertium
94 | In [2]: apertium.tag('en', 'cats')
95 | Out[2]: [cats/cat]
96 | ```
97 |
98 | ### Translation
99 |
100 | Method 1: Create a `Translator` object and call its `translate` method.
101 | ```python
102 | In [1]: import apertium
103 | In [2]: t = apertium.Translator('eng', 'spa')
104 | In [3]: t.translate('cats')
105 | Out[3]: 'Gatos'
106 | ```
107 |
108 | Method 2: Calling `translate()` directly.
109 | ```python
110 | In [1]: import apertium
111 | In [2]: apertium.translate('en', 'spa', 'cats')
112 | Out[2]: 'Gatos'
113 | ```
114 |
115 | ### Installing more modes from other language data
116 |
117 | One can also install modes by providing the path to the `lang-data`:
118 |
119 | ```python
120 | In [1]: import apertium
121 | In [2]: apertium.append_pair_path('..')
122 | ```
123 |
--------------------------------------------------------------------------------
/apertium/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'Lokendra Singh, Arghya Bhatttacharya, Sushain K. Cherivirala, Andi Qu'
2 | __license__ = 'GNU General Public License v3.0'
3 | __version__ = '0.2.4'
4 |
5 | import logging
6 | import os
7 | import platform
8 | from typing import Dict, Tuple
9 |
10 | from apertium.analysis import analyze, Analyzer # noqa: F401
11 | from apertium.generation import generate, Generator # noqa: F401
12 | from apertium.installer import install_module # noqa: F401
13 | from apertium.mode_search import search_path
14 | from apertium.tagger import tag, Tagger # noqa: F401
15 | from apertium.translation import translate, Translator # noqa: F401
16 | from apertium.utils import wrappers_available # noqa: F401
17 |
18 |
19 | class ModeNotInstalled(ValueError):
20 | pass
21 |
22 |
23 | class InstallationNotSupported(ValueError):
24 | pass
25 |
26 |
27 | def _update_modes() -> None:
28 | """
29 |
30 | """
31 | for pair_path in pair_paths:
32 | modes = search_path(pair_path)
33 | if modes['pair']:
34 | for path, lang_src, lang_trg in modes['pair']:
35 | pairs[f'{lang_src}-{lang_trg}'] = path
36 | if modes['analyzer']:
37 | for dirpath, modename, lang_pair in modes['analyzer']:
38 | analyzers[lang_pair] = (dirpath, modename)
39 | if modes['generator']:
40 | for dirpath, modename, lang_pair in modes['generator']:
41 | generators[lang_pair] = (dirpath, modename)
42 | if modes['tagger']:
43 | for dirpath, modename, lang_pair in modes['tagger']:
44 | taggers[lang_pair] = (dirpath, modename)
45 |
46 |
47 | def append_pair_path(pair_path: str) -> None:
48 | """
49 | Args:
50 | pair_path (str)
51 | """
52 | pair_paths.append(pair_path)
53 | _update_modes()
54 |
55 |
56 | def windows_update_path() -> None:
57 | """
58 | 1. Add the Apertium Binaries to shell PATH
59 | 2. Call apertium.append_pair_path for windows
60 | """
61 |
62 | try:
63 | install_path = os.environ['LOCALAPPDATA']
64 | current = os.environ['PATH']
65 |
66 | apertium_bin_path = os.path.join(install_path, 'apertium-all-dev', 'bin')
67 | if os.path.isdir(apertium_bin_path):
68 | update_path = f'{current}{os.pathsep}{apertium_bin_path}{os.pathsep}'
69 | os.environ['PATH'] = update_path
70 | apertium_lang_path = \
71 | os.path.join(install_path, 'apertium-all-dev', 'share', 'apertium')
72 | if os.path.isdir(apertium_lang_path):
73 | append_pair_path(apertium_lang_path)
74 | except KeyError:
75 | print('This function is available only for Windows')
76 | raise InstallationNotSupported(platform.system())
77 |
78 |
79 | pair_paths = ['/usr/share/apertium', '/usr/local/share/apertium']
80 | analyzers: Dict[str, Tuple[str, str]] = {}
81 | generators: Dict[str, Tuple[str, str]] = {}
82 | taggers: Dict[str, Tuple[str, str]] = {}
83 | pairs: Dict[str, str] = {}
84 | _update_modes()
85 | if platform.system() == 'Windows':
86 | windows_update_path()
87 | logging.basicConfig(format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', level=logging.ERROR)
88 | logger = logging.getLogger()
89 |
--------------------------------------------------------------------------------
/apertium/analysis/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import Dict, List
3 |
4 | from streamparser import LexicalUnit, parse
5 |
6 | import apertium
7 | from apertium.utils import execute_pipeline, parse_mode_file, to_alpha3_code
8 |
9 |
10 | class Analyzer:
11 | """
12 | Attributes:
13 | analyzer_cmds (Dict[str, List[List[str]]])
14 | lang (str)
15 | """
16 |
17 | def __init__(self, lang: str) -> None:
18 | """
19 | Args:
20 | lang (str)
21 | """
22 | self.analyzer_cmds: Dict[str, List[List[str]]] = {}
23 | self.lang: str = to_alpha3_code(lang)
24 | if self.lang not in apertium.analyzers:
25 | raise apertium.ModeNotInstalled(self.lang)
26 | else:
27 | self.path, self.mode = apertium.analyzers[self.lang]
28 |
29 | def _get_commands(self) -> List[List[str]]:
30 | """
31 | Returns:
32 | List[List[str]]
33 | """
34 | if self.lang not in self.analyzer_cmds:
35 | mode_path, mode = apertium.analyzers[self.lang]
36 | abs_mode_path = os.path.join(mode_path, 'modes', '{}.mode'.format(mode))
37 | self.analyzer_cmds[self.lang] = parse_mode_file(abs_mode_path)
38 |
39 | return self.analyzer_cmds[self.lang]
40 |
41 | @staticmethod
42 | def _postproc_text(result: str) -> List[LexicalUnit]:
43 | """
44 | Postprocesses the input
45 |
46 | Args:
47 | result (str)
48 |
49 | Returns:
50 | List[LexicalUnit]
51 | """
52 | lexical_units: List[LexicalUnit] = list(parse(result))
53 | return lexical_units
54 |
55 | def analyze(self, in_text: str, formatting: str = 'txt') -> List[LexicalUnit]:
56 | """
57 | Runs apertium to analyze the input
58 |
59 | Args:
60 | in_text (str)
61 | formatting (str)
62 |
63 | Returns:
64 | List[LexicalUnit]
65 | """
66 | self._get_commands()
67 | deformatter: List[str] = ['apertium-des{}'.format(formatting), '-n']
68 | if deformatter not in self.analyzer_cmds[self.lang]:
69 | self.analyzer_cmds[self.lang].insert(0, deformatter)
70 | result: str = execute_pipeline(in_text, self.analyzer_cmds[self.lang])
71 | return self._postproc_text(result)
72 |
73 |
74 | def analyze(lang: str, in_text: str, formatting: str = 'txt') -> List[LexicalUnit]:
75 | """
76 | Args:
77 | lang (str)
78 | in_text (str)
79 | formatting (str)
80 |
81 | Returns:
82 | List[LexicalUnit]
83 | """
84 | analyzer: Analyzer = Analyzer(lang)
85 | return analyzer.analyze(in_text, formatting)
86 |
--------------------------------------------------------------------------------
/apertium/generation/__init__.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List, Union
2 |
3 | import apertium
4 | from apertium.utils import execute_pipeline, parse_mode_file, to_alpha3_code
5 |
6 |
7 | class Generator:
8 | """
9 | Attributes:
10 | generation_cmds (Dict[str, List[List[str]]])
11 | lang (str)
12 | """
13 |
14 | def __init__(self, lang: str) -> None:
15 | """
16 | Args:
17 | lang (str)
18 | """
19 | self.generator_cmds: Dict[str, List[List[str]]] = {}
20 | self.lang: str = lang
21 |
22 | def _get_commands(self) -> List[List[str]]:
23 | """
24 | Returns:
25 | List[List[str]]
26 | """
27 | if self.lang not in self.generator_cmds:
28 | mode_path, mode = apertium.generators[self.lang]
29 | self.generator_cmds[self.lang] = parse_mode_file(mode_path + '/modes/' + mode + '.mode')
30 | return self.generator_cmds[self.lang]
31 |
32 | def generate(self, in_text: str, formatting: str = 'none') -> Union[str, List[str]]:
33 | """
34 | Args:
35 | in_text (str)
36 | formatting (str)
37 |
38 | Returns:
39 | Union[str, List[str]]
40 | """
41 | self.lang = to_alpha3_code(self.lang)
42 |
43 | if self.lang in apertium.generators:
44 | commands = list(self._get_commands())
45 | result = execute_pipeline(in_text, commands)
46 | return result.rstrip('\x00')
47 | else:
48 | raise apertium.ModeNotInstalled(self.lang)
49 |
50 |
51 | def generate(lang: str, in_text: str, formatting: str = 'none') -> Union[str, List[str]]:
52 | """
53 | Args:
54 | lang (str)
55 | in_text (str)
56 | formatting (str)
57 |
58 | Returns:
59 | Union[str, List[str]]
60 | """
61 | generator = Generator(lang)
62 | return generator.generate(in_text, formatting)
63 |
--------------------------------------------------------------------------------
/apertium/installer.py:
--------------------------------------------------------------------------------
1 | from distutils.dir_util import copy_tree
2 | import logging
3 | import os
4 | import platform
5 | import re
6 | import shutil
7 | import subprocess
8 | import tempfile
9 | from typing import Dict, Optional, Union
10 | from urllib.request import urlretrieve
11 | from zipfile import ZipFile
12 |
13 | import apertium
14 |
15 | nightly: bool = True
16 |
17 |
18 | class Windows:
19 | """Download ApertiumWin64 and Move to %localappdata%"""
20 | base_link = 'http://apertium.projectjj.com/{}'
21 |
22 | def __init__(self) -> None:
23 | self._install_path: str = str(os.getenv('LOCALAPPDATA'))
24 | self._apertium_path: str = str(os.path.join(self._install_path, 'apertium-all-dev'))
25 | self._download_path = tempfile.mkdtemp()
26 | logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
27 | self._logger = logging.getLogger()
28 | self._logger.setLevel(logging.DEBUG)
29 |
30 | def _download_zip(self, download_files: Dict[str, str], extract_path: Optional[str]) -> None:
31 | for zip_name, zip_link in download_files.items():
32 | zip_download_path = os.path.join(self._download_path, zip_name)
33 | urlretrieve(Windows.base_link.format(zip_link), filename=zip_download_path)
34 | self._logger.info('%s -> %s download completed', Windows.base_link.format(zip_link), zip_name)
35 |
36 | # Extract the zip
37 | with ZipFile(zip_download_path) as zip_file:
38 | zip_file.extractall(path=extract_path)
39 | self._logger.info('%s Extraction completed', zip_name)
40 | os.remove(zip_download_path)
41 | self._logger.info('%s removed', zip_name)
42 |
43 | def _download_package(self, package: str) -> None:
44 | """Installs Packages to %localappdata%/Apertium"""
45 |
46 | install_sh = 'nightly' if nightly else 'release'
47 | zip_path = f'win32/{install_sh}/data.php?zip='
48 | package_zip = {package: zip_path + package}
49 | self._download_zip(package_zip, self._download_path)
50 |
51 | # move the extracted files to desired location
52 | lang_data_path = os.path.join(self._download_path, 'usr', 'share', 'apertium')
53 |
54 | self._logger.info('Copying Language Data to Apertium')
55 | for directory in os.listdir(lang_data_path):
56 | source: str = str(os.path.join(lang_data_path, directory))
57 | destination: str = str(os.path.join(self._apertium_path, 'share', 'apertium', directory))
58 | copy_tree(source, destination)
59 | self._logger.info('%s -> %s', source, destination)
60 |
61 | shutil.rmtree(os.path.join(self._download_path, 'usr'))
62 |
63 | def _edit_modes(self) -> None:
64 | r"""The mode files need to be modified before being used on Windows System
65 |
66 | 1. Replace /usr/share with %localappdata%\apertium-all-dev\share
67 | 2. Replace "/" with "\" to make path compatible with Windows System
68 | """
69 |
70 | # List of Mode Files
71 | mode_path: str = str(os.path.join(self._apertium_path, 'share', 'apertium', 'modes'))
72 | for f in os.listdir(mode_path):
73 | if os.path.isfile(os.path.join(mode_path, f)) and f.endswith('.mode'):
74 | self._logger.info('Editing mode %s ', f)
75 | with open(os.path.join(mode_path, f)) as infile:
76 | line = infile.read()
77 |
78 | contents = line.split(' ')
79 | # Editing mode file to be compatible with windows platform
80 | for i, t in enumerate(contents):
81 | if len(t) > 2 and t[0] == "'" and t[1] == '/':
82 | t = t.replace('/', os.sep)
83 | t = t.replace(r'\usr', self._apertium_path)
84 | contents[i] = t
85 | line = ' '.join(contents)
86 | with open(os.path.join(mode_path, f), 'w') as outfile:
87 | outfile.write(line)
88 | outfile.close()
89 |
90 | def install_apertium_base(self) -> None:
91 | """Installs Apertium-all-dev to %localappdata%"""
92 |
93 | apertium_windows = {
94 | 'apertium-all-dev.zip': '/win64/nightly/apertium-all-dev.zip',
95 | }
96 |
97 | self._download_zip(apertium_windows, self._install_path)
98 |
99 | def install_apertium_module(self, language: str) -> None:
100 | self._download_package(language)
101 | self._edit_modes()
102 |
103 | def install_wrapper(self, swig_wrapper: str) -> None:
104 | # TODO: create installer for wrappers on windows
105 | pass
106 |
107 |
108 | class Debian:
109 | @staticmethod
110 | def _install_package_source() -> None:
111 | install_sh = 'install-nightly.sh' if nightly else 'install-release.sh'
112 | install_script_url = f'http://apertium.projectjj.com/apt/{install_sh}'
113 | with tempfile.NamedTemporaryFile('w') as install_script:
114 | urlretrieve(install_script_url, install_script.name)
115 | execute = subprocess.run(['sudo', 'bash', install_script.name])
116 | execute.check_returncode()
117 |
118 | @staticmethod
119 | def _download_package(package: str) -> None:
120 | command = ['sudo', 'apt-get', 'install', '-y', package]
121 | execute = subprocess.run(command)
122 | execute.check_returncode()
123 |
124 | @staticmethod
125 | def _rename_wrappers() -> None:
126 | wrapper_name = {
127 | 'python3-apertium-core': '_apertium_core',
128 | 'python3-apertium-lex-tools': '_apertium_lex_tools',
129 | 'python3-cg3': '_constraint_grammar',
130 | 'python3-lttoolbox': '_lttoolbox',
131 | }
132 | dist_package = '/usr/lib/python3/dist-packages'
133 | for wrapper in wrapper_name.values():
134 | for f in os.listdir(dist_package):
135 | if f.startswith(wrapper):
136 | old_name = os.path.join(dist_package, f)
137 | new_name = os.path.join(dist_package, '{}.so'.format(f.split('.')[0]))
138 | if old_name != new_name:
139 | subprocess.run(['sudo', 'mv', old_name, new_name])
140 |
141 | def install_apertium_module(self, language: str) -> None:
142 | self._download_package(language)
143 |
144 | def install_apertium_base(self) -> None:
145 | self._install_package_source()
146 | self._download_package('apertium-all-dev')
147 |
148 | def install_wrapper(self, swig_wrapper: str) -> None:
149 | self._download_package(swig_wrapper)
150 | self._rename_wrappers()
151 |
152 |
153 | def get_installer() -> Union[Windows, Debian]:
154 | system: str = platform.system()
155 | if system == 'Windows':
156 | return Windows()
157 | elif system == 'Linux':
158 | with open('/etc/os-release') as os_release:
159 | distro_name = os_release.read()
160 | if re.search('[Dd]ebian|[Uu]buntu', distro_name) is not None:
161 | return Debian()
162 | else:
163 | raise apertium.InstallationNotSupported(distro_name)
164 | else:
165 | raise apertium.InstallationNotSupported(system)
166 |
167 |
168 | def install_apertium() -> None:
169 | installer = get_installer()
170 | installer.install_apertium_base()
171 |
172 |
173 | def install_module(module: str) -> None:
174 | apertium_module = 'apertium-{}'.format(module)
175 | installer: Union[Windows, Debian] = get_installer()
176 | installer.install_apertium_module(apertium_module)
177 | apertium._update_modes()
178 |
179 |
180 | def install_wrapper(swig_wrapper: str) -> None:
181 | installer: Union[Windows, Debian] = get_installer()
182 | installer.install_wrapper(swig_wrapper)
183 |
184 |
185 | def install_apertium_linux() -> None:
186 | """
187 | Installs apertium-* packages on Linux Platforms
188 | """
189 | if platform.system() == 'Linux':
190 | install_module('anaphora')
191 |
--------------------------------------------------------------------------------
/apertium/iso639.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 |
3 | iso_639_codes: Dict[str, str] = {
4 | 'aar': 'aa',
5 | 'abk': 'ab',
6 | 'afr': 'af',
7 | 'aka': 'ak',
8 | 'amh': 'am',
9 | 'ara': 'ar',
10 | 'arg': 'an',
11 | 'asm': 'as',
12 | 'ava': 'av',
13 | 'ave': 'ae',
14 | 'aym': 'ay',
15 | 'azb': 'az',
16 | 'aze': 'az',
17 | 'bak': 'ba',
18 | 'bam': 'bm',
19 | 'bel': 'be',
20 | 'ben': 'bn',
21 | 'bih': 'bh',
22 | 'bis': 'bi',
23 | 'bod': 'bo',
24 | 'bos': 'bs',
25 | 'bre': 'br',
26 | 'bul': 'bg',
27 | 'cat': 'ca',
28 | 'ces': 'cs',
29 | 'cha': 'ch',
30 | 'che': 'ce',
31 | 'chu': 'cu',
32 | 'chv': 'cv',
33 | 'cor': 'kw',
34 | 'cos': 'co',
35 | 'cre': 'cr',
36 | 'cym': 'cy',
37 | 'dan': 'da',
38 | 'deu': 'de',
39 | 'div': 'dv',
40 | 'dzo': 'dz',
41 | 'ell': 'el',
42 | 'eng': 'en',
43 | 'epo': 'eo',
44 | 'est': 'et',
45 | 'eus': 'eu',
46 | 'ewe': 'ee',
47 | 'fao': 'fo',
48 | 'fas': 'fa',
49 | 'fij': 'fj',
50 | 'fin': 'fi',
51 | 'fra': 'fr',
52 | 'fry': 'fy',
53 | 'ful': 'ff',
54 | 'gla': 'gd',
55 | 'gle': 'ga',
56 | 'glg': 'gl',
57 | 'glv': 'gv',
58 | 'grn': 'gn',
59 | 'guj': 'gu',
60 | 'hat': 'ht',
61 | 'hau': 'ha',
62 | 'hbs': 'sh',
63 | 'heb': 'he',
64 | 'her': 'hz',
65 | 'hin': 'hi',
66 | 'hmo': 'ho',
67 | 'hrv': 'hr',
68 | 'hun': 'hu',
69 | 'hye': 'hy',
70 | 'ibo': 'ig',
71 | 'ido': 'io',
72 | 'iii': 'ii',
73 | 'iku': 'iu',
74 | 'ile': 'ie',
75 | 'ina': 'ia',
76 | 'ind': 'id',
77 | 'ipk': 'ik',
78 | 'isl': 'is',
79 | 'ita': 'it',
80 | 'jav': 'jv',
81 | 'jpn': 'ja',
82 | 'kal': 'kl',
83 | 'kan': 'kn',
84 | 'kas': 'ks',
85 | 'kat': 'ka',
86 | 'kau': 'kr',
87 | 'kaz': 'kk',
88 | 'khm': 'km',
89 | 'kik': 'ki',
90 | 'kin': 'rw',
91 | 'kir': 'ky',
92 | 'kom': 'kv',
93 | 'kon': 'kg',
94 | 'kor': 'ko',
95 | 'kua': 'kj',
96 | 'kur': 'ku',
97 | 'lao': 'lo',
98 | 'lat': 'la',
99 | 'lav': 'lv',
100 | 'lim': 'li',
101 | 'lin': 'ln',
102 | 'lit': 'lt',
103 | 'ltz': 'lb',
104 | 'lub': 'lu',
105 | 'lug': 'lg',
106 | 'mah': 'mh',
107 | 'mal': 'ml',
108 | 'mar': 'mr',
109 | 'mkd': 'mk',
110 | 'mlg': 'mg',
111 | 'mlt': 'mt',
112 | 'mon': 'mn',
113 | 'mri': 'mi',
114 | 'msa': 'ms',
115 | 'mya': 'my',
116 | 'nau': 'na',
117 | 'nav': 'nv',
118 | 'nbl': 'nr',
119 | 'nde': 'nd',
120 | 'ndo': 'ng',
121 | 'nep': 'ne',
122 | 'nld': 'nl',
123 | 'nno': 'nn',
124 | 'nob': 'nb',
125 | 'nor': 'no',
126 | 'nya': 'ny',
127 | 'oci': 'oc',
128 | 'oji': 'oj',
129 | 'ori': 'or',
130 | 'orm': 'om',
131 | 'oss': 'os',
132 | 'pan': 'pa',
133 | 'pes': 'fa',
134 | 'pli': 'pi',
135 | 'pol': 'pl',
136 | 'por': 'pt',
137 | 'pus': 'ps',
138 | 'que': 'qu',
139 | 'roh': 'rm',
140 | 'ron': 'ro',
141 | 'run': 'rn',
142 | 'rus': 'ru',
143 | 'sag': 'sg',
144 | 'san': 'sa',
145 | 'sin': 'si',
146 | 'slk': 'sk',
147 | 'slv': 'sl',
148 | 'sme': 'se',
149 | 'smo': 'sm',
150 | 'sna': 'sn',
151 | 'snd': 'sd',
152 | 'som': 'so',
153 | 'sot': 'st',
154 | 'spa': 'es',
155 | 'sqi': 'sq',
156 | 'srd': 'sc',
157 | 'srp': 'sr',
158 | 'ssw': 'ss',
159 | 'sun': 'su',
160 | 'swa': 'sw',
161 | 'swe': 'sv',
162 | 'tah': 'ty',
163 | 'tam': 'ta',
164 | 'tat': 'tt',
165 | 'tel': 'te',
166 | 'tgk': 'tg',
167 | 'tgl': 'tl',
168 | 'tha': 'th',
169 | 'tir': 'ti',
170 | 'ton': 'to',
171 | 'tsn': 'tn',
172 | 'tso': 'ts',
173 | 'tuk': 'tk',
174 | 'tur': 'tr',
175 | 'twi': 'tw',
176 | 'uig': 'ug',
177 | 'ukr': 'uk',
178 | 'urd': 'ur',
179 | 'uzb': 'uz',
180 | 'ven': 've',
181 | 'vie': 'vi',
182 | 'vol': 'vo',
183 | 'wln': 'wa',
184 | 'wol': 'wo',
185 | 'xho': 'xh',
186 | 'yid': 'yi',
187 | 'yor': 'yo',
188 | 'zha': 'za',
189 | 'zho': 'zh',
190 | 'zul': 'zu',
191 | }
192 |
--------------------------------------------------------------------------------
/apertium/mode_search.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | from typing import Dict, List, Pattern, Tuple, Union
4 |
5 | from apertium.utils import to_alpha3_code
6 |
7 |
8 | def is_loop(dirpath: str, rootpath: str, real_root: Union[None, str] = None) -> bool:
9 | """
10 | Args:
11 | dirpath (str)
12 | rootpath (str)
13 | real_root (Union[None, str])
14 |
15 | Returns:
16 | bool
17 | """
18 | if os.path.islink(dirpath):
19 | # We just descended into a directory via a symbolic link
20 | # Check if we're referring to a directory that is
21 | # a parent of our nominal directory
22 |
23 | if not real_root:
24 | real_root = os.path.abspath(os.path.realpath(rootpath))
25 |
26 | relative = os.path.relpath(dirpath, rootpath)
27 | nominal_path = os.path.join(real_root, relative)
28 | real_path = os.path.abspath(os.path.realpath(dirpath))
29 |
30 | for nominal, real in zip(nominal_path.split(os.sep),
31 | real_path.split(os.sep)):
32 | if nominal != real:
33 | return False
34 | else:
35 | return True
36 | else:
37 | return False
38 |
39 |
40 | def search_path(rootpath: str, include_pairs: bool = True) -> Dict[str, List[Tuple[str, str, str]]]:
41 | """
42 | Args:
43 | rootpath (str)
44 | include_pairs (bool)
45 |
46 | Returns:
47 | Dict[str, List[Tuple[str, str, str]]]
48 | """
49 | lang_code: str = r'[a-z]{2,3}(?:_[A-Za-z]+)?'
50 | type_re: Dict[str, Pattern[str]] = {
51 | 'analyzer': re.compile(r'(({0}(-{0})?)-(an)?mor(ph)?)\.mode'.format(lang_code)),
52 | 'generator': re.compile(r'(({0}(-{0})?)-gener[A-z]*)\.mode'.format(lang_code)),
53 | 'pair': re.compile(r'({0})-({0})\.mode'.format(lang_code)),
54 | 'tagger': re.compile(r'(({0}(-{0})?)-tagger[A-z]*)\.mode'.format(lang_code)),
55 | }
56 | modes: Dict[str, List[Tuple[str, str, str]]] = {
57 | 'analyzer': [],
58 | 'generator': [],
59 | 'pair': [],
60 | 'tagger': [],
61 | }
62 |
63 | real_root = os.path.abspath(os.path.realpath(rootpath))
64 |
65 | for dirpath, dirnames, files in os.walk(rootpath, followlinks=True):
66 | if is_loop(dirpath, rootpath, real_root):
67 | dirnames[:] = []
68 | continue
69 | for filename in [f for f in files if f.endswith('.mode')]:
70 | for mtype, regex in type_re.items():
71 | m = regex.match(filename)
72 | if m:
73 | if mtype != 'pair':
74 | modename = m.group(1) # e.g. en-es-anmorph
75 | langlist = [to_alpha3_code(l) for l in m.group(2).split('-')]
76 | lang_pair = '-'.join(langlist) # e.g. en-es
77 | dir_of_modes = os.path.dirname(dirpath)
78 | mode = (dir_of_modes,
79 | modename,
80 | lang_pair)
81 | modes[mtype].append(mode)
82 | elif include_pairs:
83 | lang_src, lang_trg = m.groups()
84 | mode = (os.path.join(dirpath, filename),
85 | to_alpha3_code(lang_src),
86 | to_alpha3_code(lang_trg))
87 | modes[mtype].append(mode)
88 | return modes
89 |
--------------------------------------------------------------------------------
/apertium/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apertium/apertium-python/81b10e509f65fcf1c77a0c2080f398897d3629c2/apertium/py.typed
--------------------------------------------------------------------------------
/apertium/tagger/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import Dict, List
3 |
4 | from streamparser import LexicalUnit, parse
5 |
6 | import apertium
7 | from apertium.utils import execute_pipeline, parse_mode_file, to_alpha3_code
8 |
9 |
10 | class Tagger:
11 | """
12 | Attributes:
13 | tagger_cmds (Dict[str, List[List[str]]])
14 | lang (str)
15 | """
16 |
17 | def __init__(self, lang: str) -> None:
18 | """
19 | Args:
20 | lang (str)
21 | """
22 | self.tagger_cmds: Dict[str, List[List[str]]] = {}
23 | self.lang: str = to_alpha3_code(lang)
24 | if self.lang not in apertium.taggers:
25 | raise apertium.ModeNotInstalled(self.lang)
26 | else:
27 | self.path, self.mode = apertium.taggers[self.lang]
28 |
29 | def _get_commands(self) -> List[List[str]]:
30 | """
31 | Returns:
32 | List[List[str]]
33 | """
34 | if self.lang not in self.tagger_cmds:
35 | mode_path, mode = apertium.taggers[self.lang]
36 | abs_mode_path = os.path.join(mode_path, 'modes', '{}.mode'.format(mode))
37 | self.tagger_cmds[self.lang] = parse_mode_file(abs_mode_path)
38 |
39 | return self.tagger_cmds[self.lang]
40 |
41 | @staticmethod
42 | def _postproc_text(result: str) -> List[LexicalUnit]:
43 | """
44 | Postprocesses the input
45 |
46 | Args:
47 | result (str)
48 |
49 | Returns:
50 | List[LexicalUnit]
51 | """
52 | lexical_units = list(parse(result))
53 | return lexical_units
54 |
55 | def tag(self, in_text: str, formatting: str = 'txt') -> List[LexicalUnit]:
56 | """
57 | Runs apertium to tagger the input
58 |
59 | Args:
60 | in_text (str)
61 | formatting (str)
62 |
63 | Returns:
64 | List[LexicalUnit]
65 | """
66 | self._get_commands()
67 | deformatter = ['apertium-des{}'.format(formatting), '-n']
68 | if deformatter not in self.tagger_cmds[self.lang]:
69 | self.tagger_cmds[self.lang].insert(0, deformatter)
70 | result = execute_pipeline(in_text, self.tagger_cmds[self.lang])
71 | return self._postproc_text(result)
72 |
73 |
74 | def tag(lang: str, in_text: str, formatting: str = 'txt') -> List[LexicalUnit]:
75 | """
76 | Args:
77 | lang (str)
78 | in_text (str)
79 | formatting (str)
80 |
81 | Returns:
82 | List[LexicalUnit]
83 | """
84 | tagger = Tagger(lang)
85 | return tagger.tag(in_text, formatting)
86 |
--------------------------------------------------------------------------------
/apertium/translation/__init__.py:
--------------------------------------------------------------------------------
1 | import re
2 | from subprocess import CalledProcessError, PIPE, Popen
3 | from typing import Dict, List, Optional, Tuple, Union
4 |
5 | import apertium # noqa: E402
6 | from apertium.utils import execute_pipeline, parse_mode_file, to_alpha3_code # noqa: E402
7 |
8 |
9 | class Translator:
10 | """
11 | Attributes:
12 | translation_cmds (Dict[Tuple[str, str], List[List[str]]])
13 | lang1 (str)
14 | lang2 (str)
15 | """
16 |
17 | def __init__(self, lang1: str, lang2: str) -> None:
18 | """
19 | Args:
20 | lang1 (str)
21 | lang2 (str)
22 | """
23 | self.translation_cmds: Dict[Tuple[str, str], List[List[str]]] = {}
24 | self.lang1 = lang1
25 | self.lang2 = lang2
26 |
27 | def _get_commands(self, lang1: str, lang2: str) -> List[List[str]]:
28 | """
29 | Args:
30 | lang1 (str)
31 | lang2 (str)
32 |
33 | Returns:
34 | List[List[str]]
35 | """
36 | if (lang1, lang2) not in self.translation_cmds:
37 | mode_path = apertium.pairs['%s-%s' % (lang1, lang2)]
38 | self.translation_cmds[(lang1, lang2)] = parse_mode_file(mode_path)
39 | return self.translation_cmds[(lang1, lang2)]
40 |
41 | def _get_format(self, formatting: Optional[str], deformat: Optional[str], reformat: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
42 | """
43 | Args:
44 | formatting (Optional[str])
45 | deformat (Optional[str])
46 | reformat (Optional[str])
47 |
48 | Returns:
49 | Tuple[Optional[str], Optional[str]]
50 | """
51 | if formatting:
52 | deformat = 'apertium-des' + formatting
53 | reformat = 'apertium-re' + formatting
54 | else:
55 | if 'apertium-des' not in deformat: # type: ignore
56 | deformat = 'apertium-des' + deformat # type: ignore
57 | if 'apertium-re' not in reformat: # type: ignore
58 | reformat = 'apertium-re' + reformat # type: ignore
59 |
60 | return deformat, reformat
61 |
62 | def _check_ret_code(self, proc: Popen, cmd: str) -> None:
63 | """
64 | Args:
65 | proc (Popen)
66 | cmd (str)
67 | """
68 | if proc.returncode != 0:
69 | raise CalledProcessError(proc.returncode, cmd)
70 |
71 | def _validate_formatters(self, deformat: Optional[str], reformat: Optional[str]) -> Tuple[Union[str, bool], Union[str, bool]]:
72 | """
73 | Args:
74 | deformat (Optional[str])
75 | reformat (Optional[str])
76 |
77 | Returns:
78 | Tuple[Union[str, bool], Union[str, bool]]
79 | """
80 | def valid1(elt: Optional[str], lst: List[Union[str, bool]]) -> Union[str, bool]:
81 | """
82 | Args:
83 | elt (Optional[str])
84 | lst (List[Union[str, bool]])
85 |
86 | Returns:
87 | Union[str, bool]
88 | """
89 | if elt in lst:
90 | return elt
91 | else:
92 | return lst[0]
93 |
94 | # First is fallback:
95 | deformatters: List[Union[str, bool]] = [
96 | 'apertium-deshtml',
97 | 'apertium-destxt',
98 | 'apertium-desrtf',
99 | False,
100 | ]
101 | reformatters: List[Union[str, bool]] = [
102 | 'apertium-rehtml-noent',
103 | 'apertium-rehtml',
104 | 'apertium-retxt',
105 | 'apertium-rertf',
106 | False,
107 | ]
108 | return valid1(deformat, deformatters), valid1(reformat, reformatters)
109 |
110 | def _get_deformat(self, deformat: str, text: str) -> str:
111 | """
112 | Args:
113 | deformat (str)
114 | text (str)
115 |
116 | Returns:
117 | str
118 | """
119 | if deformat:
120 | proc_deformat = Popen(deformat, stdin=PIPE, stdout=PIPE)
121 | proc_deformat.stdin.write(bytes(text, 'utf-8'))
122 | deformatted = proc_deformat.communicate()[0]
123 | deformatted = deformatted.decode()
124 | self._check_ret_code(proc_deformat, deformat)
125 | else:
126 | deformatted = bytes(text, 'utf-8')
127 | res = str(deformatted)
128 | return res
129 |
130 | def _get_reformat(self, reformat: str, text: str) -> bytes:
131 | """
132 | Args:
133 | reformat (str)
134 | text (str)
135 |
136 | Returns:
137 | str
138 | """
139 | result: bytes
140 | if reformat:
141 | proc_reformat = Popen(reformat, stdin=PIPE, stdout=PIPE)
142 | proc_reformat.stdin.write(bytes(text, 'utf-8'))
143 | result = proc_reformat.communicate()[0]
144 | self._check_ret_code(proc_reformat, reformat)
145 | else:
146 | result = re.sub(rb'\0$', b'', text) # type: ignore
147 | return result
148 |
149 | def translate(self, text: str, mark_unknown: bool = False, formatting: Optional[str] = None, deformat: str = 'txt', reformat: str = 'txt') -> str:
150 | """
151 | Args:
152 | text (str)
153 | mark_unknown (bool)
154 | formatting (Optional[str])
155 | deformat (str)
156 | reformat (str)
157 |
158 | Returns:
159 | str
160 | """
161 | if '{}-{}'.format(*map(to_alpha3_code, [self.lang1, self.lang2])) in apertium.pairs:
162 | pair = map(to_alpha3_code, [self.lang1, self.lang2])
163 | else:
164 | raise apertium.ModeNotInstalled()
165 |
166 | if pair is not None:
167 | lang1, lang2 = pair
168 | cmds = list(self._get_commands(lang1, lang2))
169 | unsafe_deformat, unsafe_reformat = self._get_format(formatting, deformat, reformat)
170 | deformater, reformater = self._validate_formatters(unsafe_deformat, unsafe_reformat)
171 | deformatted = self._get_deformat(str(deformater), text)
172 | output = execute_pipeline(deformatted, cmds)
173 | result: bytes = self._get_reformat(str(reformater), output).strip()
174 | return result.decode()
175 |
176 |
177 | def translate(lang1: str, lang2: str, text: str, mark_unknown: bool = False,
178 | formatting: Optional[str] = None, deformat: str = 'txt', reformat: str = 'txt') -> str:
179 | """
180 | Args:
181 | lang1: str
182 | lang2: str
183 | text (str)
184 | mark_unknown (bool)
185 | formatting (Optional[str])
186 | deformat (str)
187 | reformat (str)
188 |
189 | Returns:
190 | str
191 | """
192 | translator = Translator(lang1, lang2)
193 | return translator.translate(text, mark_unknown, formatting, deformat, reformat)
194 |
--------------------------------------------------------------------------------
/apertium/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import platform
3 | import subprocess
4 | import sys
5 | import tempfile
6 | from typing import Any, Dict, List, Tuple, Union
7 |
8 | try:
9 | if platform.system() == 'Linux':
10 | sys.path.append('/usr/lib/python3/dist-packages')
11 |
12 | import apertium_core
13 | import apertium_lex_tools
14 | import lttoolbox
15 | import constraint_grammar
16 | wrappers_available = True
17 | except ImportError:
18 | wrappers_available = False
19 |
20 | import apertium # noqa: F401
21 | from apertium.iso639 import iso_639_codes
22 |
23 | iso639_codes_inverse = {v: k for k, v in iso_639_codes.items()}
24 | escape_chars = b'[]{}?^$@\\'
25 | special_chars_map = {i: '\\' + chr(i) for i in escape_chars}
26 | initialized_wrappers = {} # type: Dict[Union[List[str], Tuple[str, ...]], Union[FSTProc, LRX, Any]]
27 |
28 | if wrappers_available:
29 | class FSTProc(lttoolbox.FST): # type: ignore
30 | def __init__(self, dictionary_path: str, arg: str) -> None:
31 | super().__init__(dictionary_path)
32 | if arg == '-g':
33 | self.initGeneration()
34 | elif arg == '-b':
35 | self.initBiltrans()
36 | elif arg == '-p':
37 | self.initPostgeneration()
38 | else:
39 | self.initAnalysis()
40 |
41 | class LRX(apertium_lex_tools.LRXProc): # type: ignore
42 | def __init__(self, dictionary_path: str) -> None:
43 | super().__init__(dictionary_path)
44 | self.init()
45 |
46 |
47 | def to_alpha3_code(code: str) -> str:
48 | """
49 | Args:
50 | code (str)
51 |
52 | Returns:
53 | str
54 | """
55 | if '_' in code:
56 | code, variant = code.split('_')
57 | return '%s_%s' % ((iso639_codes_inverse[code], variant) if code in iso639_codes_inverse else (code, variant))
58 | else:
59 | return iso639_codes_inverse[code] if code in iso639_codes_inverse else code
60 |
61 |
62 | def deformatter(text: str) -> str:
63 | """
64 | This function is a text format processor. Data should be passed
65 | through this processor before being piped to lt-proc.
66 | """
67 | return '{}[][\n]'.format(text.translate(special_chars_map))
68 |
69 |
70 | def handle_command_with_wrapper(command: Union[List[str], Tuple[str, ...]], end: bytes) -> Tuple[bytes, bool]:
71 | """
72 | Executes the given command via wrappers
73 | """
74 | used_wrapper = True
75 | if command not in initialized_wrappers.keys() and wrappers_available:
76 | if 'lt-proc' == command[0]:
77 | lt_proc_command, dictionary_path = command[:-1], command[-1]
78 | arg = command[1]
79 | lttoolbox.LtLocale.tryToSetLocale()
80 | fst = FSTProc(dictionary_path, arg) # type: FSTProc
81 | if not fst.valid():
82 | raise ValueError('FST Invalid')
83 | initialized_wrappers[command] = fst
84 | elif 'lrx-proc' == command[0]:
85 | dictionary_path = command[-1]
86 | apertium_lex_tools.LtLocale.tryToSetLocale()
87 | lrx = LRX(dictionary_path) # type: LRX
88 | initialized_wrappers[command] = lrx
89 | elif 'apertium-transfer' == command[0]:
90 | transfer = apertium_core.ApertiumTransfer(command[-2], command[-1])
91 | initialized_wrappers[command] = transfer
92 | elif 'apertium-interchunk' == command[0]:
93 | interchunk = apertium_core.ApertiumInterchunk(command[-2], command[-1])
94 | initialized_wrappers[command] = interchunk
95 | elif 'apertium-postchunk' == command[0]:
96 | postchunk = apertium_core.ApertiumPostchunk(command[-2], command[-1])
97 | initialized_wrappers[command] = postchunk
98 | elif 'cg-proc' == command[0]:
99 | dictionary_path = command[-1]
100 | cg = constraint_grammar.CGProc(dictionary_path)
101 | initialized_wrappers[command] = cg
102 |
103 | input_file = tempfile.NamedTemporaryFile(delete=False, mode='w')
104 | output_file = tempfile.NamedTemporaryFile(delete=False)
105 |
106 | text = end.decode()
107 | input_file.write(text)
108 | input_file.close()
109 |
110 | if 'lt-proc' == command[0]:
111 | fst = initialized_wrappers[command]
112 | lt_proc_command, dictionary_path, arg = command[:-1], command[-1], command[1]
113 | fst.lt_proc(lt_proc_command, input_file.name, output_file.name)
114 | elif 'lrx-proc' == command[0]:
115 | apertium_lex_tools.LtLocale.tryToSetLocale()
116 | lrx = initialized_wrappers[command]
117 | lrx.lrx_proc(command, input_file.name, output_file.name)
118 | elif 'apertium-transfer' == command[0]:
119 | transfer = initialized_wrappers[command]
120 | transfer.transfer_text(command, input_file.name, output_file.name)
121 | elif 'apertium-interchunk' == command[0]:
122 | interchunk = initialized_wrappers[command]
123 | interchunk.interchunk_text(command, input_file.name, output_file.name)
124 | elif 'apertium-postchunk' == command[0]:
125 | postchunk = initialized_wrappers[command]
126 | postchunk.postchunk_text(command, input_file.name, output_file.name)
127 | elif 'apertium-pretransfer' == command[0]:
128 | apertium_core.pretransfer(command, input_file.name, output_file.name)
129 | elif 'apertium-tagger' == command[0]:
130 | command = list(command)
131 | command += [input_file.name, output_file.name]
132 | tuple_command = tuple(command)
133 | apertium_core.ApertiumTagger(tuple_command)
134 | elif 'cg-proc' == command[0]:
135 | cg = initialized_wrappers[command]
136 | cg.cg_proc(command, input_file.name, output_file.name)
137 | else:
138 | used_wrapper = False
139 |
140 | if used_wrapper:
141 | output_file.seek(0)
142 | end = output_file.read()
143 | output_file.close()
144 |
145 | os.remove(input_file.name)
146 | os.remove(output_file.name)
147 |
148 | return end, used_wrapper
149 |
150 |
151 | def execute_pipeline(inp: str, commands: List[List[str]]) -> str:
152 | """
153 | Executes the given list of commands and returns the final output
154 |
155 | Returns:
156 | str
157 | """
158 | end = inp.encode()
159 | for command in commands:
160 | # On Windows, a NamedTemporaryFile with delete=True can only be opened once.
161 | # Since the file is opened both by Python and the C++ SWIG wrappers, we use
162 | # delete=False and manually delete the file.
163 | used_wrapper = True
164 | tuple_command = tuple(command) # type: Tuple[str, ...]
165 | if 'apertium-destxt' == command[0]:
166 | output = deformatter(end.decode())
167 | end = output.encode()
168 | continue
169 |
170 | if wrappers_available:
171 | end, used_wrapper = handle_command_with_wrapper(tuple_command, end)
172 | if not wrappers_available or not used_wrapper:
173 | apertium.logger.warning('Calling subprocess %s', command[0])
174 | proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
175 | end, _ = proc.communicate(end)
176 | return end.decode()
177 |
178 |
179 | def parse_mode_file(mode_path: str) -> List[List[str]]:
180 | """
181 | Args:
182 | mode_path (str)
183 |
184 | Returns:
185 | List[List[str]]
186 | """
187 | with open(mode_path) as mode_file:
188 | mode_str = mode_file.read().strip()
189 | if mode_str:
190 | commands = []
191 | for cmd in mode_str.strip().split('|'):
192 | # TODO: we should make language pairs install
193 | # modes.xml instead; this is brittle (what if a path
194 | # has | or ' in it?)
195 | cmd = cmd.replace('$2', '').replace('$1', '-g')
196 | commands.append([c.strip("'") for c in cmd.split()])
197 | return commands
198 | else:
199 | raise apertium.ModeNotInstalled(mode_path)
200 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SOURCEDIR = source
8 | BUILDDIR = build
9 |
10 | # Put it first so that "make" without argument is like "make help".
11 | help:
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13 |
14 | .PHONY: help Makefile
15 |
16 | # Catch-all target: route all unknown targets to Sphinx using the new
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 | %: Makefile
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/docs/source/about.md:
--------------------------------------------------------------------------------
1 | About
2 | =====
3 |
4 | Introduction
5 | ------------
6 |
7 | - The code-base is in development for the GSoC '19 project called **Apertium API in Python**.
8 | - The Apertium core modules are written in C++.
9 | - The existing codebase has `Subprocess` and [SWIG](http://www.swig.org/) wrapper implementations of the higher level functions of Apertium and CG modules.
10 |
11 | About the Existing Code Base
12 | -----------------------------
13 |
14 | - The existing codebase has `Subprocess` and SWIG wrapper implementations of the basic functions used by Apertium modules.
15 |
16 | Contributing
17 | ------------
18 |
19 | - Issue Tracker:
20 | - Source Code:
21 | - Run unit tests with `./setup.py test`.
22 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from recommonmark.parser import CommonMarkParser
5 | from recommonmark.transform import AutoStructify
6 |
7 | sys.path.insert(0, os.path.abspath('../..'))
8 |
9 |
10 | # -- Project information -----------------------------------------------------
11 |
12 | project = 'apertium-python'
13 | copyright = '2018, Andi Qu'
14 | author = 'Andi Qu'
15 |
16 | # The short X.Y version
17 | version = ''
18 | # The full version, including alpha/beta/rc tags
19 | release = ''
20 |
21 | extensions = [
22 | 'sphinx.ext.autodoc',
23 | 'sphinx.ext.intersphinx',
24 | 'sphinx.ext.viewcode',
25 | 'sphinx.ext.githubpages',
26 | 'sphinx.ext.napoleon',
27 | ]
28 | napoleon_include_special_with_doc = False
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['ntemplates']
32 |
33 | # The suffix(es) of source filenames.
34 | # You can specify multiple suffix as a list of string:
35 | #
36 |
37 | source_parsers = {
38 | '.md': CommonMarkParser,
39 | }
40 |
41 | source_suffix = ['.rst', '.md']
42 |
43 | # The master toctree document.
44 | master_doc = 'index'
45 |
46 | # The language for content autogenerated by Sphinx. Refer to documentation
47 | # for a list of supported languages.
48 | #
49 | # This is also used if you do content translation via gettext catalogs.
50 | # Usually you set "language" from the command line for these cases.
51 | language = None
52 |
53 | # List of patterns, relative to source directory, that match files and
54 | # directories to ignore when looking for source files.
55 | # This pattern also affects html_static_path and html_extra_path.
56 | exclude_patterns = []
57 |
58 | # The name of the Pygments (syntax highlighting) style to use.
59 | pygments_style = None
60 |
61 |
62 | # -- Options for HTML output -------------------------------------------------
63 |
64 | # The theme to use for HTML and HTML Help pages. See the documentation for
65 | # a list of builtin themes.
66 | #
67 | html_theme = 'alabaster'
68 |
69 | # Theme options are theme-specific and customize the look and feel of a theme
70 | # further. For a list of options available for each theme, see the
71 | # documentation.
72 | #
73 | # html_theme_options = {}
74 |
75 | # Add any paths that contain custom static files (such as style sheets) here,
76 | # relative to this directory. They are copied after the builtin static files,
77 | # so a file named "default.css" will overwrite the builtin "default.css".
78 | html_static_path = ['nstatic']
79 |
80 | # Custom sidebar templates, must be a dictionary that maps document names
81 | # to template names.
82 | #
83 | # The default sidebars (for documents that don't match any pattern) are
84 | # defined by theme itself. Builtin themes are using these templates by
85 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
86 | # 'searchbox.html']``.
87 | #
88 | # html_sidebars = {}
89 |
90 |
91 | # -- Options for HTMLHelp output ---------------------------------------------
92 |
93 | # Output file base name for HTML help builder.
94 | htmlhelp_basename = 'apertium-pythondoc'
95 |
96 |
97 | # -- Options for LaTeX output ------------------------------------------------
98 |
99 | latex_elements = {
100 | # The paper size ('letterpaper' or 'a4paper').
101 | #
102 | # 'papersize': 'letterpaper',
103 |
104 | # The font size ('10pt', '11pt' or '12pt').
105 | #
106 | # 'pointsize': '10pt',
107 |
108 | # Additional stuff for the LaTeX preamble.
109 | #
110 | # 'preamble': '',
111 |
112 | # Latex figure (float) alignment
113 | #
114 | # 'figure_align': 'htbp',
115 | }
116 |
117 | # Grouping the document tree into LaTeX files. List of tuples
118 | # (source start file, target name, title,
119 | # author, documentclass [howto, manual, or own class]).
120 | latex_documents = [
121 | (master_doc, 'apertium-python.tex', 'apertium-python Documentation',
122 | 'Andi Qu', 'manual'),
123 | ]
124 |
125 |
126 | # -- Options for manual page output ------------------------------------------
127 |
128 | # One entry per manual page. List of tuples
129 | # (source start file, name, description, authors, manual section).
130 | man_pages = [
131 | (master_doc, 'apertium-python', 'apertium-python Documentation',
132 | [author], 1)
133 | ]
134 |
135 |
136 | # -- Options for Texinfo output ----------------------------------------------
137 |
138 | # Grouping the document tree into Texinfo files. List of tuples
139 | # (source start file, target name, title, author,
140 | # dir menu entry, description, category)
141 | texinfo_documents = [
142 | (master_doc, 'apertium-python', 'apertium-python Documentation',
143 | author, 'apertium-python', 'One line description of project.',
144 | 'Miscellaneous'),
145 | ]
146 |
147 |
148 | # -- Options for Epub output -------------------------------------------------
149 |
150 | # Bibliographic Dublin Core info.
151 | epub_title = project
152 |
153 | # The unique identifier of the text. This can be a ISBN number
154 | # or the project homepage.
155 | #
156 | # epub_identifier = ''
157 |
158 | # A unique identification for the text.
159 | #
160 | # epub_uid = ''
161 |
162 | # A list of files that should not be packed into the epub file.
163 | epub_exclude_files = ['search.html']
164 |
165 |
166 | # -- Extension configuration -------------------------------------------------
167 |
168 | # -- Options for intersphinx extension ---------------------------------------
169 |
170 | # Example configuration for intersphinx: refer to the Python standard library.
171 | intersphinx_mapping = {'https://docs.python.org/': None}
172 |
173 |
174 | # reCommonMark Setup
175 | def setup(app):
176 | app.add_config_value('recommonmark_config', {
177 | 'auto_toc_tree_section': 'Contents',
178 | }, True)
179 | app.add_transform(AutoStructify)
180 |
--------------------------------------------------------------------------------
/docs/source/index.md:
--------------------------------------------------------------------------------
1 | Apertium + Python
2 | =================
3 |
4 | Contents
5 | --------
6 |
7 | * [About](about.md)
8 | * [Installation](install.md)
9 | * [Usage](usage.md)
10 |
11 | Apertium
12 | --------
13 |
14 | ```eval_rst
15 | .. automodule:: apertium
16 | :members:
17 | ```
18 |
19 | Analysis
20 | --------
21 |
22 | ```eval_rst
23 | .. automodule:: apertium.analysis
24 | :members:
25 | ```
26 |
27 | Generation
28 | ----------
29 |
30 | ```eval_rst
31 | .. automodule:: apertium.generation
32 | :members:
33 | ```
34 |
35 | Tagger
36 | ------
37 |
38 | ```eval_rst
39 | .. automodule:: apertium.tagger
40 | :members:
41 | ```
42 |
43 | Translation
44 | -----------
45 |
46 | ```eval_rst
47 | .. automodule:: apertium.translation
48 | :members:
49 | ```
50 |
51 | Indices and tables
52 | ------------------
53 |
54 | ```eval_rst
55 | * :ref:`genindex`
56 | * :ref:`modindex`
57 | * :ref:`search`
58 | ```
59 |
--------------------------------------------------------------------------------
/docs/source/install.md:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 | From PyPI
5 | -----------
6 |
7 | - Install from PyPI by running
8 | ```
9 | pip install apertium
10 | ```
11 |
12 | - For developers, `pipenv` can be used to install the development dependencies and enter a shell with them:
13 | ```
14 | pip install pipenv
15 | pipenv install --dev
16 | pipenv shell
17 | ```
18 |
19 | Apertium packages can be installed from python interpreter as well
20 | - Install `apertium-all-dev`
21 | ```python
22 | import apertium
23 | apertium.installer.install_apertium()
24 | ```
25 |
26 | - Install language packages
27 | ```python
28 | import apertium
29 | apertium.installer.install_module('eng')
30 | apertium.installer.install_module('en-es')
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/source/usage.md:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | - For multiple invocations `Method 1` is more performant, as the dictionary needs to be loaded only once.
5 |
6 | Analysis
7 | --------
8 |
9 | Performing Morphological Analysis
10 |
11 | - Method 1: Create an `Analyzer` object and call its `analyze` method.
12 |
13 | ```python
14 | In [1]: import apertium
15 | In [2]: a = apertium.Analyzer('en')
16 | In [3]: a.analyze('cats')
17 | Out[3]: [cats/cat, ./.]
18 | ```
19 |
20 | - Method 2: Calling `analyze()` directly.
21 |
22 | ```python
23 | In [1]: import apertium
24 | In [2]: apertium.analyze('en', 'cats')
25 | Out[2]: cats/cat
26 | ```
27 |
28 | Generation
29 | ----------
30 |
31 | Performing Morphological Generation
32 |
33 | - Method 1: Create a `Generator` object and call its `generate` method.
34 |
35 | ```python
36 | In [1]: import apertium
37 | In [2]: g = apertium.Generator('en')
38 | In [3]: g.generate('-cat$')
39 | Out[3]: 'cats'
40 | ```
41 |
42 | - Method 2: Calling `generate()` directly.
43 |
44 | ```python
45 | In [1]: import apertium
46 | In [2]: apertium.generate('en', '-cat$')
47 | Out[2]: 'cats'
48 | ```
49 |
50 | Installing more modes from other language data
51 | ----------------------------------------------
52 |
53 | One can also install modes by providing the path to the lang-data using this simple function::
54 |
55 | In [1]: import apertium
56 | In [2]: apertium.append_pair_path('..')
57 |
58 | Install language packages with `apertium.installer.install_module(language_name)`. For example `apertium-eng` can be installed by executing `apertium.installer.install_module('eng')`
59 |
60 | Tagger
61 | ------
62 |
63 | Performing Tagging::
64 |
65 | - Method 1: Create a `Tagger` object and call its `tag` method.
66 |
67 | ```python
68 | In [1]: import apertium
69 | In [2]: tagger = apertium.Tagger('eng')
70 | In [3]: tagger.tag('cats')
71 | Out[3]: [cats/cat]
72 | ```
73 |
74 | - Method 2: Calling `tag()` directly.
75 |
76 | ```python
77 | In [1]: import apertium
78 | In [2]: apertium.tag('en', 'cats')
79 | Out[2]: [cats/cat]
80 | ```
81 |
82 | Translation
83 | -----------
84 |
85 | Performing Translations::
86 |
87 | - Method 1: Create a `Translator` object and call its `translate` method.
88 |
89 | ```python
90 | In [1]: import apertium
91 | In [2]: t = apertium.Translator('eng', 'spa')
92 | In [3]: t.translate('cats')
93 | Out[3]: 'Gatos'
94 | ```
95 |
96 | - Method 2: Calling `translate()` directly.
97 |
98 | ```python
99 | In [1]: import apertium
100 | In [2]: apertium.translate('en', 'spa', 'cats')
101 | Out[2]: 'Gatos'
102 | ```
103 |
104 | Installing more modes from other language data
105 | ----------------------------------------------
106 |
107 | - One can also install modes by providing the path to the lang-data:
108 |
109 | In [1]: import apertium
110 | In [2]: apertium.append_pair_path('..')
111 |
112 | - Install language packages with `apertium.installer.install_module(language_name)`. For example `apertium-eng` can be installed by executing `apertium.installer.install_module('eng')`
113 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from os import path
4 | import platform
5 | import re
6 | from typing import List
7 |
8 | from setuptools import find_packages, setup
9 | from setuptools.command.develop import develop
10 | from setuptools.command.install import install
11 |
12 |
13 | def install_binaries() -> None:
14 | import apertium
15 |
16 | apertium.installer.nightly = True
17 | apertium.installer.install_apertium()
18 | apertium.installer.install_module('eng')
19 | apertium.installer.install_module('eng-spa')
20 |
21 | def kaz_tat_install():
22 | apertium.installer.nightly = False
23 | ubuntu = apertium.installer.Debian()
24 | if platform.system() == 'Linux':
25 | ubuntu._install_package_source()
26 | apertium.installer.install_module('kaz-tat')
27 | apertium.installer.nightly = True
28 | if platform.system() == 'Linux':
29 | ubuntu._install_package_source()
30 | kaz_tat_install()
31 |
32 | apertium.installer.install_wrapper('python3-apertium-core')
33 | apertium.installer.install_wrapper('python3-apertium-lex-tools')
34 | apertium.installer.install_wrapper('python3-cg3')
35 | apertium.installer.install_wrapper('python3-lttoolbox')
36 | apertium.installer.install_apertium_linux()
37 |
38 |
39 | class CustomInstallCommand(install):
40 | def run(self) -> None:
41 | install.run(self)
42 | install_binaries()
43 |
44 |
45 | class CustomDevelopCommand(develop):
46 | def run(self) -> None:
47 | develop.run(self)
48 | install_binaries()
49 |
50 |
51 | def find_details(find_value: str, file_paths: List[str]) -> str:
52 | pwd = path.abspath(path.dirname(__file__))
53 | with open(path.join(pwd, *file_paths), 'r') as input_file:
54 | match = re.search(r"^__{}__ = ['\"]([^'\"]*)['\"]".format(find_value), input_file.read(), re.M)
55 | if match:
56 | return match.group(1)
57 | raise RuntimeError('Unable to find {} string.'.format(find_value))
58 |
59 |
60 | setup(
61 | name='apertium',
62 | author=find_details('author', ['apertium', '__init__.py']),
63 | author_email='sushain@skc.name',
64 | license=find_details('license', ['apertium', '__init__.py']),
65 | version=find_details('version', ['apertium', '__init__.py']),
66 | keywords='apertium machine translation linguistics',
67 | description='Apertium core modules available in Python',
68 | classifiers=[
69 | 'Development Status :: 3 - Alpha',
70 | 'Topic :: Text Processing :: Linguistic',
71 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
72 | 'Intended Audience :: Science/Research',
73 | 'Intended Audience :: Developers',
74 | 'Programming Language :: Python :: 3.6',
75 | 'Programming Language :: Python :: 3.7',
76 | 'Programming Language :: Python :: 3.8',
77 | 'Programming Language :: Python :: 3 :: Only',
78 | 'Operating System :: Microsoft :: Windows',
79 | 'Operating System :: POSIX',
80 | ],
81 | long_description=open(path.join(path.abspath(path.dirname(__file__)), 'README.md')).read(),
82 | long_description_content_type='text/markdown; charset=UTF-8',
83 | platforms=['Debian', 'Windows'],
84 | url='https://github.com/apertium/apertium-python',
85 | python_requires='>=3.5',
86 | setup_requires=[
87 | 'apertium-streamparser==5.0.2',
88 | ],
89 | install_requires=[
90 | 'apertium-streamparser==5.0.2',
91 | ],
92 | test_suite='tests',
93 | package_data={'apertium': ['py.typed']},
94 | packages=find_packages(exclude=['tests']),
95 | cmdclass={
96 | 'develop': CustomDevelopCommand,
97 | 'install': CustomInstallCommand,
98 | },
99 | )
100 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import importlib.util
2 | import os
3 | import platform
4 | import shutil
5 | import sys
6 | from typing import Dict, Tuple
7 | import unittest
8 |
9 | from streamparser import known, SReading
10 |
11 | base_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
12 | sys.path.append(base_path)
13 |
14 | import apertium # noqa: E402
15 |
16 |
17 | class TestApertiumInit(unittest.TestCase):
18 | def test_append_pair_path(self):
19 | apertium.pair_paths = []
20 | apertium.analyzers = {} # type: Dict[str, Tuple[str, str]]
21 | apertium.generators = {} # type: Dict[str, Tuple[str, str]]
22 | apertium.taggers = {} # type: Dict[str, Tuple[str, str]]
23 | apertium.pairs = {} # type: Dict[str, str]
24 | apertium.append_pair_path('/usr/share/apertium')
25 | apertium.append_pair_path('/usr/local/share/apertium')
26 | if platform.system() == 'Windows':
27 | apertium.windows_update_path()
28 | if not apertium.pair_paths or not apertium.analyzers or not apertium.generators or not apertium.taggers or not apertium.pairs:
29 | self.fail('Pair Paths not added to the list/dictionary')
30 |
31 | def test_windows_update_path(self):
32 | if platform.system() != 'Windows':
33 | with self.assertRaises(apertium.InstallationNotSupported):
34 | apertium.windows_update_path()
35 | else:
36 | apertium.pair_paths = []
37 | apertium.analyzers = {} # type: Dict[str, Tuple[str, str]]
38 | apertium.generators = {} # type: Dict[str, Tuple[str, str]]
39 | apertium.taggers = {} # type: Dict[str, Tuple[str, str]]
40 | apertium.pairs = {} # type: Dict[str, str]
41 | apertium.windows_update_path()
42 | if not apertium.pair_paths or not apertium.analyzers or not apertium.generators or not apertium.taggers or not apertium.pairs:
43 | self.fail('Pair Paths not added to the list/dictionary')
44 |
45 | def test_windows_update_path_via_installer(self):
46 | if platform.system() != 'Windows':
47 | with self.assertRaises(apertium.InstallationNotSupported):
48 | apertium.windows_update_path()
49 | else:
50 | apertium.windows_update_path()
51 | test_binaries = TestInstallation()
52 | test_binaries.test_apertium_installer()
53 |
54 |
55 | class TestAnalyze(unittest.TestCase):
56 | def test_analyzer_en(self):
57 | analyzer = apertium.Analyzer('en')
58 | lexical_units = analyzer.analyze('cats')
59 | lexical_unit = lexical_units[0]
60 | self.assertListEqual(lexical_unit.readings, [[SReading(baseform='cat', tags=['n', 'pl'])]])
61 | self.assertEqual(lexical_unit.wordform, 'cats')
62 | self.assertEqual(lexical_unit.knownness, known)
63 |
64 | def test_analyze_en(self):
65 | lexical_units = apertium.analyze('eng', 'cats')
66 | lexical_unit = lexical_units[0]
67 | self.assertListEqual(lexical_unit.readings, [[SReading(baseform='cat', tags=['n', 'pl'])]])
68 | self.assertEqual(lexical_unit.wordform, 'cats')
69 | self.assertEqual(lexical_unit.knownness, known)
70 |
71 | def test_uninstalled_mode(self):
72 | with self.assertRaises(apertium.ModeNotInstalled):
73 | apertium.Analyzer('spa')
74 |
75 |
76 | class TestGenerate(unittest.TestCase):
77 | def test_generator_single(self):
78 | generator = apertium.Generator('en')
79 | wordform = generator.generate('^cat$')
80 | self.assertEqual(wordform, 'cats')
81 |
82 | def test_generator_multiple(self):
83 | generator = apertium.Generator('en')
84 | lexical_units = generator.generate('^cat$ ^cat$')
85 | self.assertEqual(lexical_units, 'cats cats')
86 |
87 | def test_generator_bare(self):
88 | generator = apertium.Generator('en')
89 | lexical_units = generator.generate('cat')
90 | self.assertEqual(lexical_units, 'cat')
91 |
92 | def test_generator_uninstalled_mode(self):
93 | generator = apertium.Generator('spa')
94 | with self.assertRaises(apertium.ModeNotInstalled):
95 | generator.generate('cat')
96 |
97 | def test_single(self):
98 | wordform = apertium.generate('en', '^cat$')
99 | self.assertEqual(wordform, 'cats')
100 |
101 | def test_multiple(self):
102 | lexical_units = apertium.generate('en', '^cat$ ^cat$')
103 | self.assertEqual(lexical_units, 'cats cats')
104 |
105 | def test_bare(self):
106 | lexical_units = apertium.generate('en', 'cat')
107 | self.assertEqual(lexical_units, 'cat')
108 |
109 | def test_uninstalled_mode(self):
110 | with self.assertRaises(apertium.ModeNotInstalled):
111 | apertium.generate('spa', 'cat')
112 |
113 |
114 | class TestInstallation(unittest.TestCase):
115 | def test_apertium_installer(self):
116 | # This test doesn't remove existing apertium binaries.
117 | # So it is possible that apertium.installer.install_apertium() isn't working
118 | apertium.installer.install_apertium()
119 | apertium_processes = ['apertium-destxt', 'apertium-interchunk', 'apertium-postchunk',
120 | 'apertium-pretransfer', 'apertium-tagger', 'apertium-transfer',
121 | 'lrx-proc', 'lt-proc'
122 | ]
123 | for process in apertium_processes:
124 | self.assertIsNotNone(shutil.which(process), 'apertium installer not working. {} not available on system path'.format(process))
125 | break
126 |
127 | @unittest.skipIf(platform.system() == 'Windows', 'apertium binaries not available for windows')
128 | def test_install_apertium_linux(self):
129 | apertium.installer.install_apertium_linux()
130 | apertium_processes = ['apertium-anaphora',
131 | ]
132 | for process in apertium_processes:
133 | self.assertIsNotNone(shutil.which(process), 'apertium linux installer not working. {} not available on system path'.format(process))
134 | break
135 |
136 | def test_install_module(self):
137 | language = 'kir'
138 | apertium.installer.install_module(language)
139 | self.assertIn(language, apertium.analyzers, 'apetium.install_module not working')
140 |
141 | @unittest.skipIf(platform.system() == 'Windows', 'wrappers not available for windows')
142 | def test_install_wrapper(self):
143 | apertium.installer.install_wrapper('python3-lttoolbox')
144 | if platform.system() == 'Linux':
145 | sys.path.append('/usr/lib/python3/dist-packages')
146 | self.assertIsNotNone(importlib.util.find_spec('lttoolbox'), 'Wrapper not installed')
147 |
148 |
149 | class TestSubProcess(unittest.TestCase):
150 | def setUpModule():
151 | self._wrappers_available = apertium.utils.wrappers_available
152 | apertium.utils.wrappers_available = False
153 |
154 | def tearDownModule():
155 | apertium.utils.wrappers_available = self._wrappers_available
156 |
157 | def test_analyze_en_subprocess(self):
158 | test_analyze = TestAnalyze()
159 | test_analyze.test_analyzer_en()
160 | test_analyze.test_analyze_en()
161 |
162 | def test_generate_en_subprocess(self):
163 | test_generate = TestGenerate()
164 | test_generate.test_generator_single()
165 | test_generate.test_generator_multiple()
166 | test_generate.test_generator_bare()
167 | test_generate.test_single()
168 | test_generate.test_multiple()
169 | test_generate.test_bare()
170 |
171 | def test_translate_en_es_subprocess(self):
172 | test_translate = TestTranslate()
173 | test_translate.test_translator_en_spa()
174 | test_translate.test_en_spa()
175 |
176 | def test_tagger_en_subprocess(self):
177 | test_tagger = TestTagger()
178 | test_tagger.test_tagger_en()
179 | test_tagger.test_tag_en()
180 |
181 |
182 | class TestTranslate(unittest.TestCase):
183 | def test_translator_en_spa(self):
184 | translator = apertium.Translator('eng', 'spa')
185 | translated = translator.translate('cats')
186 | self.assertEqual(translated, 'Gatos')
187 |
188 | def test_en_spa(self):
189 | translated = apertium.translate('eng', 'spa', 'cats')
190 | self.assertEqual(translated, 'Gatos')
191 |
192 | def test_en_spa_formatting(self):
193 | translated = apertium.translate('eng', 'spa', 'cats', formatting='txt')
194 | self.assertEqual(translated, 'Gatos')
195 |
196 | def test_kaz_tat(self):
197 | translated = apertium.translate('kaz', 'tat', 'мысық')
198 | self.assertEqual(translated, 'мәче')
199 |
200 | def test_kaz_tat_formatting(self):
201 | translated = apertium.translate('kaz', 'tat', 'мысық', formatting='txt')
202 | self.assertEqual(translated, 'мәче')
203 |
204 | def test_translator_kaz_tat(self):
205 | translator = apertium.Translator('kaz', 'tat')
206 | translated = translator.translate('мысық')
207 | self.assertEqual(translated, 'мәче')
208 |
209 |
210 | class TestTagger(unittest.TestCase):
211 | def test_tagger_en(self):
212 | tagger = apertium.Tagger('en')
213 | lexical_units = tagger.tag('cats')
214 | lexical_unit = lexical_units[0]
215 | self.assertListEqual(lexical_unit.readings, [[SReading(baseform='cat', tags=['n', 'pl'])]])
216 | self.assertEqual(lexical_unit.wordform, 'cats')
217 | self.assertEqual(lexical_unit.knownness, known)
218 |
219 | def test_tag_en(self):
220 | lexical_units = apertium.tag('eng', 'cats')
221 | lexical_unit = lexical_units[0]
222 | self.assertListEqual(lexical_unit.readings, [[SReading(baseform='cat', tags=['n', 'pl'])]])
223 | self.assertEqual(lexical_unit.wordform, 'cats')
224 | self.assertEqual(lexical_unit.knownness, known)
225 |
226 | def test_uninstalled_mode(self):
227 | with self.assertRaises(apertium.ModeNotInstalled):
228 | apertium.Tagger('spa')
229 |
--------------------------------------------------------------------------------