nncli

NextCloud Notes Command Line Interface
git clone git://git.danielmoch.com/nncli.git
Log | Files | Refs | LICENSE

commit cc241e583d345ed364a46bb57f677fdf028fd660
parent 58618398420ee21845e8d1ffb1d2aa3662ae4abf
Author: Daniel Moch <daniel@danielmoch.com>
Date:   Wed, 25 Jul 2018 05:45:33 -0400

Refactor sncli into nncli

This completes the cosmetic refactoring. Next step is to refactor
nnotes_cli to make use of the NextCloud Notes API.

Diffstat:
MREADME.md | 21+++++++--------------
Mnnotes_cli/config.py | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mnnotes_cli/nncli.py | 106++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mnnotes_cli/notes_db.py | 2+-
Msetup.py | 2+-
5 files changed, 128 insertions(+), 68 deletions(-)

diff --git a/README.md b/README.md @@ -72,23 +72,16 @@ Check your OS distribution for installation packages. - flag note as markdown or not - view and edit note tags -### Screenshots - -![nncli](https://github.com/insanum/nncli/raw/master/screenshots/screenshot1.png) -![nncli](https://github.com/insanum/nncli/raw/master/screenshots/screenshot2.png) -![nncli](https://github.com/insanum/nncli/raw/master/screenshots/screenshot3.png) -![nncli](https://github.com/insanum/nncli/raw/master/screenshots/screenshot4.png) - ### HowTo ``` Usage: nncli [OPTIONS] [COMMAND] [COMMAND_ARGS] OPTIONS: -h, --help - usage help -v, --verbose - verbose output -n, --nosync - don't perform a server - sync -r, --regex - search string is a regular - expression -k <key>, --key=<key> - note key -t <title>, - --title=<title> - title of note for create (cli mode) -c <file>, - --config=<file> - config file to read from (defaults to ~/.nnclirc) + sync -r, --regex - search string is a regular + expression -k <key>, --key=<key> - note key -t <title>, + --title=<title> - title of note for create (cli mode) -c <file>, + --config=<file> - config file to read from (defaults to ~/.nnclirc) COMMANDS: <none> - console gui mode when no command specified sync - perform a full sync @@ -111,9 +104,9 @@ Check your OS distribution for installation packages. #### Configuration -The current NextCloud Notes API does not support oauth authentication so your -NextCloud Notes account information must live in the configuration file. -Please be sure to protect this file. +The current NextCloud Notes API does not support oauth authentication so +your NextCloud Notes account information must live in the configuration +file. Please be sure to protect this file. nncli pulls in configuration from the `.nnclirc` file located in your $HOME directory. At the very least, the following example `.nnclirc` diff --git a/nnotes_cli/config.py b/nnotes_cli/config.py @@ -1,3 +1,26 @@ +# +# The MIT License (MIT) +# +# Copyright (c) 2018 Daniel Moch +# +# 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. +# # Copyright (c) 2014 Eric Davis # Licensed under the MIT License @@ -8,11 +31,31 @@ class Config: def __init__(self, custom_file=None): self.home = os.path.abspath(os.path.expanduser('~')) + if 'XDG_CONFIG_HOME' in os.environ.keys(): + self.config_home = \ + os.path.join(os.environ['XDG_CONFIG_HOME'], 'nncli') + else: + self.config_home = \ + os.path.join( + os.path.expanduser('~'), + '.config', + 'nncli' + ) + if 'XDG_CACHE_HOME' in os.environ.keys(): + self.cache_home = \ + os.path.join(os.environ['XDG_CACHE_HOME'], 'nncli') + else: + self.cache_home = \ + os.path.join( + os.path.expanduser('~'), + '.cache', + 'nncli' + ) defaults = \ { - 'cfg_sn_username' : '', - 'cfg_sn_password' : '', - 'cfg_db_path' : os.path.join(self.home, '.sncli'), + 'cfg_nn_username' : '', + 'cfg_nn_password' : '', + 'cfg_db_path' : self.cache_home, 'cfg_search_tags' : 'yes', # with regex searches 'cfg_sort_mode' : 'date', # 'alpha' or 'date' 'cfg_pinned_ontop' : 'yes', @@ -26,7 +69,7 @@ def __init__(self, custom_file=None): 'cfg_max_logs' : '5', 'cfg_log_timeout' : '5', 'cfg_log_reversed' : 'yes', - 'cfg_sn_host' : 'simple-note.appspot.com', + 'cfg_nn_host' : '', 'cfg_tempdir' : '', 'kb_help' : 'h', @@ -125,23 +168,23 @@ def __init__(self, custom_file=None): if custom_file is not None: self.configs_read = cp.read([custom_file]) else: - self.configs_read = cp.read([os.path.join(self.home, '.snclirc')]) + self.configs_read = cp.read([os.path.join(self.config_home, 'config')]) - cfg_sec = 'sncli' + cfg_sec = 'nncli' if not cp.has_section(cfg_sec): cp.add_section(cfg_sec) # special handling for password so we can retrieve it by running a command - sn_password = cp.get(cfg_sec, 'cfg_sn_password', raw=True) - if not sn_password: - command = cp.get(cfg_sec, 'cfg_sn_password_eval', raw=True) + nn_password = cp.get(cfg_sec, 'cfg_nn_password', raw=True) + if not nn_password: + command = cp.get(cfg_sec, 'cfg_nn_password_eval', raw=True) if command: try: - sn_password = subprocess.check_output(command, shell=True, universal_newlines=True) + nn_password = subprocess.check_output(command, shell=True, universal_newlines=True) # remove trailing newlines to avoid requiring butchering shell commands (they can't usually be in passwords anyway) - sn_password = sn_password.rstrip('\n') + nn_password = nn_password.rstrip('\n') except subprocess.CalledProcessError as e: print('Error evaluating command for password.') print(e) diff --git a/nnotes_cli/nncli.py b/nnotes_cli/nncli.py @@ -1,3 +1,26 @@ +# +# The MIT License (MIT) +# +# Copyright (c) 2018 Daniel Moch +# +# 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. +# # Copyright (c) 2014 Eric Davis # Licensed under the MIT License @@ -12,7 +35,7 @@ from .notes_db import NotesDB, ReadError, WriteError from logging.handlers import RotatingFileHandler -class sncli: +class nncli: def __init__(self, do_server_sync, verbose=False, config_file=None): self.config = Config(config_file) @@ -31,7 +54,7 @@ def __init__(self, do_server_sync, verbose=False, config_file=None): force_full_sync = True # configure the logging module - self.logfile = os.path.join(self.config.get_config('db_path'), 'sncli.log') + self.logfile = os.path.join(self.config.get_config('db_path'), 'nncli.log') self.loghandler = RotatingFileHandler(self.logfile, maxBytes=100000, backupCount=1) self.loghandler.setLevel(logging.DEBUG) self.loghandler.setFormatter(logging.Formatter(fmt='%(asctime)s [%(levelname)s] %(message)s')) @@ -40,7 +63,7 @@ def __init__(self, do_server_sync, verbose=False, config_file=None): self.logger.addHandler(self.loghandler) self.config.logfile = self.logfile - logging.debug('sncli logging initialized') + logging.debug('nncli logging initialized') self.logs = [] @@ -56,7 +79,7 @@ def __init__(self, do_server_sync, verbose=False, config_file=None): # with hundreds of notes will cause a recursion panic under # urwid. This simple workaround gets the job done. :-) self.verbose = True - self.log('sncli database doesn\'t exist, forcing full sync...') + self.log('nncli database doesn\'t exist, forcing full sync...') self.sync_notes() self.verbose = verbose @@ -168,11 +191,11 @@ def exec_diff_on_note(self, note, old_note): def gui_header_clear(self): self.master_frame.contents['header'] = ( None, None ) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_header_set(self, w): self.master_frame.contents['header'] = ( w, None ) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_header_get(self): return self.master_frame.contents['header'][0] @@ -184,13 +207,13 @@ def gui_footer_log_clear(self): ui = self.gui_footer_input_get() self.master_frame.contents['footer'] = \ (urwid.Pile([ urwid.Pile([]), urwid.Pile([ui]) ]), None) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_footer_log_set(self, pl): ui = self.gui_footer_input_get() self.master_frame.contents['footer'] = \ (urwid.Pile([ urwid.Pile(pl), urwid.Pile([ui]) ]), None) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_footer_log_get(self): return self.master_frame.contents['footer'][0].contents[0][0] @@ -199,13 +222,13 @@ def gui_footer_input_clear(self): pl = self.gui_footer_log_get() self.master_frame.contents['footer'] = \ (urwid.Pile([ urwid.Pile([pl]), urwid.Pile([]) ]), None) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_footer_input_set(self, ui): pl = self.gui_footer_log_get() self.master_frame.contents['footer'] = \ (urwid.Pile([ urwid.Pile([pl]), urwid.Pile([ui]) ]), None) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_footer_input_get(self): return self.master_frame.contents['footer'][0].contents[1][0] @@ -216,12 +239,12 @@ def gui_footer_focus_input(self): def gui_body_clear(self): self.master_frame.contents['body'] = ( None, None ) - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_body_set(self, w): self.master_frame.contents['body'] = ( w, None ) self.gui_update_status_bar() - self.sncli_loop.draw_screen() + self.nncli_loop.draw_screen() def gui_body_get(self): return self.master_frame.contents['body'][0] @@ -276,7 +299,7 @@ def log(self, msg): if self.verbose: self.gui_footer_log_set(log_pile) - self.sncli_loop.set_alarm_in( + self.nncli_loop.set_alarm_in( int(self.config.get_config('log_timeout')), self.log_timeout, None) @@ -916,12 +939,12 @@ def gui_init_view(self, loop, view_note): self.thread_sync.start() def gui_clear(self): - self.sncli_loop.widget = urwid.Filler(urwid.Text('')) - self.sncli_loop.draw_screen() + self.nncli_loop.widget = urwid.Filler(urwid.Text('')) + self.nncli_loop.draw_screen() def gui_reset(self): - self.sncli_loop.widget = self.master_frame - self.sncli_loop.draw_screen() + self.nncli_loop.widget = self.master_frame + self.nncli_loop.draw_screen() def gui_stop(self): # don't exit if there are any notes not yet saved to the disk @@ -1044,14 +1067,14 @@ def gui(self, key): urwid.Pile([]) ]), focus_part='body') - self.sncli_loop = urwid.MainLoop(self.master_frame, + self.nncli_loop = urwid.MainLoop(self.master_frame, palette, handle_mouse=False) - self.sncli_loop.set_alarm_in(0, self.gui_init_view, + self.nncli_loop.set_alarm_in(0, self.gui_init_view, True if key else False) - self.sncli_loop.run() + self.nncli_loop.run() def cli_list_notes(self, regex, search_string): @@ -1280,7 +1303,7 @@ def SIGINT_handler(signum, frame): def usage(): print (''' Usage: - sncli [OPTIONS] [COMMAND] [COMMAND_ARGS] + nncli [OPTIONS] [COMMAND] [COMMAND_ARGS] OPTIONS: -h, --help - usage help @@ -1289,7 +1312,8 @@ def usage(): -r, --regex - search string is a regular expression -k <key>, --key=<key> - note key -t <title>, --title=<title> - title of note for create (cli mode) - -c <file>, --config=<file> - config file to read from (defaults to ~/.snclirc) + -c <file>, --config=<file> - config file to read from (defaults to + ~/.config/nncli/config) COMMANDS: <none> - console gui mode when no command specified @@ -1348,25 +1372,25 @@ def main(argv=sys.argv[1:]): usage() if not args: - sncli(sync, verbose, config).gui(key) + nncli(sync, verbose, config).gui(key) return - def sncli_start(sync=sync, verbose=verbose, config=config): - sn = sncli(sync, verbose, config) + def nncli_start(sync=sync, verbose=verbose, config=config): + sn = nncli(sync, verbose, config) if sync: sn.sync_notes() return sn if args[0] == 'sync': - sn = sncli_start(True) + sn = nncli_start(True) elif args[0] == 'list': - sn = sncli_start() + sn = nncli_start() sn.cli_list_notes(regex, ' '.join(args[1:])) elif args[0] == 'dump': - sn = sncli_start() + sn = nncli_start() if key: sn.cli_note_dump(key) else: @@ -1375,10 +1399,10 @@ def sncli_start(sync=sync, verbose=verbose, config=config): elif args[0] == 'create': if len(args) == 1: - sn = sncli_start() + sn = nncli_start() sn.cli_note_create(False, title) elif len(args) == 2 and args[1] == '-': - sn = sncli_start() + sn = nncli_start() sn.cli_note_create(True, title) else: usage() @@ -1386,17 +1410,17 @@ def sncli_start(sync=sync, verbose=verbose, config=config): elif args[0] == 'import': if len(args) == 1: - sn = sncli_start() + sn = nncli_start() sn.cli_note_import(False) elif len(args) == 2 and args[1] == '-': - sn = sncli_start() + sn = nncli_start() sn.cli_note_import(True) else: usage() elif args[0] == 'export': - sn = sncli_start() + sn = nncli_start() if key: sn.cli_note_export(key) else: @@ -1407,7 +1431,7 @@ def sncli_start(sync=sync, verbose=verbose, config=config): if not key: usage() - sn = sncli_start() + sn = nncli_start() sn.cli_note_edit(key) elif args[0] == 'trash' or args[0] == 'untrash': @@ -1415,7 +1439,7 @@ def sncli_start(sync=sync, verbose=verbose, config=config): if not key: usage() - sn = sncli_start() + sn = nncli_start() sn.cli_note_trash(key, 1 if args[0] == 'trash' else 0) elif args[0] == 'pin' or args[0] == 'unpin': @@ -1423,7 +1447,7 @@ def sncli_start(sync=sync, verbose=verbose, config=config): if not key: usage() - sn = sncli_start() + sn = nncli_start() sn.cli_note_pin(key, 1 if args[0] == 'pin' else 0) elif args[0] == 'markdown' or args[0] == 'unmarkdown': @@ -1431,7 +1455,7 @@ def sncli_start(sync=sync, verbose=verbose, config=config): if not key: usage() - sn = sncli_start() + sn = nncli_start() sn.cli_note_markdown(key, 1 if args[0] == 'markdown' else 0) # Tag API @@ -1448,7 +1472,7 @@ def sncli_start(sync=sync, verbose=verbose, config=config): if args[1] == 'get': - sn = sncli_start() + sn = nncli_start() tags = sn.cli_note_tags_get(key) if tags: print(tags) @@ -1456,19 +1480,19 @@ def sncli_start(sync=sync, verbose=verbose, config=config): elif args[1] == 'set': tags = args[2] - sn = sncli_start() + sn = nncli_start() sn.cli_note_tags_set(key, tags) elif args[1] == 'add': new_tags = args[2] - sn = sncli_start() + sn = nncli_start() sn.cli_note_tags_add(key, new_tags) elif args[1] == 'rm': rm_tags = args[2] - sn = sncli_start() + sn = nncli_start() sn.cli_note_tags_rm(key, rm_tags) else: diff --git a/nnotes_cli/notes_db.py b/nnotes_cli/notes_db.py @@ -81,7 +81,7 @@ def __init__(self, config, log, update_view): n['savedate'] = now # set a localkey to each note in memory # Note: 'key' is used only for syncing with server - 'localkey' - # is used for everything else in sncli + # is used for everything else in nncli n['localkey'] = localkey # add the note to our database diff --git a/setup.py b/setup.py @@ -21,7 +21,7 @@ packages=['simplenote_cli'], entry_points={ 'console_scripts': [ - 'sncli = simplenote_cli.sncli:main' + 'nncli = nnotes_cli.nncli:main' ] }, classifiers=[