pax_global_header00006660000000000000000000000064146525137430014524gustar00rootroot0000000000000052 comment=f70521b4e5f268b238f71740378c011c5738a9d9 keep-2.11/000077500000000000000000000000001465251374300123735ustar00rootroot00000000000000keep-2.11/.gitignore000066400000000000000000000013071465251374300143640ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ .vscode keep-2.11/.pep8speaks.yml000066400000000000000000000002501465251374300152540ustar00rootroot00000000000000pycodestyle: max-line-length: 100 # Default is 79 in PEP8 ignore: # Errors and warnings to ignore - E266 - E303 - E128 - W504 keep-2.11/LICENSE.md000066400000000000000000000020601465251374300137750ustar00rootroot00000000000000MIT License Copyright (c) 2017 Himanshu Mishra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. keep-2.11/README.md000066400000000000000000000077121465251374300136610ustar00rootroot00000000000000![logo](https://raw.githubusercontent.com/OrkoHunter/keep/master/data/logo.png#gh-light-mode-only) ![logo](./data/logo-white.png#gh-dark-mode-only) [![PyPI](https://img.shields.io/pypi/v/keep)](https://github.com/orkohunter/keep/releases) ![PyPI - Downloads](https://img.shields.io/pypi/dm/keep) # A Meta CLI toolkit > Your personal shell command keeper ## Why? Writwick Wraj loves using the command line. Writwick googles \"How to do X in terminal?\" and multiple forums and blog posts finally provide him the magical _command_ for the rescue. Problem Solved ! Fast forward couple weeks, Writwick has to do X in terminal, again. Wraj remembers solving this few weeks ago. Let him do a reverse-i-search with _Ctrl+R_. Nope, can\'t remember sh\*t. Browser search history? 25 web pages found matching _X_. Argh! Writwik finally finds the solution. From this time Writwik starts writing the commands somewhere online for the future. Wait, why shouldn\'t he keep the command in his terminal itself if this is only place where he\'ll ever have use it? ## Features - Save a new command with a brief description - Search the saved commands using powerful patterns - Save the commands as a secret GitHub gist - Use `keep push` and `keep pull` to sync the commands between GitHub gist and other computers. **ProTip : Save the commands you usually forget in ssh sessions and sync it with your local machine.** ## Installation $ pip3 install keep Use Python 3.6 or later. You can install pip3 using apt-get as `sudo apt install python3-pip`. ## Usage Usage: keep [OPTIONS] COMMAND [ARGS]... Keep and view shell commands in terminal only. Read more at https://github.com/orkohunter/keep Options: -v, --verbose Enables verbose mode. --help Show this message and exit. Commands: edit Edit a saved command. github_token Register a GitHub Token to use GitHub Gists as a backup. grep Searches for a saved command. init Initializes the CLI. list Shows the saved commands. new Saves a new command. pull Pull commands from saved GitHub gist. push Push commands to a secret GitHub gist. rm Deletes a saved command. run Executes a saved command. update Check for an update of Keep. [See the detailed usage and tutorial.](https://github.com/OrkoHunter/keep/blob/master/tutorial.md) ### Command-line Completion To enable command-line completion (TAB completion) follow these steps for the shell of your choice #### bash 1. Create a directory in your home directory called `.bash` mkdir -p $HOME/.bash 2. Copy [completion/keep.bash](https://github.com/OrkoHunter/keep/blob/master/completions/keep.bash) to `$HOME/.bash/keep` curl -SLo "$HOME/.bash/keep" "https://raw.githubusercontent.com/OrkoHunter/keep/master/completions/keep.bash" 3. Add the following lines to `$HOME/.bashrc` file [ -f "$HOME/.bash/keep" ] && . "$HOME/.bash/keep" #### zsh 1. Create a directory in your home called `.zsh` mkdir -p $HOME/.zsh 2. Copy [completion/keep.zsh](https://github.com/OrkoHunter/keep/blob/master/completions/keep.zsh) to `$HOME/.zsh/_keep` curl -SLo "$HOME/.zsh/_keep" "https://raw.githubusercontent.com/OrkoHunter/keep/master/completions/keep.zsh" 3. Add the following lines inside `$HOME/.zshrc` file fpath=($HOME/.zsh $fpath) autoload -Uz compinit && compinit ### Contribute This is a very young project. If you have got any suggestions for new features or improvements, please comment over [here](https://github.com/OrkoHunter/keep/issues/11). Pull Requests are most welcome ! ❤ --- Not a command line fanatic? Here are some resources for you : - - - - keep-2.11/completions/000077500000000000000000000000001465251374300147275ustar00rootroot00000000000000keep-2.11/completions/keep.bash000066400000000000000000000010171465251374300165110ustar00rootroot00000000000000#/usr/bin/env bash function _keep() { local cur prev commands COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" commands="edit github_token grep init list new pull push rm run update" case "$prev" in run|rm) mapfile -t COMPREPLY < <(compgen -W "$(keep completion --bash)" -- $cur) ;; edit) COMPREPLY=("--editor") ;; keep) COMPREPLY=( $(compgen -W "-h -v --verbose $commands" -- $cur) ) ;; esac return 0 } complete -F _keep keep keep-2.11/completions/keep.zsh000066400000000000000000000021651465251374300164050ustar00rootroot00000000000000#compdef _keep keep function _keep { local -a commands _arguments -C \ "-v[Enables verbose mode]:" \ "--verbose[Enables verbose mode]:" \ "--help[Show the help message and exit]:" \ "1: :->cmnds" \ "*::arg:->args" case $state in cmnds) commands=( "edit:Edit a saved command." "github_token:Register a GitHub Token to use GitHub Gists as a backup." "grep:Searches for a saved command." "init:Initializes the CLI." "list:Shows the saved commands." "new:Saves a new command." "pull:Pull commands from saved GitHub gist." "push:Push commands to a secret GitHub gist." "rm:Deletes a saved command." "run:Executes a saved command." "update:Check for an update of Keep." ) _describe "command" commands ;; esac case "$words[1]" in edit) _keep_edit ;; rm) _keep_commands ;; run) _keep_commands ;; esac } function _keep_edit { _arguments \ "--editor[Editor to use]" } function _keep_commands { local -a commands commands=("${(@f)$(keep completion --zsh)}") _describe "command" commands } keep-2.11/data/000077500000000000000000000000001465251374300133045ustar00rootroot00000000000000keep-2.11/data/logo-white.png000066400000000000000000000120121465251374300160640ustar00rootroot00000000000000PNG  IHDR v?sRGBIDATx^ VY.EpG6QT8 8 *₊B EhQAJU+EpA\ .c\PEu~5ޛsnM3IsN/9[B$@ $K@B.4@`ed   0 2 V2 mr+Bȅ6LBLL!B&!\&Jr @@.`%S@ I I) \h$$X@.@`ed   0 2 V2 mr+Bȅ6LBLL!B&!\&Jr @@.`%S@ I I) \h$$X@.@`ed   0 2 V2 mr+Bȅ6LBLL!B&!\&Jr @@.`%S@ I I) \h$$XX\2&q% E%3H1IEqLX/ʥ˫$թ"V9%X\eCL,+&8S#(T\L+% FOO"I=}00"$jF0r8$+$z`"9K'ʽaE0#@$ ,F.fFКE0C r ,J.f3"8 ..e)6MgB`rA03ia\j ,Z.f횂π`fʸUX\.wIjDą̼ߔt* "M1$Y0c%3?D/H{v/IW,TX\*IilEe6@[.eC.IO GE˽=(3T. I?%^X"1[˚3D.^)Z+gq [ Z.k̡rO;j=("vX\ C䒙7hrV[Iគ@Zv ro^j@eyc_wri F.6񳒾M˯ud$KJdI/?%?tGKϏiw\ڜ%L+"%eG4 }>yE;F93A. Z`*N?<)o_ϫ>ęid(q9\Gvp$?/#Q^\yIߺT^|yޝѿ坒~Po Um+}\'vG*IG_HI7x1䚐K%f\ׇ6eyאc3tN^Ot.ye#$]9-=rT;1Wmfײk-r'|ͯ ז'u2sȥ6]0cUDuNFψwMnqJIݤoDcwL/lDJ*$3?LүzhGn›ߏY0";>tqB4}HGN6͍z4OUyey@D< ^`I(eD5:?ye+p<gK7>cTԱ f\ܿѡ]p[;<("ܱ[2ӣKݤE˟Koϊpq2aM&qj9J.`Stwpۃ,%f5OzMqf'4zpRQLwvr>fۤO.\ΖŹa P &-r%ES:?6ᑖꔙ՗GĽk2Lq\{f;H=w|^vofmcCji$mύ@$$?3_,XlQLw䶗xhv:w~cn@L"yj,G]Ճ!:;y $jyrP"=r"DoEngIM:X?P*3=.6"|m|)86f(oiqE=$"V%"_rꑞ\7ڤ+"«D8`"bp K#ԝr9+އd6'IXɣT:sԔ7[8l[DDw KOV]1E{Ip̞9#Uş\7eٹGGē(3=Z{y)xzؗ}#Ve,Ef,"! i,C낧{#M$".̼E S;l߹t [hNxig_p;_Qf"3}eI?YgwM=\Ƥ\7jyrdwmn ]nP]癿^osfG;7#m۷ٜ֩ʝО Nl>WK]&ɟU9\\gHzn{w=! N`2"XFK#O.L,IdvgKf}ɞ=t=pӑ?DZmrikyfv ej> =ԋ ZhlόtkyԢr:* ͍< xLDxxvgL? xbݡg;^4e^8-,Mʥ|ʓ<9qq PsriL>[w׸km&y: ϯ_G-+7ew;Ot7;wޮ>y=ޗq 0"3"rXsK#/+}9 [ToGb<1{~ +_O'/E7fɚH/ŝʟVoaUȯ%~ 4|ޖ-ym6aԛ{\K_7?ie@XC2ZF,[aȥ"K?m~|pK? I:ЭK,B!PC`Qrъ%3v>vwJ "NjTH`qr)1~./_%pxY.7EʥG0G+Bq.~. ,V.;nģKS& vpAgCC dIDATxope>MVS(SӒ!#% j˟h;،?QQkq :uѾ)3XTA[PDRmB[g-I/'{w&|?"=gvrt$^ʿv qg2o00TSeX aa```gC#OCq00 C00 C00 Cd9(GUsy|i9Ʋ7La`bGݰWg(Emrq+ E%&:.]/,;D2 LT\z)K;g2LD\V?!CRL͸+Gcά7 I905@5dY(L Ktr eIz=@dܐ:Xg9X:ɹ3uoA"[ ^@=.@kNy:}muY[Qq]D{c H,0^\{(.^yw YK};4|^b }p- ]&N?km\^tڝS2yݍ ߫Ee3;|\փ4. x3 2| %YcEOǾ.xti{N׆5 /Ik vkoRaZH`.ү U8v+WüN`tխ)Po"Q=cGDzcp04u'oB+h^/Lʐs& {\ u|l%-'Nlp9۷u[1bE >x"K;TQ[NR+Wx݉SG frBFk%fù Yn,.=){SZF.oFi3;tC.+)إ=Wy(=qd Ψ]i^!orWQĻԇmy:0a"E}HmG0>nSqYZꄷ094;SV3 ȋ,ȭ3u~g=2Oz)*..w-O$7~MǞf >#}tRDmׁ3JהV]-LmT;.eI{S)$,Chn#9=3˰'7ghWW9UמDސʱ;%{XVc<M ;K4s-,EݣnqiyNU۫o(ʺ]!'X/8/ݮ&DšDdar[b_4ˑ5HɀU+hI鼊ՎŸVV-\>KV,@F0~e뙱ǽ= 0: cmd, desc = matches[selected] command = "$ {} :: {}".format(cmd, desc) if click.confirm("Remove\n\t{}\n\n?".format(command), default=True): utils.remove_command(cmd) click.echo('Command successfully removed!') elif matches == []: click.echo('No saved commands matches the pattern {}'.format(pattern)) else: click.echo("No commands to remove, Add one by 'keep new'. ") keep-2.11/keep/commands/cmd_run.py000066400000000000000000000037571465251374300171350ustar00rootroot00000000000000import os import click from keep import cli, utils @click.command('run', short_help='Executes a saved command.', context_settings=dict(ignore_unknown_options=True)) @click.argument('pattern', required=False) @click.argument('arguments', nargs=-1, type=click.UNPROCESSED) @click.option('--safe', is_flag=True, help='Ignore missing arguments') @click.option('-n', '--no-confirm', is_flag=True, help='Don\'t ask confirm before running') @cli.pass_context def cli(ctx, pattern, arguments, safe, no_confirm): """Executes a saved command.""" if not pattern: pattern = "(.*?s)" matches = utils.grep_commands(pattern) if matches: selected = utils.select_command(matches) if selected >= 0: cmd, desc = matches[selected] pcmd = utils.create_pcmd(cmd) raw_params, params, defaults = utils.get_params_in_pcmd(pcmd) arguments = list(arguments) kargs = {} for r, p, d in zip(raw_params, params, defaults): if arguments: val = arguments.pop(0) click.echo(f"{p}: {val}", err=True) kargs[r] = val elif safe: if d: kargs[r] = d else: p_default = d if d else None val = click.prompt(f"Enter value for '{p}'", default=p_default, err=True) kargs[r] = val final_cmd = utils.substitute_pcmd(pcmd, kargs, safe) if no_confirm: isconfirmed = True else: command = f"$ {final_cmd} :: {desc}" isconfirmed = click.confirm(f"Execute\n\t{command}\n?", default=True, err=True) if isconfirmed: os.system(final_cmd) elif matches == []: click.echo(f'No saved commands matches the pattern {pattern}', err=True) else: click.echo("No commands to run, Add one by 'keep new'. ", err=True) keep-2.11/keep/commands/cmd_update.py000066400000000000000000000005001465251374300175720ustar00rootroot00000000000000import click from keep import cli, utils, about @click.command('update', short_help='Check for an update of Keep.') @cli.pass_context def cli(ctx): """Check for an update of Keep.""" utils.check_update(ctx, forced=True) click.secho("Keep is at its latest version v{}".format(about.__version__), fg='green') keep-2.11/keep/legacy_commands/000077500000000000000000000000001465251374300164445ustar00rootroot00000000000000keep-2.11/keep/legacy_commands/removed_cmd_pull.py000066400000000000000000000010421465251374300223330ustar00rootroot00000000000000import os import click from keep import cli, utils @click.command('pull', short_help='Updates the local database with remote.') @click.option('--overwrite', is_flag=True, help='Overwrite local commands') @cli.pass_context def cli(ctx, overwrite): """Updates the local database with remote.""" credentials_path = os.path.join(os.path.expanduser('~'), '.keep', '.credentials') if not os.path.exists(credentials_path): click.echo('You are not registered.') utils.register() else: utils.pull(ctx, overwrite) keep-2.11/keep/legacy_commands/removed_cmd_push.py000066400000000000000000000006721465251374300223460ustar00rootroot00000000000000import os import click from keep import cli, utils @click.command('push', short_help='Pushes the local database to remote.') @cli.pass_context def cli(ctx): """Pushes the local database to remote.""" credentials_path = os.path.join(os.path.expanduser('~'), '.keep', '.credentials') if not os.path.exists(credentials_path): click.echo('You are not registered.') utils.register() else: utils.push(ctx) keep-2.11/keep/legacy_commands/removed_cmd_register.py000066400000000000000000000007071465251374300232120ustar00rootroot00000000000000import os import click from keep import cli, utils @click.command('register', short_help='Register user over server.') @cli.pass_context def cli(ctx): """Register user over server.""" dir_path = os.path.join(os.path.expanduser('~'), '.keep', '.credentials') if os.path.exists(dir_path): if click.confirm('[CRITICAL] Reset credentials saved in ~/.keep/.credentials ?', abort=True): os.remove(dir_path) utils.register() keep-2.11/keep/utils.py000066400000000000000000000273241465251374300150410ustar00rootroot00000000000000"""Utility functions of the cli.""" import datetime import json import os import re import random import string import sys import time import click import requests from terminaltables3 import AsciiTable from textwrap import wrap from keep import about # Directory for Keep files dir_path = os.path.join(os.path.expanduser('~'), '.keep') # URL for the API api_url = 'https://keep-cli.herokuapp.com' def check_update(ctx, forced=False): """ Check for update on pypi. Limit to 1 check per day if not forced """ try: if ctx.update_checked and not forced: return except AttributeError: update_check_file = os.path.join(dir_path, 'update_check.txt') today = datetime.date.today().strftime("%m/%d/%Y") if os.path.exists(update_check_file): date = open(update_check_file, 'r').read() else: date = [] if forced or today != date: ctx.update_checked = True date = today with open(update_check_file, 'w') as f: f.write(date) r = requests.get("https://pypi.org/pypi/keep/json").json() version = r['info']['version'] curr_version = about.__version__ if version > curr_version: click.secho("Keep seems to be outdated. Current version = " "{}, Latest version = {}".format(curr_version, version) + "\n\nPlease update with ", bold=True, fg='red') click.secho("\tpip3 --no-cache-dir install -U keep==" + str(version), fg='green') click.secho("\n\n") def first_time_use(ctx): click.secho("Detected fresh installation. Initializing environment in ~/.keep directory", fg='green') for _ in range(2): click.echo('.', nl=False) time.sleep(0.5) click.echo('.OK', nl=True) os.mkdir(dir_path) # register() sys.exit(0) def list_commands(ctx): table_data = [['Id', 'Command', 'Description', 'Alias']] no_of_columns = len(table_data[0]) commands = read_commands() for i, (cmd, fields) in enumerate(commands.items()): table_data.append([str(i + 1), '$ ' + cmd, fields['desc'], fields['alias']]) table = AsciiTable(table_data) max_width = table.table_width//3 for i in range(len(table_data) - 1): for j in range(no_of_columns): data = table.table_data[i + 1][j] if len(data) > max_width: table.table_data[i + 1][j] = '\n'.join(wrap(data, max_width)) table.inner_row_border = True print(table.table) def log(ctx, message): """Prints log when verbose set to True.""" if ctx.verbose: ctx.log(message) # def push(ctx): # credentials_file = os.path.join(dir_path, '.credentials') # credentials = json.loads(open(credentials_file, 'r').read()) # json_path = os.path.join(dir_path, 'commands.json') # credentials['commands'] = open(json_path).read() # url = api_url + '/push' # if click.confirm("This will overwrite the saved " # "commands on the server. Proceed?", default=True): # r = requests.post(url, json=credentials) # if r.status_code == 200: # click.echo("Server successfully updated.") # def pull(ctx, overwrite): # credentials_file = os.path.join(dir_path, '.credentials') # credentials = json.loads(open(credentials_file, 'r').read()) # json_path = os.path.join(dir_path, 'commands.json') # url = api_url + '/pull' # r = requests.post(url, json=credentials) # if r.status_code == 200: # commands = json.loads(r.json()['commands']) # if not overwrite: # my_commands = read_commands() # commands.update(my_commands) # if not overwrite or ( # overwrite and click.confirm( # "This will overwrite the locally saved commands. Proceed?", default=True)): # with open(json_path, 'w') as f: # f.write(json.dumps(commands)) # click.echo("Local database successfully updated.") # def register(): # if not os.path.exists(dir_path): # click.secho("\n[CRITICAL] {0} does not exits.\nPlease run 'keep init'," # " and try registering again.\n".format(dir_path), # fg="red", err=True) # sys.exit(1) # # User may not choose to register and work locally. # # Registration is required to push the commands to server # if click.confirm('Proceed to register?', abort=True, default=True): # # Verify for existing user # click.echo("Your credentials will be saved in the ~/.keep directory.") # email = click.prompt('Email', confirmation_prompt=True) # json_res = {'email': email} # click.echo('Verifying with existing users...') # r = requests.post('https://keep-cli.herokuapp.com/check-user', json=json_res) # if r.json()['exists']: # click.secho('User already exists !', fg='red') # email = click.prompt('Email', confirmation_prompt=True) # json_res = {'email': email} # r = requests.post('https://keep-cli.herokuapp.com/check-user', json=json_res) # # Generate password for the user # chars = string.ascii_letters + string.digits # password = ''.join(random.choice(chars) for _ in range(255)) # credentials_file = os.path.join(dir_path, '.credentials') # credentials = { # 'email': email, # 'password': password # } # click.secho("Generated password for " + email, fg='cyan') # # Register over the server # click.echo("Registering new user ...") # json_res = { # 'email': email, # 'password': password # } # r = requests.post('https://keep-cli.herokuapp.com/register', json=json_res) # if r.status_code == 200: # click.secho("User successfully registered !", fg='green') # # Save the credentials into a file # with open(credentials_file, 'w+') as f: # f.write(json.dumps(credentials)) # click.secho(password, fg='cyan') # click.secho("Credentials file saved at ~/.keep/.credentials.json", fg='green') # sys.exit(0) def remove_command(cmd): commands = read_commands() if cmd in commands: del commands[cmd] write_commands(commands) else: click.echo('Command - {} - does not exist.'.format(cmd)) def save_command(cmd, desc, alias=""): json_path = os.path.join(dir_path, 'commands.json') commands = {} if os.path.exists(json_path): commands = json.loads(open(json_path, 'r').read()) fields = {'desc': desc, 'alias': alias} commands[cmd] = fields with open(json_path, 'w') as f: f.write(json.dumps(commands)) def read_commands(): json_path = os.path.join(dir_path, 'commands.json') if not os.path.exists(json_path): return None commands = json.loads(open(json_path, 'r').read()) return commands def write_commands(commands): json_path = os.path.join(dir_path, 'commands.json') with open(json_path, 'w') as f: f.write(json.dumps(commands)) def grep_commands(pattern): commands = read_commands() result = None if commands: result = [] for i, (cmd, fields) in enumerate(commands.items()): desc = fields['desc'] alias = fields['alias'] if pattern.isdigit() and pattern == str(i + 1): result.clear() result.append((cmd, desc)) break else: if alias == pattern and alias.strip() != "": result.clear() result.append((cmd, desc)) break if re.search(pattern, cmd + " :: " + desc): result.append((cmd, desc)) continue # Show if all the parts of the pattern are in one command/desc keywords_len = len(pattern.split()) i_keyword = 0 for keyword in pattern.split(): if keyword.lower() in cmd.lower() or keyword.lower() in desc.lower(): i_keyword += 1 if i_keyword == keywords_len: result.append((cmd, desc)) return result def select_command(commands): click.echo("", err=True) for idx, command in enumerate(commands): cmd, desc = command click.secho(f" {idx + 1} \t", nl=False, fg='yellow', err=True) click.secho(f" {cmd} :: {desc}", fg='green', err=True) selection = 1 while True and len(commands) > 1: click.echo("", err=True) selection = click.prompt( f"Select a command [1-{len(commands)}] (0 to cancel)", type=int, err=True ) if selection in range(len(commands) + 1): break click.echo("Number is not in range", err=True) return selection - 1 def edit_commands(commands, editor=None, edit_header=""): edit_msg = [edit_header] for cmd, fields in commands.items(): desc, alias = fields['desc'], fields['alias'] cmd = json.dumps(cmd) desc = json.dumps(desc) alias = json.dumps(alias) edit_msg.append("{} :: {} :: {}".format(cmd, alias, desc)) edited = click.edit('\n'.join(edit_msg), editor=editor) command_regex = re.compile(r'(\".*\")\s*::\s*(\".*\")\s*::\s*(\".*\")') new_commands = {} if edited: for line in edited.split('\n'): if (line.startswith('#') or line == ""): continue re_match = command_regex.search(line) if re_match and len(re_match.groups()) == 3: cmd, alias, desc = re_match.groups() try: cmd = json.loads(cmd) desc = json.loads(desc) alias = json.loads(alias) except ValueError: click.echo("Error parsing json from edit file.") return None new_commands[cmd] = {'desc': desc, 'alias': alias} else: click.echo("Could not read line '{}'".format(line)) return new_commands def format_commands(commands): res = [] for cmd, fields in commands.items(): desc, alias = fields['desc'], fields['alias'] res.append("$ {} :: {} :: {}".format(cmd, alias, desc)) return res def create_pcmd(command): class KeepCommandTemplate(string.Template): default_sep = '=' idpattern = r'[_a-z][_a-z0-9{}]*'.format(default_sep) def __init__(self, template): super().__init__(template) return KeepCommandTemplate(command) def get_params_in_pcmd(pcmd): patt = pcmd.pattern params = [] defaults = [] raw = [] for match in re.findall(patt, pcmd.template): var = match[1] or match[2] svar = var.split(pcmd.default_sep) p, d = svar[0], pcmd.default_sep.join(svar[1:]) if p and p not in params: raw.append(var) params.append(p) defaults.append(d) return raw, params, defaults def substitute_pcmd(pcmd, kargs, safe=False): if safe: return pcmd.safe_substitute(**kargs) else: return pcmd.substitute(**kargs) def get_github_token(): token_file_path = os.path.join(dir_path, '.credentials') token = None if os.path.exists(token_file_path): with open(token_file_path) as token_file: token = json.load(token_file) if not token or 'token' not in token or not token['token']: click.secho('Github access token not found :(', fg='red') click.secho('Use the "keep github_token" command to store the token first', fg='red') return None return token keep-2.11/pyproject.toml000066400000000000000000000011211465251374300153020ustar00rootroot00000000000000[project] name = "keep" version = "2.11" description = "Personal shell command keeper" readme = "README.md" authors = [ { name = "Himanshu Mishra", email = "himanshu.mishra.kgp@gmail.com" }, ] dependencies = ["PyGithub", "click", "requests", "terminaltables3"] requires-python = ">=3.8" [project.urls] Homepage = "https://github.com/orkohunter/keep" Downloads = "https://github.com/orkohunter/keep/archive/master.zip" [project.scripts] keep = "keep.cli:cli" [tool.flit.sdist] include = ["completions/"] [build-system] requires = ["flit_core>=3.4"] build-backend = "flit_core.buildapi" keep-2.11/release.sh000077500000000000000000000002651465251374300143550ustar00rootroot00000000000000# NOTE: Make sure to update version number in pyproject.toml # Install reps needed # pip install twine wheel build rm -rf build/* rm -rf dist/* python -m build twine upload dist/* keep-2.11/tutorial.md000066400000000000000000000130141465251374300145570ustar00rootroot00000000000000# Tutorial - Initialize Keep with `keep init`. This creates a `.keep` directory in your home folder. - Start saving commands using `keep new`. - Once you have saved couple of commands, start using `keep grep`. - This is not exactly similar to `keep list | grep` as `keep grep` search is more powerful. - Use `keep github_token` to register a GitHub token for gist as backup. It is then stored inside your `~/.keep/.credentials` file. - Now it's time to store the commands on github gists. Use `keep push`. - **NOTE: keep creates a secret GitHub gist. They can still be accessed using the direct URL of the gist.** - If you have got another computer, install `keep` on it. Do `keep init`. Copy your `~/.keep/.credentials` over to that computer in the same location, or follow `keep github_token`. - Do `keep pull` to retrieve all the saved commands. - Have fun ! # Commands
edit
Edit the saved commands and their descriptions.
grep "PATTERN"
This is used to search for saved commands.

Argument passed to keep grep is interpreted in two ways. First, it is interpreted as a regular expression and if it matches any command or description, then the search is displayed. If this fails, the argument is then splitted and the individule words of the argument are searched in the commands and the descriptions. If all the splits of the argument are found in a command or a discription, the result is displayed.
init
Initializes the CLI.

The CLI is supposed to be installed if there exists a .keep direcotry in the home directory. Once installed, this command is not supposed to be used unless the user intends to reset the local database of keep.
list
Shows the saved commands.

Keep uses tabulate library to show the result. If the command length is long, the table may look like in need of wrapping. But increasing the terminal width works for the best.
new
Saves new command locally.

If the command already exists, it updates the previously stored description.
pull
Updates the local commands with saved GitHub gist.

Once registered, this command fetches your database saved on the remote (GitHub gist) and updates the local storage.
push
Updates GitHub gist with local commands.

This command updates the remote (GitHub gist) with the local storage. Consider using this command before storing a new command on some other computer without pulling the latest changes.
github_token
Register a GitHub token.

Register a GitHub token to use push and pull commands with a GitHub gist.
rm
Deletes a command.

Input the exact command you have saved and this removes it from the local storage. If the user uses push after removing any command, the same will be overwritten on the remote.
run "PATTERN".
Executes a saved command.

Pattern works in the same way as in keep grep
update
Checks for an update on pypi.
# Pro Tip - Commands with parameters When saving a new command with `keep new`, if a word is prefixed with `$` while saving a command with Keep, it is treated as a variable and Keep asks for an input. _Examples:_ - Adding parameters through Keep input ```bash $ keep run "tar" 1 $ tar zxvf $tarfile -C $dest :: Extract tar content to destination Enter value for 'tarfile': /path/to/tar Enter value for 'dest': /my/folder/ Execute $ tar zxvf /path/to/tar -C /my/folder/ :: Extract tar content to destination ? [Y/n]: ``` - Parameters can also be passed directly into commands. ```bash $ keep run "grep" /path/to/dir "data[0-9]+" "> file" 1 $ grep -irnw $dir -e $pattern $out :: Look for a regex pattern inside files dir: /path/to/dir pattern: data[0-9]+ out: > file Execute $ grep -irnw /path/to/dir -e file_[0-9]+ > file :: Look for a regex pattern inside files ? [Y/n]: ``` - If any required parameter is missed out, Keep will ask its input separately. ```bash $ keep run "grep" /path/to/dir "data[0-9]+" 1 $ grep -irnw $dir -e $pattern $out :: Look for a regex pattern inside files dir: /path/to/dir pattern: data[0-9]+ Enter value for 'out': Execute $ grep -irnw /path/to/dir -e data[0-9]+ :: Look for a regex pattern inside files ? [Y/n]: ``` - Disable confirm message before run ```bash $ keep run "tar" -n 1 $ tar zxvf $tarfile -C $dest :: Extract tar content to destination Enter value for 'tarfile': /path/to/tar Enter value for 'dest': /my/folder/ ```