├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── demo.gif
├── pyproject.lock
├── pyproject.toml
├── scrmbl
├── __init__.py
├── cli.py
└── main.py
└── tests
├── __init__.py
├── chars.txt
├── lipsum.txt
├── test_cli.py
└── test_scrmbl.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # macos
2 | .DS_Store
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
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 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 |
63 | # Flask stuff:
64 | instance/
65 | .webassets-cache
66 |
67 | # Scrapy stuff:
68 | .scrapy
69 |
70 | # Sphinx documentation
71 | docs/_build/
72 |
73 | # PyBuilder
74 | target/
75 |
76 | # Jupyter Notebook
77 | .ipynb_checkpoints
78 |
79 | # IPython
80 | profile_default/
81 | ipython_config.py
82 |
83 | # pyenv
84 | .python-version
85 |
86 | # celery beat schedule file
87 | celerybeat-schedule
88 |
89 | # SageMath parsed files
90 | *.sage.py
91 |
92 | # Environments
93 | .env
94 | .venv
95 | env/
96 | venv/
97 | ENV/
98 | env.bak/
99 | venv.bak/
100 |
101 | # Spyder project settings
102 | .spyderproject
103 | .spyproject
104 |
105 | # Rope project settings
106 | .ropeproject
107 |
108 | # mkdocs documentation
109 | /site
110 |
111 | # mypy
112 | .mypy_cache/
113 | .dmypy.json
114 | dmypy.json
115 |
116 | .vscode/
117 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.5"
4 | - "3.6"
5 | - "3.7-dev"
6 | before_install:
7 | - pip install poetry
8 | install:
9 | - poetry develop
10 | script:
11 | - flake8 .
12 | - coverage run --source scrmbl -m pytest -v
13 | - coverage report
14 | - coveralls
15 | before_deploy:
16 | - poetry build
17 | deploy:
18 | - provider: releases
19 | api_key: $GITHUB_TOKEN
20 | file_glob: true
21 | file: dist/*
22 | skip_cleanup: true
23 | on:
24 | tags: true
25 | python: 3.6
26 | - provider: pypi
27 | user: $PYPI_USERNAME
28 | password: $PYPI_PASSWORD
29 | skip_cleanup: true
30 | on:
31 | tags: true
32 | python: 3.6
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Etienne Napoleone
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scrmbl
2 |
3 | Library and CLI for "scrambled" printing in terminal.
4 |
5 | Have you ever wanted to print your text like some corny action movies?
6 |
7 |
8 |
9 |
10 |
11 | note: the `%` are coming from my tty recorder
12 |
13 | ## Requirements
14 |
15 | - Tested on Python >= 3.5
16 |
17 | ## Install
18 |
19 | ### CLI
20 |
21 | ```
22 | pip3 install --user scrmbl
23 | ```
24 |
25 | ### Library
26 |
27 | Using pip in a virtualenv.
28 |
29 | ```bash
30 | pip install scrmbl
31 | ```
32 |
33 | Using Poetry:
34 |
35 | ```bash
36 | poetry add scrmbl
37 | ```
38 |
39 | Using Pipenv:
40 |
41 | ```bash
42 | pipenv install scrmbl
43 | ```
44 |
45 | ## Usage
46 |
47 | Refer to the gif to see the effect
48 |
49 | ### CLI
50 |
51 | ```
52 | Usage: scrmbl [OPTIONS] [MESSAGE]
53 |
54 | Scrmbl print the given message.
55 |
56 | Options:
57 | -s, --speed FLOAT Time in seconds between prints. Default: 0.05
58 | -i, --iterations INTEGER Number of iterations per character. Default: 2
59 | -c, --charset FILE Set of chars to scramble.
60 | --version Show the version and exit.
61 | --help Show this message and exit.
62 | ```
63 |
64 | Can also read from stdin.
65 |
66 | ```bash
67 | ls -lrtha | scrmbl
68 | ```
69 |
70 | ## Library
71 |
72 | ```python
73 | >>> import scrmbl
74 |
75 | >>> scrmbl.echo('09:30pm, Washington, NSA HEADQUARTERS')
76 | '09:30pm, Washington, NSA HEADQUARTERS'
77 |
78 | # handle multiline
79 | >>> scrmbl.echo('09:30pm, Washington\nNSA HEADQUARTERS')
80 | '02:56am, New-York'
81 | 'FBI HEADQUARTERS'
82 |
83 | # custom settings:
84 | # charset = String of characters to scramble with
85 | # speed = Time in seconds between prints
86 | # iterations = number of iterations before printing the final character
87 | >>> scrmbl.echo('NSA OFFICE', charset='ABCDefg/-', speed=0.2, iterations=6)
88 | 'CIA OFFICE'
89 | ```
90 |
91 | ## Thanks
92 |
93 | Special thanks for contributing:
94 | - [@podstava](https://github.com/podstava)
95 | - [@0jdxt](https://github.com/0jdxt)
96 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etienne-napoleone/scrmbl/e98fe2bfd378e703cfaaa7f1b9763c85d0b8177d/demo.gif
--------------------------------------------------------------------------------
/pyproject.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | category = "dev"
3 | description = "Atomic file writes."
4 | name = "atomicwrites"
5 | optional = false
6 | platform = "*"
7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
8 | version = "1.2.1"
9 |
10 | [[package]]
11 | category = "dev"
12 | description = "Classes Without Boilerplate"
13 | name = "attrs"
14 | optional = false
15 | platform = "*"
16 | python-versions = "*"
17 | version = "18.2.0"
18 |
19 | [[package]]
20 | category = "dev"
21 | description = "Python package for providing Mozilla's CA Bundle."
22 | name = "certifi"
23 | optional = false
24 | platform = "*"
25 | python-versions = "*"
26 | version = "2018.8.24"
27 |
28 | [[package]]
29 | category = "dev"
30 | description = "Universal encoding detector for Python 2 and 3"
31 | name = "chardet"
32 | optional = false
33 | platform = "*"
34 | python-versions = "*"
35 | version = "3.0.4"
36 |
37 | [[package]]
38 | category = "main"
39 | description = "Composable command line interface toolkit"
40 | name = "click"
41 | optional = false
42 | platform = "*"
43 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
44 | version = "7.0"
45 |
46 | [[package]]
47 | category = "dev"
48 | description = "Cross-platform colored terminal text."
49 | name = "colorama"
50 | optional = false
51 | platform = "UNKNOWN"
52 | python-versions = "*"
53 | version = "0.3.9"
54 |
55 | [package.requirements]
56 | platform = "win32"
57 |
58 | [[package]]
59 | category = "dev"
60 | description = "Code coverage measurement for Python"
61 | name = "coverage"
62 | optional = false
63 | platform = "*"
64 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
65 | version = "4.5.1"
66 |
67 | [[package]]
68 | category = "dev"
69 | description = "Show coverage stats online via coveralls.io"
70 | name = "coveralls"
71 | optional = false
72 | platform = "*"
73 | python-versions = "*"
74 | version = "1.5.1"
75 |
76 | [package.dependencies]
77 | coverage = ">=3.6"
78 | docopt = ">=0.6.1"
79 | requests = ">=1.0.0"
80 |
81 | [[package]]
82 | category = "dev"
83 | description = "Pythonic argument parser, that will make you smile"
84 | name = "docopt"
85 | optional = false
86 | platform = "UNKNOWN"
87 | python-versions = "*"
88 | version = "0.6.2"
89 |
90 | [[package]]
91 | category = "dev"
92 | description = "the modular source code checker: pep8, pyflakes and co"
93 | name = "flake8"
94 | optional = false
95 | platform = "*"
96 | python-versions = "*"
97 | version = "3.5.0"
98 |
99 | [package.dependencies]
100 | mccabe = ">=0.6.0,<0.7.0"
101 | pycodestyle = ">=2.0.0,<2.4.0"
102 | pyflakes = ">=1.5.0,<1.7.0"
103 |
104 | [[package]]
105 | category = "dev"
106 | description = "Internationalized Domain Names in Applications (IDNA)"
107 | name = "idna"
108 | optional = false
109 | platform = "*"
110 | python-versions = "*"
111 | version = "2.7"
112 |
113 | [[package]]
114 | category = "dev"
115 | description = "McCabe checker, plugin for flake8"
116 | name = "mccabe"
117 | optional = false
118 | platform = "*"
119 | python-versions = "*"
120 | version = "0.6.1"
121 |
122 | [[package]]
123 | category = "dev"
124 | description = "More routines for operating on iterables, beyond itertools"
125 | name = "more-itertools"
126 | optional = false
127 | platform = "*"
128 | python-versions = "*"
129 | version = "4.3.0"
130 |
131 | [package.dependencies]
132 | six = ">=1.0.0,<2.0.0"
133 |
134 | [[package]]
135 | category = "dev"
136 | description = "Object-oriented filesystem paths"
137 | name = "pathlib2"
138 | optional = false
139 | platform = "*"
140 | python-versions = "*"
141 | version = "2.3.2"
142 |
143 | [package.dependencies]
144 | six = "*"
145 |
146 | [package.requirements]
147 | python = "<3.6"
148 |
149 | [[package]]
150 | category = "dev"
151 | description = "plugin and hook calling mechanisms for python"
152 | name = "pluggy"
153 | optional = false
154 | platform = "unix"
155 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
156 | version = "0.7.1"
157 |
158 | [[package]]
159 | category = "dev"
160 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
161 | name = "py"
162 | optional = false
163 | platform = "unix"
164 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
165 | version = "1.6.0"
166 |
167 | [[package]]
168 | category = "dev"
169 | description = "Python style guide checker"
170 | name = "pycodestyle"
171 | optional = false
172 | platform = "*"
173 | python-versions = "*"
174 | version = "2.3.1"
175 |
176 | [[package]]
177 | category = "dev"
178 | description = "passive checker of Python programs"
179 | name = "pyflakes"
180 | optional = false
181 | platform = "*"
182 | python-versions = "*"
183 | version = "1.6.0"
184 |
185 | [[package]]
186 | category = "dev"
187 | description = "pytest: simple powerful testing with Python"
188 | name = "pytest"
189 | optional = false
190 | platform = "unix"
191 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
192 | version = "3.8.2"
193 |
194 | [package.dependencies]
195 | atomicwrites = ">=1.0"
196 | attrs = ">=17.4.0"
197 | more-itertools = ">=4.0.0"
198 | pluggy = ">=0.7"
199 | py = ">=1.5.0"
200 | setuptools = "*"
201 | six = ">=1.10.0"
202 |
203 | [package.dependencies.colorama]
204 | platform = "win32"
205 | version = "*"
206 |
207 | [package.dependencies.pathlib2]
208 | python = "<3.6"
209 | version = ">=2.2.0"
210 |
211 | [[package]]
212 | category = "dev"
213 | description = "Pytest plugin for measuring coverage."
214 | name = "pytest-cov"
215 | optional = false
216 | platform = "*"
217 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
218 | version = "2.6.0"
219 |
220 | [package.dependencies]
221 | coverage = ">=4.4"
222 | pytest = ">=2.9"
223 |
224 | [[package]]
225 | category = "dev"
226 | description = "Python HTTP for Humans."
227 | name = "requests"
228 | optional = false
229 | platform = "*"
230 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
231 | version = "2.19.1"
232 |
233 | [package.dependencies]
234 | certifi = ">=2017.4.17"
235 | chardet = ">=3.0.2,<3.1.0"
236 | idna = ">=2.5,<2.8"
237 | urllib3 = ">=1.21.1,<1.24"
238 |
239 | [[package]]
240 | category = "dev"
241 | description = "Python 2 and 3 compatibility utilities"
242 | name = "six"
243 | optional = false
244 | platform = "*"
245 | python-versions = "*"
246 | version = "1.11.0"
247 |
248 | [[package]]
249 | category = "dev"
250 | description = "HTTP library with thread-safe connection pooling, file post, and more."
251 | name = "urllib3"
252 | optional = false
253 | platform = "*"
254 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
255 | version = "1.23"
256 |
257 | [metadata]
258 | content-hash = "b8e8bd0c8b465ec69f789637c436c083c1ecd8934a7ac5a7fa24e7a2ecd9e09b"
259 | platform = "*"
260 | python-versions = "^3.5"
261 |
262 | [metadata.hashes]
263 | atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"]
264 | attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"]
265 | certifi = ["376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", "456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"]
266 | chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
267 | click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
268 | colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"]
269 | coverage = ["03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", "0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", "104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", "10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", "15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", "198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", "23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", "28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", "2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", "2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", "337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", "3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", "3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", "3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", "3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", "4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", "56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", "5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", "69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", "6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", "701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", "7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", "76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", "7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", "7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", "7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", "8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", "9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", "9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", "ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", "b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", "be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", "c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", "de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", "e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", "e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", "f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"]
270 | coveralls = ["ab638e88d38916a6cedbf80a9cd8992d5fa55c77ab755e262e00b36792b7cd6d", "b2388747e2529fa4c669fb1e3e2756e4e07b6ee56c7d9fce05f35ccccc913aa0"]
271 | docopt = ["49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"]
272 | flake8 = ["7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", "c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"]
273 | idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"]
274 | mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
275 | more-itertools = ["c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", "c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", "fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"]
276 | pathlib2 = ["8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", "d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"]
277 | pluggy = ["6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"]
278 | py = ["06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"]
279 | pycodestyle = ["682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", "6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"]
280 | pyflakes = ["08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", "8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"]
281 | pytest = ["7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e", "9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"]
282 | pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"]
283 | requests = ["63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", "ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"]
284 | six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"]
285 | urllib3 = ["a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"]
286 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "scrmbl"
3 | version = "1.0.0"
4 | description = "Library for scrambled printing in terminal"
5 | authors = ["Etienne Napoleone "]
6 | readme = "README.md"
7 | license = "MIT"
8 | homepage = "https://github.com/etienne-napoleone/scrmbl"
9 | repository = "https://github.com/etienne-napoleone/scrmbl"
10 | documentation = "https://github.com/etienne-napoleone/scrmbl/blob/master/README.md"
11 | keywords = ["print", "library", "movie", "action", "scramble"]
12 |
13 | [tool.poetry.dependencies]
14 | python = "^3.5"
15 | click = "^7.0"
16 |
17 | [tool.poetry.dev-dependencies]
18 | pytest = "*"
19 | flake8 = "^3.5"
20 | coveralls = "^1.5"
21 | pytest-cov = "^2.6"
22 |
23 | [tool.poetry.scripts]
24 | scrmbl = 'scrmbl.cli:cli'
25 |
--------------------------------------------------------------------------------
/scrmbl/__init__.py:
--------------------------------------------------------------------------------
1 | from scrmbl.main import echo
2 |
3 | __all__ = ['echo']
4 |
5 | __version__ = '1.0.0'
6 |
--------------------------------------------------------------------------------
/scrmbl/cli.py:
--------------------------------------------------------------------------------
1 | import re
2 | import sys
3 |
4 | import click
5 |
6 | import scrmbl
7 |
8 |
9 | @click.command()
10 | @click.argument('message', default='')
11 | @click.option('-s', '--speed', type=click.FLOAT, default=0.05,
12 | help='Time in seconds between prints. Default: 0.05')
13 | @click.option('-i', '--iterations', type=click.INT, default=2,
14 | help='Number of iterations per character. Default: 2')
15 | @click.option('-c', '--charset',
16 | type=click.Path(exists=True, allow_dash=True, dir_okay=False),
17 | help='Set of chars to scramble.')
18 | @click.version_option(version=scrmbl.__version__)
19 | def cli(message: str, speed: float, iterations: int, charset: str) -> None:
20 | """Scrmbl print the given message."""
21 | # no text input
22 | if not message:
23 | # if no stdin or just '-c -'
24 | if sys.stdin.isatty() or charset == '-':
25 | raise click.UsageError('"MESSAGE" is empty. No argument or stdin.')
26 | for line in sys.stdin:
27 | message += line
28 | message = _strip_ansi_colors(message)
29 | if charset:
30 | with click.open_file(charset) as f:
31 | charset_content = f.read()
32 | charset_content = charset_content.replace('\n', '')
33 | else:
34 | charset_content = None
35 | scrmbl.echo(message, charset=charset_content, speed=speed,
36 | iterations=iterations)
37 |
38 |
39 | def _strip_ansi_colors(message: str) -> str:
40 | """Strip ANSI color codes"""
41 | escape_codes = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
42 | return escape_codes.sub('', message)
43 |
--------------------------------------------------------------------------------
/scrmbl/main.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 | import time
4 |
5 | import click
6 |
7 | random.seed()
8 |
9 | ALL_CHARS = string.digits + string.ascii_letters + string.punctuation
10 | COLS, _ = click.get_terminal_size()
11 |
12 |
13 | def echo(message: str, charset: str = ALL_CHARS, speed: float = 0.05,
14 | iterations: int = 2) -> None:
15 | """Scrmbl print the given message."""
16 | if not charset:
17 | charset = ALL_CHARS
18 | for line in message.split('\n'):
19 | echoed = ''
20 | for char in line:
21 | for _ in range(iterations):
22 | if char != ' ':
23 | ran_char = random.choice(charset)
24 | click.echo('\r{}{}'.format(echoed, ran_char), nl=False)
25 | else:
26 | click.echo('\r{}'.format(echoed), nl=False)
27 | time.sleep(speed)
28 | echoed += char
29 | # wrap if line longer than console cols
30 | if len(echoed) >= COLS - 1:
31 | click.echo('\r' + echoed)
32 | echoed = ''
33 | if echoed:
34 | click.echo('\r' + echoed)
35 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etienne-napoleone/scrmbl/e98fe2bfd378e703cfaaa7f1b9763c85d0b8177d/tests/__init__.py
--------------------------------------------------------------------------------
/tests/chars.txt:
--------------------------------------------------------------------------------
1 | abcdefg
2 |
--------------------------------------------------------------------------------
/tests/lipsum.txt:
--------------------------------------------------------------------------------
1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras accumsan arcu quis ex mollis suscipit. Sed non orci porttitor, tincidunt mauris id, volutpat diam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam eget porta massa. Aliquam facilisis sit amet quam id sodales. Donec egestas pharetra pellentesque. Nam vestibulum dictum pharetra. Ut eget est at nibh condimentum sagittis ac a dui. Duis iaculis convallis rhoncus. Mauris maximus erat scelerisque, semper leo eget, rhoncus enim. Nulla ullamcorper in purus eget varius.
2 |
3 | Praesent imperdiet odio in massa egestas, a laoreet quam viverra. In hac habitasse platea dictumst. Suspendisse eu lobortis felis. Maecenas auctor purus risus, eget tempor turpis tincidunt at. Aliquam elementum feugiat ligula ac dictum. Aenean sit amet enim non lectus semper dignissim. Aenean posuere pretium faucibus. Suspendisse blandit est vel ultricies mollis. Sed pellentesque sem vel ullamcorper tempus. Fusce non nisi lorem. Phasellus at urna ut est viverra auctor. Vestibulum faucibus dapibus nisi nec mollis. Aenean commodo libero ipsum, ac mattis lectus dapibus vitae. Praesent urna massa, condimentum a sapien dapibus, fringilla gravida urna.
4 |
5 | Vestibulum gravida feugiat urna vitae commodo. Sed consequat mollis sem, ultrices ornare sapien dignissim ac. Mauris vestibulum ut nunc eget ultricies. Fusce vel fringilla arcu. Aenean felis ipsum, accumsan scelerisque ullamcorper ut, dignissim ac purus. Integer ornare, ligula sit amet condimentum hendrerit, turpis neque mattis erat, vitae fermentum lectus eros at urna. Sed nec dignissim felis, ac condimentum velit. Nam et tortor auctor, semper eros eget, lacinia dui. Maecenas id placerat mauris. Proin eget volutpat risus, sed ultrices tortor. Vivamus hendrerit congue dui, ut volutpat purus rhoncus sed. Maecenas consequat diam non metus dignissim faucibus. Donec sit amet posuere ex. In convallis placerat mi ac porttitor. Nullam blandit posuere mauris, et venenatis dui lobortis eget. Sed id est nec urna placerat pellentesque ac ut massa.
6 |
7 | Vivamus quis tristique metus. Fusce lobortis laoreet porta. Nam a luctus magna. Etiam eu tempor turpis, et scelerisque eros. Curabitur placerat mattis nibh sit amet lobortis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec dictum erat in justo sollicitudin suscipit. Nulla at odio quis magna posuere congue mollis non mi.
8 |
9 | Nam bibendum, lacus at molestie porttitor, risus ipsum egestas mauris, non vulputate ex erat nec diam. Nunc nec sollicitudin lectus. Aenean elementum sollicitudin augue, quis luctus dui mollis ut. Sed sed risus id enim gravida lobortis et in metus. Suspendisse eget sollicitudin risus. Praesent eget lacinia nunc. Maecenas laoreet nibh sit amet arcu hendrerit auctor. Proin in nulla in ex iaculis rutrum sed eu lectus. Vestibulum sollicitudin turpis ut odio aliquet, in gravida tellus sodales. Vestibulum vel tortor nec ante elementum venenatis in eu erat. Proin ornare semper ex a tempus. Etiam placerat orci ut tellus aliquet, a cursus nisl volutpat.
10 |
11 | Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris non ligula sapien. Donec eu felis sed diam suscipit accumsan. Etiam eu aliquet ligula, non tristique massa. Proin quis tempus mi, in hendrerit ex. Fusce vestibulum sodales mauris feugiat eleifend. Suspendisse at est accumsan, laoreet tortor quis, volutpat quam. Curabitur pellentesque nunc eu tempus cursus. Donec eget mollis enim, in fringilla eros. Curabitur malesuada eget orci eget faucibus. Sed id auctor velit. Vivamus ac diam quis eros pellentesque sollicitudin. Ut eu nisi ultricies justo finibus finibus.
12 |
13 | Morbi vel leo ut mi congue elementum. Donec scelerisque est sapien, ut malesuada arcu malesuada nec. Cras euismod ligula id leo posuere pellentesque. Ut et quam ut massa condimentum bibendum. Mauris venenatis fermentum nisl, quis convallis sem aliquam ac. Phasellus lobortis ac neque at interdum. Pellentesque non convallis magna. Vivamus ornare velit a leo interdum, a auctor nunc commodo. Donec lobortis lacus et nisl volutpat, at pharetra ex posuere.
14 |
15 | Nunc malesuada arcu non faucibus sodales. Morbi placerat quam ipsum, nec aliquam nulla vulputate at. Integer dignissim molestie faucibus. Mauris quis tortor porttitor sem pharetra ullamcorper. Vestibulum sodales varius efficitur. Quisque fringilla luctus massa vitae porttitor. Phasellus fermentum nibh eros, vitae condimentum felis finibus quis. In faucibus eget metus malesuada lobortis. Donec ut neque eget tellus imperdiet convallis nec eu tortor. Quisque felis purus, blandit eu fringilla eu, scelerisque a enim.
16 |
17 | Donec rhoncus lectus ex, vitae rhoncus sem aliquam in. Cras vitae facilisis justo. Nullam non fringilla arcu. In viverra lobortis lacus, a convallis nunc bibendum a. Nunc porta risus eu nisl varius euismod. Vestibulum sodales rutrum diam, nec elementum sapien suscipit non. Duis id turpis id justo aliquet lacinia at metus.
18 |
--------------------------------------------------------------------------------
/tests/test_cli.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from click.testing import CliRunner
4 |
5 | from scrmbl.cli import cli
6 |
7 |
8 | def test_hello_world():
9 | runner = CliRunner()
10 | result = runner.invoke(cli, ['Hello, world!'])
11 | assert result.exit_code == 0
12 | assert result.output.split('\r')[-1] == 'Hello, world!\n'
13 |
14 |
15 | def test_input_stream():
16 | with open('tests/lipsum.txt', 'r') as fin:
17 | data = fin.read()
18 | runner = CliRunner()
19 | result = runner.invoke(cli, ['-s', '0'], input=data)
20 |
21 | assert result.exit_code == 0
22 |
23 | lines = iter(result.output.split('\r'))
24 | # only retrieve longest lines i.e. full lines
25 | filt = []
26 | prev, curr = None, next(lines)
27 | while curr:
28 | if prev and len(curr) < len(prev):
29 | filt.append(prev)
30 |
31 | try:
32 | prev, curr = curr, next(lines)
33 | except StopIteration:
34 | curr = None
35 |
36 | for line in filt:
37 | assert line.split('\n')[0] in data
38 |
39 |
40 | def test_invalid_cmd():
41 | runner = CliRunner()
42 | result = runner.invoke(cli, ['-c', '-'], input='abcdefg')
43 | assert result.exit_code == 2
44 | assert 'Usage:' in result.output
45 | assert 'Error' in result.output
46 |
47 |
48 | def test_charset():
49 | runner = CliRunner()
50 | result = runner.invoke(cli, ['-c', 'tests/chars.txt', 'test'])
51 | assert result.exit_code == 0
52 | for line in result.output.split('\r'):
53 | if line:
54 | assert re.match(r'^[tes]{0,4}[abcdefg]?$', line)
55 |
--------------------------------------------------------------------------------
/tests/test_scrmbl.py:
--------------------------------------------------------------------------------
1 | import scrmbl
2 |
3 |
4 | def test_version():
5 | assert scrmbl.__version__ == '1.0.0'
6 |
7 |
8 | def test_echo_single(capsys):
9 | scrmbl.echo('this is a test')
10 | captured = capsys.readouterr().out
11 | assert captured.split('\r')[-1] == 'this is a test\n'
12 |
13 |
14 | def test_echo_multi(capsys):
15 | scrmbl.echo('this is a test\nthis is also a test')
16 | captured = capsys.readouterr().out
17 | assert captured.split('\n')[0].split('\r')[-1] == 'this is a test'
18 | assert captured.split('\r')[-1] == 'this is also a test\n'
19 |
--------------------------------------------------------------------------------