├── .DS_Store ├── .gitignore ├── README.md ├── images ├── TermClicker.gif ├── termclicker-iterm-config.png └── youtube-player.png ├── itermprint.py ├── itermreprint.py └── setup.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deploytoprod/TermClicker/9e476b3ceb732b7a427b3aeeb0339461df0002ba/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # IPython 78 | profile_default/ 79 | ipython_config.py 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | .dmypy.json 112 | dmypy.json 113 | 114 | # Pyre type checker 115 | .pyre/ 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](images/TermClicker.gif) 2 | 3 | # TermClicker 4 | 5 | No more copy/pasting commands when you are delivering demos to your audience, this can lead to human mistakes, typos, confusion and undesirable stuff you don't want in your demo session. *TermClicker* loads commands from a text file and inputs them into your terminal, line by line, every time you press your clicker button. It is not a Terminal simulator. 6 | 7 | ## Requirements 8 | 9 | *TermClicker* requires you to use [iTerm] or any other Terminal application that supports running co-processes whenever you press a key on your keyboard. 10 | 11 | ## Configuration 12 | 13 | I found easier to create a video showing how to configure, here it is: 14 | 15 | [![Watch the video](images/youtube-player.png)](https://www.youtube.com/watch?v=PDM8LZ5mSsM&feature=youtu.be) 16 | 17 | If are grumpy and do not like videos, find text instructions below. 18 | 19 | Clone the repository into a folder, open your [iTerm] App, go to Preferences > Keys, add a new Keymapping like the image below, specifying the `itermprint.py` location followed by your commands file. We usually assign the *Page Down* key because this is the key that clickers passes to the OS when you press the forward button. 20 | 21 | `/Users/freitasr/itermprint.py ` 22 | 23 | ![TermClicker Configuration on iTerm](images/termclicker-iterm-config.png) 24 | 25 | ### The commands file 26 | 27 | The desired commands should be specified in a text file and you should break them into separate lines. The first line of this file must contain the index, which is an integer that corresponds to the first command execution, every time you invoke the app, it gets the command specified on that line, prints it and increments that integer for the next execution. It is that simple. We usually start with the index `1` when we want to start a presentation. 28 | 29 | `itermreprint.py` does almost the same thing, but uses the command specified on the line above what is defined on the index and doesn't increment when it finishes printing. Ideal to use when you want to replay the last command. 30 | 31 | `^^^^` at the end of a command line tells TermClicker to ignore the rest of the line - including the new line. This is useful when you don't want TermClick to "press enter" for you at the end of a command. Also handy for commenting your commandfile! 32 | 33 | ## Caveats and observations 34 | 35 | Since the commands will be executed in a non-interactive way, make sure that everything you want to input on your CLI demo is predicted in your commands file. i.e. when you SSH an instance for the first time, you have to accept the RSA key typing `yes`, remember to put that `yes` on your commands list. 36 | 37 | 38 | [iTerm]: https://www.iterm2.com/ 39 | 40 | --- 41 | 42 | ## New feature! 43 | 44 | Easy install with: 45 | 46 | sudo pip install git+https://github.com/bobeirasa/TermClicker.git 47 | 48 | This will create console scripts `tcprint` and `tcreprint`, use these in place of the python scripts in the iTerm Keymapping. 49 | -------------------------------------------------------------------------------- /images/TermClicker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deploytoprod/TermClicker/9e476b3ceb732b7a427b3aeeb0339461df0002ba/images/TermClicker.gif -------------------------------------------------------------------------------- /images/termclicker-iterm-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deploytoprod/TermClicker/9e476b3ceb732b7a427b3aeeb0339461df0002ba/images/termclicker-iterm-config.png -------------------------------------------------------------------------------- /images/youtube-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deploytoprod/TermClicker/9e476b3ceb732b7a427b3aeeb0339461df0002ba/images/youtube-player.png -------------------------------------------------------------------------------- /itermprint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # itermprint.py 4 | # About: This application simulates users inputs on iTerm2 according to a line from an input file, addressed by the first file's line, and increments it for further uses. 5 | # Author: Rafael Lopes 6 | # Author: Rafael Lopes 7 | # Version: 1.0 8 | 9 | from sys import stdout, argv 10 | from time import sleep 11 | from random import uniform 12 | 13 | def main(): 14 | path=str(argv[1]) 15 | #print path 16 | 17 | f = open(path,'r') 18 | lines = f.readlines() 19 | f.close() 20 | 21 | head = int(lines[0]) 22 | #print head 23 | nexthead = head + 1 24 | 25 | line = lines[head] 26 | line = str(line) 27 | # if you see ^^^ remove everything after, including new line 28 | if "^^^" in line: 29 | line = line[:line.find("^^^")] 30 | 31 | for char in line: 32 | if char == '\n': 33 | sleep(uniform(0.05,1.08)) 34 | stdout.write(char) 35 | stdout.flush() 36 | sleep(uniform(0.01, 0.08)) 37 | 38 | 39 | #sys.stdout.write(lines[head]) 40 | 41 | lines[0] = lines[0].replace(str(lines[0]), str(nexthead) + "\n") 42 | 43 | with open(path, 'w') as fout: 44 | for line in lines: 45 | fout.write(line) 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /itermreprint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # itermreprint.py 4 | # About: This application simulates users inputs on iTerm2 according to a line from an input file, addressed by the first file's line, and does not increments it for further uses. 5 | # Author: Rafael Lopes 6 | # Author: Rafael Lopes 7 | # Version: 1.0 8 | 9 | from sys import stdout, argv 10 | from time import sleep 11 | from random import uniform 12 | 13 | def main(): 14 | path=str(argv[1]) 15 | #print path 16 | 17 | f = open(path,'r') 18 | lines = f.readlines() 19 | f.close() 20 | 21 | head = int(lines[0]) 22 | #print head 23 | #nexthead = head + 1 24 | 25 | line = lines[head-1] 26 | line = str(line) 27 | # if you see ^^^ remove everything after, including new line 28 | if "^^^" in line: 29 | line = line[:line.find("^^^")] 30 | 31 | for char in line: 32 | stdout.write(char) 33 | stdout.flush() 34 | sleep(uniform(0.01, 0.08)) 35 | 36 | #sys.stdout.write(lines[head]) 37 | 38 | #lines[0] = lines[0].replace(str(lines[0]), str(nexthead) + "\n") 39 | 40 | #with open(path, 'w') as fout: 41 | # for line in lines: 42 | # fout.write(line) 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="TermClicker", 8 | version="1.0.0", 9 | author="Rafael Lopes", 10 | author_email="dev@rafalop.es", 11 | description="Your CLI presentations will never be the same!", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/bobeirasa/TermClicker", 15 | py_modules=['itermprint','itermreprint'], 16 | entry_points={'console_scripts': 17 | ['tcprint = itermprint:main', 18 | 'tcreprint = itermreprint:main']} 19 | ) 20 | --------------------------------------------------------------------------------