commit 5eda83ae4d474931f996a745deb0f26acea4bcb9
parent 64c35e8ee5294b268e789b5b64737d2cff3fff49
Author: Daniel Moch <daniel@danielmoch.com>
Date: Sat, 8 Sep 2018 06:31:06 -0400
Address pylint findings in nncli.py
Ref #10
Diffstat:
8 files changed, 1160 insertions(+), 956 deletions(-)
diff --git a/nncli/config.py b/nncli/config.py
@@ -10,7 +10,16 @@
class Config:
"""A class to contain all configuration data for nncli"""
+ class State:
+ """A container class for state information"""
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+
def __init__(self, custom_file=None):
+ self.state = Config.State(do_server_sync=True,
+ verbose=False,
+ do_gui=False,
+ search_direction=None)
self.config_home = user_config_dir('nncli', 'djmoch')
self.cache_home = user_cache_dir('nncli', 'djmoch')
@@ -589,7 +598,8 @@ def _create_configs_dict(self, parser, cfg_sec):
[parser.get(cfg_sec, 'cfg_log_reversed'), 'Log file reversed']
self.configs['tempdir'] = \
[
- parser.get(cfg_sec, 'cfg_tempdir'),
+ None if parser.get(cfg_sec, 'cfg_tempdir') == '' \
+ else parser.get(cfg_sec, 'cfg_tempdir'),
'Temporary directory for note storage'
]
diff --git a/nncli/gui.py b/nncli/gui.py
@@ -0,0 +1,911 @@
+# -*- coding: utf-8 -*-
+"""nncli_gui module"""
+import hashlib
+import subprocess
+import threading
+
+import urwid
+from . import view_titles, view_note, view_help, view_log, user_input
+from .utils import exec_cmd_on_note, get_pager
+
+# pylint: disable=too-many-instance-attributes, unused-argument
+class NncliGui:
+ """NncliGui class. Responsible for the console GUI view logic."""
+ def __init__(self, config, logger, ndb, key=None):
+ self.ndb = ndb
+ self.logger = logger
+ self.config = config
+ self.last_view = []
+ self.status_bar = self.config.get_config('status_bar')
+
+
+ self.log_lock = threading.Lock()
+ self.log_alarms = 0
+ self.logs = []
+
+ self.thread_sync = threading.Thread(
+ target=self.ndb.sync_worker,
+ args=[self.config.state.do_server_sync]
+ )
+ self.thread_sync.setDaemon(True)
+
+ self.view_titles = \
+ view_titles.ViewTitles(
+ self.config,
+ {
+ 'ndb' : self.ndb,
+ 'search_string' : None,
+ 'log' : self.log
+ }
+ )
+ self.view_note = \
+ view_note.ViewNote(
+ self.config,
+ {
+ 'ndb' : self.ndb,
+ 'id' : key, # initial key to view or None
+ 'log' : self.log
+ }
+ )
+
+ self.view_log = view_log.ViewLog(self.config)
+ self.view_help = view_help.ViewHelp(self.config)
+
+ palette = \
+ [
+ (
+ 'default',
+ self.config.get_color('default_fg'),
+ self.config.get_color('default_bg')
+ ),
+ (
+ 'status_bar',
+ self.config.get_color('status_bar_fg'),
+ self.config.get_color('status_bar_bg')
+ ),
+ (
+ 'log',
+ self.config.get_color('log_fg'),
+ self.config.get_color('log_bg')
+ ),
+ (
+ 'user_input_bar',
+ self.config.get_color('user_input_bar_fg'),
+ self.config.get_color('user_input_bar_bg')
+ ),
+ (
+ 'note_focus',
+ self.config.get_color('note_focus_fg'),
+ self.config.get_color('note_focus_bg')
+ ),
+ (
+ 'note_title_day',
+ self.config.get_color('note_title_day_fg'),
+ self.config.get_color('note_title_day_bg')
+ ),
+ (
+ 'note_title_week',
+ self.config.get_color('note_title_week_fg'),
+ self.config.get_color('note_title_week_bg')
+ ),
+ (
+ 'note_title_month',
+ self.config.get_color('note_title_month_fg'),
+ self.config.get_color('note_title_month_bg')
+ ),
+ (
+ 'note_title_year',
+ self.config.get_color('note_title_year_fg'),
+ self.config.get_color('note_title_year_bg')
+ ),
+ (
+ 'note_title_ancient',
+ self.config.get_color('note_title_ancient_fg'),
+ self.config.get_color('note_title_ancient_bg')
+ ),
+ (
+ 'note_date',
+ self.config.get_color('note_date_fg'),
+ self.config.get_color('note_date_bg')
+ ),
+ (
+ 'note_flags',
+ self.config.get_color('note_flags_fg'),
+ self.config.get_color('note_flags_bg')
+ ),
+ (
+ 'note_category',
+ self.config.get_color('note_category_fg'),
+ self.config.get_color('note_category_bg')
+ ),
+ (
+ 'note_content',
+ self.config.get_color('note_content_fg'),
+ self.config.get_color('note_content_bg')
+ ),
+ (
+ 'note_content_focus',
+ self.config.get_color('note_content_focus_fg'),
+ self.config.get_color('note_content_focus_bg')
+ ),
+ (
+ 'note_content_old',
+ self.config.get_color('note_content_old_fg'),
+ self.config.get_color('note_content_old_bg')
+ ),
+ (
+ 'note_content_old_focus',
+ self.config.get_color(
+ 'note_content_old_focus_fg'
+ ),
+ self.config.get_color(
+ 'note_content_old_focus_bg'
+ )
+ ),
+ (
+ 'help_focus',
+ self.config.get_color('help_focus_fg'),
+ self.config.get_color('help_focus_bg')
+ ),
+ (
+ 'help_header',
+ self.config.get_color('help_header_fg'),
+ self.config.get_color('help_header_bg')
+ ),
+ (
+ 'help_config',
+ self.config.get_color('help_config_fg'),
+ self.config.get_color('help_config_bg')
+ ),
+ (
+ 'help_value',
+ self.config.get_color('help_value_fg'),
+ self.config.get_color('help_value_bg')
+ ),
+ (
+ 'help_descr',
+ self.config.get_color('help_descr_fg'),
+ self.config.get_color('help_descr_bg')
+ )
+ ]
+
+ self.master_frame = urwid.Frame(
+ body=urwid.Filler(urwid.Text('')),
+ header=None,
+ footer=urwid.Pile([urwid.Pile([]), urwid.Pile([])]),
+ focus_part='body')
+
+ self.nncli_loop = urwid.MainLoop(self.master_frame,
+ palette,
+ handle_mouse=False)
+
+ self.nncli_loop.set_alarm_in(0, self.gui_init_view, \
+ True if key else False)
+
+ def run(self):
+ """Run the GUI"""
+ self.nncli_loop.run()
+
+ def gui_header_clear(self):
+ """Clear the console GUI header row"""
+ self.master_frame.contents['header'] = (None, None)
+ self.nncli_loop.draw_screen()
+
+ def gui_header_set(self, widget):
+ """Set the content of the console GUI header row"""
+ self.master_frame.contents['header'] = (widget, None)
+ self.nncli_loop.draw_screen()
+
+ def gui_footer_log_clear(self):
+ """Clear the log at the bottom of the GUI"""
+ gui = self.gui_footer_input_get()
+ self.master_frame.contents['footer'] = \
+ (urwid.Pile([urwid.Pile([]), urwid.Pile([gui])]), None)
+ self.nncli_loop.draw_screen()
+
+ def gui_footer_log_set(self, pile):
+ """Set the log at the bottom of the GUI"""
+ gui = self.gui_footer_input_get()
+ self.master_frame.contents['footer'] = \
+ (urwid.Pile([urwid.Pile(pile), urwid.Pile([gui])]), None)
+ self.nncli_loop.draw_screen()
+
+ def gui_footer_log_get(self):
+ """Get the log at the bottom of the GUI"""
+ return self.master_frame.contents['footer'][0].contents[0][0]
+
+ def gui_footer_input_clear(self):
+ """Clear the input at the bottom of the GUI"""
+ pile = self.gui_footer_log_get()
+ self.master_frame.contents['footer'] = \
+ (urwid.Pile([urwid.Pile([pile]), urwid.Pile([])]), None)
+ self.nncli_loop.draw_screen()
+
+ def gui_footer_input_set(self, gui):
+ """Set the input at the bottom of the GUI"""
+ pile = self.gui_footer_log_get()
+ self.master_frame.contents['footer'] = \
+ (urwid.Pile([urwid.Pile([pile]), urwid.Pile([gui])]), None)
+ self.nncli_loop.draw_screen()
+
+ def gui_footer_input_get(self):
+ """Get the input at the bottom of the GUI"""
+ return self.master_frame.contents['footer'][0].contents[1][0]
+
+ def gui_footer_focus_input(self):
+ """Set the GUI focus to the input at the bottom of the GUI"""
+ self.master_frame.focus_position = 'footer'
+ self.master_frame.contents['footer'][0].focus_position = 1
+
+ def gui_body_set(self, widget):
+ """Set the GUI body"""
+ self.master_frame.contents['body'] = (widget, None)
+ self.gui_update_status_bar()
+ self.nncli_loop.draw_screen()
+
+ def gui_body_get(self):
+ """Get the GUI body"""
+ return self.master_frame.contents['body'][0]
+
+ def gui_body_focus(self):
+ """Set the GUI focus to the body"""
+ self.master_frame.focus_position = 'body'
+
+ def gui_update_view(self):
+ """Update the GUI"""
+ if not self.config.state.do_gui:
+ return
+
+ try:
+ cur_key = self.view_titles.note_list \
+ [self.view_titles.focus_position].note['localkey']
+ except IndexError:
+ cur_key = None
+
+ self.view_titles.update_note_list(
+ self.view_titles.search_string,
+ sort_mode=self.config.state.current_sort_mode
+ )
+ self.view_titles.focus_note(cur_key)
+
+ if self.gui_body_get().__class__ == view_note.ViewNote:
+ self.view_note.update_note_view()
+
+ self.gui_update_status_bar()
+
+ def gui_update_status_bar(self):
+ """Update the GUI status bar"""
+ if self.status_bar != 'yes':
+ self.gui_header_clear()
+ else:
+ self.gui_header_set(self.gui_body_get().get_status_bar())
+
+ def gui_switch_frame_body(self, new_view, save_current_view=True):
+ """
+ Switch the body frame of the GUI. Used to switch to a new
+ view
+ """
+ if new_view is None:
+ if not self.last_view:
+ self.gui_stop()
+ else:
+ self.gui_body_set(self.last_view.pop())
+ else:
+ if self.gui_body_get().__class__ != new_view.__class__:
+ if save_current_view:
+ self.last_view.append(self.gui_body_get())
+ self.gui_body_set(new_view)
+
+ def delete_note_callback(self, key, delete):
+ """Update the GUI after deleting a note"""
+ if not delete:
+ return
+ self.ndb.set_note_deleted(key, True)
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ self.view_titles.update_note_title()
+
+ self.gui_update_status_bar()
+ self.ndb.sync_worker_go()
+
+ def gui_yes_no_input(self, args, yes_no):
+ """Create a yes/no input dialog at the GUI footer"""
+ self.gui_footer_input_clear()
+ self.gui_body_focus()
+ self.master_frame.keypress = self.gui_frame_keypress
+ args[0](args[1],
+ True if yes_no in ['YES', 'Yes', 'yes', 'Y', 'y'] \
+ else False
+ )
+
+ def gui_search_input(self, args, search_string):
+ """Create a search input dialog at the GUI footer"""
+ self.gui_footer_input_clear()
+ self.gui_body_focus()
+ self.master_frame.keypress = self.gui_frame_keypress
+ if search_string:
+ if self.gui_body_get() == self.view_note:
+ self.config.state.search_direction = args[1]
+ self.view_note.search_note_view_next(
+ search_string=search_string,
+ search_mode=args[0]
+ )
+ else:
+ self.view_titles.update_note_list(
+ search_string,
+ args[0],
+ sort_mode=self.config.state.current_sort_mode
+ )
+ self.gui_body_set(self.view_titles)
+
+ def gui_category_input(self, args, category):
+ """Create a category input at the GUI footer"""
+ self.gui_footer_input_clear()
+ self.gui_body_focus()
+ self.master_frame.keypress = self.gui_frame_keypress
+ if category is not None:
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ note = self.view_titles.note_list \
+ [self.view_titles.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ note = self.view_note.note
+
+ self.ndb.set_note_category(note['localkey'], category)
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ self.view_titles.update_note_title()
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ self.view_note.update_note_view()
+
+ self.gui_update_status_bar()
+ self.ndb.sync_worker_go()
+
+ def gui_pipe_input(self, args, cmd):
+ """Create a pipe input dialog at the GUI footoer"""
+ self.gui_footer_input_clear()
+ self.gui_body_focus()
+ self.master_frame.keypress = self.gui_frame_keypress
+ if cmd is not None:
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ note = self.view_titles.note_list \
+ [self.view_titles.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ note = self.view_note.old_note \
+ if self.view_note.old_note \
+ else self.view_note.note
+ try:
+ self.gui_clear()
+ pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
+ pipe.communicate(note['content'].encode('utf-8'))
+ pipe.stdin.close()
+ pipe.wait()
+ except OSError as ex:
+ self.log('Pipe error: %s' % ex)
+ finally:
+ self.gui_reset()
+
+ # pylint: disable=too-many-return-statements, too-many-branches
+ # pylint: disable=too-many-statements
+ def gui_frame_keypress(self, size, key):
+ """Keypress handler for the GUI"""
+ # convert space character into name
+ if key == ' ':
+ key = 'space'
+
+ contents = self.gui_body_get()
+
+ if key == self.config.get_keybind('quit'):
+ self.gui_switch_frame_body(None)
+
+ elif key == self.config.get_keybind('help'):
+ self.gui_switch_frame_body(self.view_help)
+
+ elif key == self.config.get_keybind('sync'):
+ self.ndb.last_sync = 0
+ self.ndb.sync_worker_go()
+
+ elif key == self.config.get_keybind('view_log'):
+ self.view_log.update_log()
+ self.gui_switch_frame_body(self.view_log)
+
+ elif key == self.config.get_keybind('down'):
+ if not contents.body.positions():
+ return None
+ last = len(contents.body.positions())
+ if contents.focus_position == (last - 1):
+ return None
+ contents.focus_position += 1
+ contents.render(size)
+
+ elif key == self.config.get_keybind('up'):
+ if not contents.body.positions():
+ return None
+ if contents.focus_position == 0:
+ return None
+ contents.focus_position -= 1
+ contents.render(size)
+
+ elif key == self.config.get_keybind('page_down'):
+ if not contents.body.positions():
+ return None
+ last = len(contents.body.positions())
+ next_focus = contents.focus_position + size[1]
+ if next_focus >= last:
+ next_focus = last - 1
+ contents.change_focus(size, next_focus,
+ offset_inset=0,
+ coming_from='above')
+
+ elif key == self.config.get_keybind('page_up'):
+ if not contents.body.positions():
+ return None
+ if 'bottom' in contents.ends_visible(size):
+ last = len(contents.body.positions())
+ next_focus = last - size[1] - size[1]
+ else:
+ next_focus = contents.focus_position - size[1]
+ if next_focus < 0:
+ next_focus = 0
+ contents.change_focus(size, next_focus,
+ offset_inset=0,
+ coming_from='below')
+
+ elif key == self.config.get_keybind('half_page_down'):
+ if not contents.body.positions():
+ return None
+ last = len(contents.body.positions())
+ next_focus = contents.focus_position + (size[1] // 2)
+ if next_focus >= last:
+ next_focus = last - 1
+ contents.change_focus(size, next_focus,
+ offset_inset=0,
+ coming_from='above')
+
+ elif key == self.config.get_keybind('half_page_up'):
+ if not contents.body.positions():
+ return None
+ if 'bottom' in contents.ends_visible(size):
+ last = len(contents.body.positions())
+ next_focus = last - size[1] - (size[1] // 2)
+ else:
+ next_focus = contents.focus_position - (size[1] // 2)
+ if next_focus < 0:
+ next_focus = 0
+ contents.change_focus(size, next_focus,
+ offset_inset=0,
+ coming_from='below')
+
+ elif key == self.config.get_keybind('bottom'):
+ if not contents.body.positions():
+ return None
+ contents.change_focus(size, (len(contents.body.positions()) - 1),
+ offset_inset=0,
+ coming_from='above')
+
+ elif key == self.config.get_keybind('top'):
+ if not contents.body.positions():
+ return None
+ contents.change_focus(size, 0,
+ offset_inset=0,
+ coming_from='below')
+
+ elif key == self.config.get_keybind('view_next_note'):
+ if self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if not self.view_titles.body.positions():
+ return None
+ last = len(self.view_titles.body.positions())
+ if self.view_titles.focus_position == (last - 1):
+ return None
+ self.view_titles.focus_position += 1
+ contents.update_note_view(
+ self.view_titles. \
+ note_list[self.view_titles. \
+ focus_position].note['localkey']
+ )
+ self.gui_switch_frame_body(self.view_note)
+
+ elif key == self.config.get_keybind('view_prev_note'):
+ if self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if not self.view_titles.body.positions():
+ return None
+ if self.view_titles.focus_position == 0:
+ return None
+ self.view_titles.focus_position -= 1
+ contents.update_note_view(
+ self.view_titles. \
+ note_list[self.view_titles. \
+ focus_position].note['localkey']
+ )
+ self.gui_switch_frame_body(self.view_note)
+
+ elif key == self.config.get_keybind('status'):
+ if self.status_bar == 'yes':
+ self.status_bar = 'no'
+ else:
+ self.status_bar = self.config.get_config('status_bar')
+
+ elif key == self.config.get_keybind('create_note'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
+ return key
+
+ self.gui_clear()
+ content = exec_cmd_on_note(None, self.config, self, self.logger)
+ self.gui_reset()
+
+ if content:
+ self.log('New note created')
+ self.ndb.create_note(content)
+ self.gui_update_view()
+ self.ndb.sync_worker_go()
+
+ elif key == self.config.get_keybind('edit_note') or \
+ key == self.config.get_keybind('view_note_ext') or \
+ key == self.config.get_keybind('view_note_json'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
+ self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ if not contents.body.positions():
+ return None
+ note = contents.note_list[contents.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ if key == self.config.get_keybind('edit_note'):
+ note = contents.note
+ else:
+ note = contents.old_note if contents.old_note \
+ else contents.note
+
+ self.gui_clear()
+ if key == self.config.get_keybind('edit_note'):
+ content = exec_cmd_on_note(note, self.config, self,
+ self.logger)
+ elif key == self.config.get_keybind('view_note_ext'):
+ content = exec_cmd_on_note(
+ note,
+ self.config,
+ self,
+ self.logger,
+ cmd=get_pager(self.config, self.logger))
+ else: # key == self.config.get_keybind('view_note_json')
+ content = exec_cmd_on_note(
+ note,
+ self.config,
+ self,
+ self.logger,
+ cmd=get_pager(self.config, self.logger),
+ raw=True
+ )
+
+ self.gui_reset()
+
+ if not content:
+ return None
+
+ md5_old = hashlib.md5(note['content'].encode('utf-8')).digest()
+ md5_new = hashlib.md5(content.encode('utf-8')).digest()
+
+ if md5_old != md5_new:
+ self.log('Note updated')
+ self.ndb.set_note_content(note['localkey'], content)
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ contents.update_note_title()
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ contents.update_note_view()
+ self.ndb.sync_worker_go()
+ else:
+ self.log('Note unchanged')
+
+ elif key == self.config.get_keybind('view_note'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
+ return key
+
+ if not contents.body.positions():
+ return None
+ self.view_note.update_note_view(
+ contents.note_list[contents.focus_position]. \
+ note['localkey'])
+ self.gui_switch_frame_body(self.view_note)
+
+ elif key == self.config.get_keybind('pipe_note'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
+ self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ if not contents.body.positions():
+ return None
+ note = contents.note_list[contents.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ note = contents.old_note if contents.old_note else contents.note
+
+ self.gui_footer_input_set(
+ urwid.AttrMap(
+ user_input.UserInput(
+ self.config,
+ key,
+ '',
+ self.gui_pipe_input,
+ None
+ ),
+ 'user_input_bar'
+ )
+ )
+ self.gui_footer_focus_input()
+ self.master_frame.keypress = \
+ self.gui_footer_input_get().keypress
+
+ elif key == self.config.get_keybind('note_delete'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
+ self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ if not contents.body.positions():
+ return None
+ note = contents.note_list[contents.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ note = contents.note
+
+ self.gui_footer_input_set(
+ urwid.AttrMap(
+ user_input.UserInput(
+ self.config,
+ 'Delete (y/n): ',
+ '',
+ self.gui_yes_no_input,
+ [
+ self.delete_note_callback,
+ note['localkey']
+ ]
+ ),
+ 'user_input_bar'
+ )
+ )
+ self.gui_footer_focus_input()
+ self.master_frame.keypress = \
+ self.gui_footer_input_get().keypress
+
+ elif key == self.config.get_keybind('note_favorite'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
+ self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ if not contents.body.positions():
+ return None
+ note = contents.note_list[contents.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ note = contents.note
+
+ favorite = not note['favorite']
+
+ self.ndb.set_note_favorite(note['localkey'], favorite)
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ contents.update_note_title()
+
+ self.ndb.sync_worker_go()
+
+ elif key == self.config.get_keybind('note_category'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
+ self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
+ if not contents.body.positions():
+ return None
+ note = contents.note_list[contents.focus_position].note
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
+ note = contents.note
+
+ self.gui_footer_input_set(
+ urwid.AttrMap(
+ user_input.UserInput(
+ self.config,
+ 'Category: ',
+ note['category'],
+ self.gui_category_input,
+ None
+ ),
+ 'user_input_bar'
+ )
+ )
+ self.gui_footer_focus_input()
+ self.master_frame.keypress = \
+ self.gui_footer_input_get().keypress
+
+ elif key == self.config.get_keybind('search_gstyle') or \
+ key == self.config.get_keybind('search_regex') or \
+ key == self.config.get_keybind('search_prev_gstyle') or \
+ key == self.config.get_keybind('search_prev_regex'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
+ self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ if self.gui_body_get().__class__ == view_note.ViewNote:
+ if key == self.config.get_keybind('search_prev_gstyle') or \
+ key == self.config.get_keybind('search_prev_regex'):
+ self.view_note.search_direction = 'backward'
+ else:
+ self.view_note.search_direction = 'forward'
+
+ options = [
+ 'gstyle' if key == self.config.get_keybind('search_gstyle')
+ or key == self.config.get_keybind('search_prev_gstyle')
+ else 'regex',
+ 'backward' if key ==
+ self.config.get_keybind('search_prev_gstyle')
+ or key == self.config.get_keybind('search_prev_regex')
+ else 'forward'
+ ]
+
+ caption = '{}{}'.format('(regex) '
+ if options[0] == 'regex'
+ else '',
+ '/' if options[1] == 'forward'
+ else '?')
+
+ self.gui_footer_input_set(
+ urwid.AttrMap(
+ user_input.UserInput(
+ self.config,
+ caption,
+ '',
+ self.gui_search_input,
+ options
+ ),
+ 'user_input_bar'
+ )
+ )
+ self.gui_footer_focus_input()
+ self.master_frame.keypress = \
+ self.gui_footer_input_get().keypress
+
+ elif key == self.config.get_keybind('search_next'):
+ if self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ self.view_note.search_note_view_next()
+
+ elif key == self.config.get_keybind('search_prev'):
+ if self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ self.view_note.search_note_view_prev()
+
+ elif key == self.config.get_keybind('clear_search'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
+ return key
+
+ self.view_titles.update_note_list(
+ None,
+ sort_mode=self.config.state.current_sort_mode
+ )
+ self.gui_body_set(self.view_titles)
+
+ elif key == self.config.get_keybind('sort_date'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
+ return key
+
+ self.config.state.current_sort_mode = 'date'
+ self.view_titles.sort_note_list('date')
+
+ elif key == self.config.get_keybind('sort_alpha'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
+ return key
+
+ self.config.state.current_sort_mode = 'alpha'
+ self.view_titles.sort_note_list('alpha')
+
+ elif key == self.config.get_keybind('sort_categories'):
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
+ return key
+
+ self.config.state.current_sort_mode = 'categories'
+ self.view_titles.sort_note_list('categories')
+
+ elif key == self.config.get_keybind('copy_note_text'):
+ if self.gui_body_get().__class__ != view_note.ViewNote:
+ return key
+
+ self.view_note.copy_note_text()
+
+ else:
+ return contents.keypress(size, key)
+
+ self.gui_update_status_bar()
+ return None
+
+ def gui_init_view(self, loop, show_note):
+ """Initialize the GUI"""
+ self.master_frame.keypress = self.gui_frame_keypress
+ self.gui_body_set(self.view_titles)
+
+ if show_note:
+ # note that title view set first to prime the view stack
+ self.gui_switch_frame_body(self.view_note)
+
+ self.thread_sync.start()
+
+ def gui_clear(self):
+ """Clear the GUI"""
+ self.nncli_loop.widget = urwid.Filler(urwid.Text(''))
+ self.nncli_loop.draw_screen()
+
+ def gui_reset(self):
+ """Reset the GUI"""
+ self.nncli_loop.widget = self.master_frame
+ self.nncli_loop.draw_screen()
+
+ def gui_stop(self):
+ """Stop the GUI"""
+ # don't exit if there are any notes not yet saved to the disk
+
+ # NOTE: this was originally causing hangs on exit with urllib2
+ # should not be a problem now since using the requests library
+ # ref https://github.com/insanum/sncli/issues/18#issuecomment-105517773
+ if self.ndb.verify_all_saved():
+ # clear the screen and exit the urwid run loop
+ self.gui_clear()
+ raise urwid.ExitMainLoop()
+ else:
+ self.log('WARNING: Not all notes saved'
+ 'to disk (wait for sync worker)')
+
+ def log(self, msg):
+ """Log as message, displaying to the user as appropriate"""
+ self.logger.log(msg)
+
+ self.log_lock.acquire()
+
+ self.log_alarms += 1
+ self.logs.append(msg)
+
+ if len(self.logs) > int(self.config.get_config('max_logs')):
+ self.log_alarms -= 1
+ self.logs.pop(0)
+
+ log_pile = []
+ for log in self.logs:
+ log_pile.append(urwid.AttrMap(urwid.Text(log), 'log'))
+
+ if self.config.state.verbose:
+ self.gui_footer_log_set(log_pile)
+
+ self.nncli_loop.set_alarm_in(
+ int(self.config.get_config('log_timeout')),
+ self.log_timeout, None)
+
+ self.log_lock.release()
+
+ def log_timeout(self, loop, arg):
+ """
+ Run periodically to check for new log entries to append to
+ the GUI footer
+ """
+ self.log_lock.acquire()
+
+ self.log_alarms -= 1
+
+ if self.log_alarms == 0:
+ self.gui_footer_log_clear()
+ self.logs = []
+ else:
+ # for some reason having problems with this being empty?
+ if not self.logs:
+ self.logs.pop(0)
+
+ log_pile = []
+
+ for log in self.logs:
+ log_pile.append(urwid.AttrMap(urwid.Text(log), 'log'))
+
+ if self.config.state.verbose:
+ self.gui_footer_log_set(log_pile)
+
+ self.log_lock.release()
diff --git a/nncli/log.py b/nncli/log.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+"""log module"""
+import logging
+from logging.handlers import RotatingFileHandler
+
+import os
+
+# pylint: disable=unused-argument
+class Logger:
+ """Handles logging for the application"""
+ def __init__(self, config):
+ self.config = config
+ self.logfile = os.path.join(
+ 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'
+ )
+ )
+ self.logger = logging.getLogger()
+ self.logger.setLevel(logging.DEBUG)
+ self.logger.addHandler(self.loghandler)
+
+ logging.debug('nncli logging initialized')
+
+ def log(self, msg):
+ """Log as message, displaying to the user as appropriate"""
+ logging.debug(msg)
+
+ if not self.config.state.do_gui:
+ if self.config.state.verbose:
+ print(msg)
diff --git a/nncli/nncli.py b/nncli/nncli.py
@@ -1,980 +1,175 @@
# -*- coding: utf-8 -*-
-
-import os, sys, getopt, re, signal, time, datetime, shlex, hashlib
-import subprocess, threading, logging
-import copy, json, urwid, datetime
-from . import view_titles, view_note, view_help, view_log, user_input
-from . import utils, temp, __version__
+"""nncli module"""
+import hashlib
+import json
+import os
+import signal
+import sys
+import time
+
+from . import utils, __version__
from .config import Config
-from .nextcloud_note import NextcloudNote
+from .gui import NncliGui
+from .log import Logger
from .notes_db import NotesDB, ReadError, WriteError
-from logging.handlers import RotatingFileHandler
+from .utils import exec_cmd_on_note
+# pylint: disable=unused-argument
class Nncli:
-
+ """Nncli class. Responsible for most of the application logic"""
def __init__(self, do_server_sync, verbose=False, config_file=None):
- self.config = Config(config_file)
- self.do_server_sync = do_server_sync
- self.verbose = verbose
- self.do_gui = False
- force_full_sync = False
- self.current_sort_mode = self.config.get_config('sort_mode')
-
- self.tempdir = self.config.get_config('tempdir')
- if self.tempdir == '':
- self.tempdir = None
+ self.config = Config(config_file)
+ self.config.state.do_server_sync = do_server_sync
+ self.config.state.verbose = verbose
+ force_full_sync = False
if not os.path.exists(self.config.get_config('db_path')):
os.mkdir(self.config.get_config('db_path'))
force_full_sync = True
- # configure the logging module
- 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'))
- self.logger = logging.getLogger()
- self.logger.setLevel(logging.DEBUG)
- self.logger.addHandler(self.loghandler)
- self.config.logfile = self.logfile
-
- logging.debug('nncli logging initialized')
-
- self.logs = []
+ self.logger = Logger(self.config)
try:
- self.ndb = NotesDB(self.config, self.log, self.gui_update_view)
- except Exception as e:
- self.log(str(e))
+ self.ndb = NotesDB(
+ self.config,
+ self.logger.log
+ )
+ except (ReadError, WriteError) as ex:
+ self.logger.log(str(ex))
sys.exit(1)
+ self.nncli_gui = NncliGui(self.config, self.logger, self.ndb)
+ self.ndb.set_update_view(self.nncli_gui.gui_update_view)
+
if force_full_sync:
# The note database doesn't exist so force a full sync. It is
# important to do this outside of the gui because an account
# with hundreds of notes will cause a recursion panic under
# urwid. This simple workaround gets the job done. :-)
- self.verbose = True
- self.log('nncli database doesn\'t exist, forcing full sync...')
+ self.config.state.verbose = True
+ self.logger.log('nncli database doesn\'t exist,'
+ ' forcing full sync...')
self.sync_notes()
- self.verbose = verbose
+ self.config.state.verbose = verbose
def sync_notes(self):
- self.ndb.sync_now(self.do_server_sync)
-
- def get_editor(self):
- editor = self.config.get_config('editor')
- if not editor:
- self.log('No editor configured!')
- return None
- return editor
-
- def get_pager(self):
- pager = self.config.get_config('pager')
- if not pager:
- self.log('No pager configured!')
- return None
- return pager
-
- def exec_cmd_on_note(self, note, cmd=None, raw=False):
-
- if not cmd:
- cmd = self.get_editor()
- if not cmd:
- return None
-
- tf = temp.tempfile_create(note if note else None, raw=raw, tempdir=self.tempdir)
- fname = temp.tempfile_name(tf)
-
- focus_position = 0
- try:
- focus_position = self.gui_body_get().focus_position
- except IndexError:
- # focus position will fail if no notes available (listbox empty)
- # TODO: find a neater way to check than try/except
- pass
- except AttributeError:
- # we're running in CLI mode
- pass
-
- subs = {
- 'fname': fname,
- 'line': focus_position + 1,
- }
- cmd_list = [c.format(**subs) for c in shlex.split(cmd)]
-
- # if the filename wasn't able to be subbed, append it
- # this makes it fully backwards compatible with previous configs
- if '{fname}' not in cmd:
- cmd_list.append(fname)
-
- self.log("EXECUTING: {}".format(cmd_list))
-
- try:
- subprocess.check_call(cmd_list)
- except Exception as e:
- self.log('Command error: ' + str(e))
- temp.tempfile_delete(tf)
- return None
-
- content = None
- if not raw:
- content = temp.tempfile_content(tf)
- if not content or content == '\n':
- content = None
-
- temp.tempfile_delete(tf)
-
- if self.do_gui:
- self.nncli_loop.screen.clear()
- self.nncli_loop.draw_screen()
-
- return content
-
- def gui_header_clear(self):
- self.master_frame.contents['header'] = ( None, None )
- self.nncli_loop.draw_screen()
-
- def gui_header_set(self, w):
- self.master_frame.contents['header'] = ( w, None )
- self.nncli_loop.draw_screen()
-
- 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.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.nncli_loop.draw_screen()
-
- def gui_footer_log_get(self):
- return self.master_frame.contents['footer'][0].contents[0][0]
-
- 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.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.nncli_loop.draw_screen()
-
- def gui_footer_input_get(self):
- return self.master_frame.contents['footer'][0].contents[1][0]
-
- def gui_footer_focus_input(self):
- self.master_frame.focus_position = 'footer'
- self.master_frame.contents['footer'][0].focus_position = 1
-
- def gui_body_set(self, w):
- self.master_frame.contents['body'] = ( w, None )
- self.gui_update_status_bar()
- self.nncli_loop.draw_screen()
-
- def gui_body_get(self):
- return self.master_frame.contents['body'][0]
-
- def gui_body_focus(self):
- self.master_frame.focus_position = 'body'
-
- def log_timeout(self, loop, arg):
- self.log_lock.acquire()
-
- self.log_alarms -= 1
-
- if self.log_alarms == 0:
- self.gui_footer_log_clear()
- self.logs = []
- else:
- # for some reason having problems with this being empty?
- if len(self.logs) > 0:
- self.logs.pop(0)
-
- log_pile = []
-
- for l in self.logs:
- log_pile.append(urwid.AttrMap(urwid.Text(l), 'log'))
-
- if self.verbose:
- self.gui_footer_log_set(log_pile)
-
- self.log_lock.release()
-
- def log(self, msg):
- logging.debug(msg)
-
- if not self.do_gui:
- if self.verbose:
- print(msg)
- return
-
- self.log_lock.acquire()
-
- self.log_alarms += 1
- self.logs.append(msg)
-
- if len(self.logs) > int(self.config.get_config('max_logs')):
- self.log_alarms -= 1
- self.logs.pop(0)
-
- log_pile = []
- for l in self.logs:
- log_pile.append(urwid.AttrMap(urwid.Text(l), 'log'))
-
- if self.verbose:
- self.gui_footer_log_set(log_pile)
-
- self.nncli_loop.set_alarm_in(
- int(self.config.get_config('log_timeout')),
- self.log_timeout, None)
-
- self.log_lock.release()
-
- def gui_update_view(self):
- if not self.do_gui:
- return
-
- try:
- cur_key = self.view_titles.note_list[self.view_titles.focus_position].note['localkey']
- except IndexError as e:
- cur_key = None
- pass
-
- self.view_titles.update_note_list(self.view_titles.search_string, sort_mode=self.current_sort_mode)
- self.view_titles.focus_note(cur_key)
-
- if self.gui_body_get().__class__ == view_note.ViewNote:
- self.view_note.update_note_view()
-
- self.gui_update_status_bar()
-
- def gui_update_status_bar(self):
- if self.status_bar != 'yes':
- self.gui_header_clear()
- else:
- self.gui_header_set(self.gui_body_get().get_status_bar())
-
- def gui_switch_frame_body(self, new_view, save_current_view=True):
- if new_view == None:
- if len(self.last_view) == 0:
- # XXX verify all notes saved...
- self.gui_stop()
- else:
- self.gui_body_set(self.last_view.pop())
- else:
- if self.gui_body_get().__class__ != new_view.__class__:
- if save_current_view:
- self.last_view.append(self.gui_body_get())
- self.gui_body_set(new_view)
-
- def delete_note_callback(self, key, delete):
- if not delete:
- return
- note = self.ndb.get_note(key)
- self.ndb.set_note_deleted(key, True)
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- self.view_titles.update_note_title()
-
- self.gui_update_status_bar()
- self.ndb.sync_worker_go()
-
- def gui_yes_no_input(self, args, yes_no):
- self.gui_footer_input_clear()
- self.gui_body_focus()
- self.master_frame.keypress = self.gui_frame_keypress
- args[0](args[1],
- True if yes_no in [ 'YES', 'Yes', 'yes', 'Y', 'y' ]
- else False)
-
- def gui_search_input(self, args, search_string):
- self.gui_footer_input_clear()
- self.gui_body_focus()
- self.master_frame.keypress = self.gui_frame_keypress
- if search_string:
- if (self.gui_body_get() == self.view_note):
- self.search_direction = args[1]
- self.view_note.search_note_view_next(search_string=search_string, search_mode=args[0])
- else:
- self.view_titles.update_note_list(search_string, args[0], sort_mode=self.current_sort_mode)
- self.gui_body_set(self.view_titles)
-
- def gui_category_input(self, args, category):
- self.gui_footer_input_clear()
- self.gui_body_focus()
- self.master_frame.keypress = self.gui_frame_keypress
- if category != None:
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- note = self.view_titles.note_list[self.view_titles.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- note = self.view_note.note
-
- self.ndb.set_note_category(note['localkey'], category)
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- self.view_titles.update_note_title()
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- self.view_note.update_note_view()
-
- self.gui_update_status_bar()
- self.ndb.sync_worker_go()
-
- def gui_pipe_input(self, args, cmd):
- self.gui_footer_input_clear()
- self.gui_body_focus()
- self.master_frame.keypress = self.gui_frame_keypress
- if cmd != None:
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- note = self.view_titles.note_list[self.view_titles.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- note = self.view_note.old_note if self.view_note.old_note \
- else self.view_note.note
- args = shlex.split(cmd)
- try:
- self.gui_clear()
- pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
- pipe.communicate(note['content'].encode('utf-8'))
- pipe.stdin.close()
- pipe.wait()
- except OSError as e:
- self.log('Pipe error: ' + str(e))
- finally:
- self.gui_reset()
-
- def gui_frame_keypress(self, size, key):
- # convert space character into name
- if key == ' ':
- key = 'space'
-
- lb = self.gui_body_get()
-
- if key == self.config.get_keybind('quit'):
- self.gui_switch_frame_body(None)
-
- elif key == self.config.get_keybind('help'):
- self.gui_switch_frame_body(self.view_help)
-
- elif key == self.config.get_keybind('sync'):
- self.ndb.last_sync = 0
- self.ndb.sync_worker_go()
-
- elif key == self.config.get_keybind('view_log'):
- self.view_log.update_log()
- self.gui_switch_frame_body(self.view_log)
-
- elif key == self.config.get_keybind('down'):
- if len(lb.body.positions()) <= 0:
- return None
- last = len(lb.body.positions())
- if lb.focus_position == (last - 1):
- return None
- lb.focus_position += 1
- lb.render(size)
-
- elif key == self.config.get_keybind('up'):
- if len(lb.body.positions()) <= 0:
- return None
- if lb.focus_position == 0:
- return None
- lb.focus_position -= 1
- lb.render(size)
-
- elif key == self.config.get_keybind('page_down'):
- if len(lb.body.positions()) <= 0:
- return None
- last = len(lb.body.positions())
- next_focus = lb.focus_position + size[1]
- if next_focus >= last:
- next_focus = last - 1
- lb.change_focus(size, next_focus,
- offset_inset=0,
- coming_from='above')
-
- elif key == self.config.get_keybind('page_up'):
- if len(lb.body.positions()) <= 0:
- return None
- if 'bottom' in lb.ends_visible(size):
- last = len(lb.body.positions())
- next_focus = last - size[1] - size[1]
- else:
- next_focus = lb.focus_position - size[1]
- if next_focus < 0:
- next_focus = 0
- lb.change_focus(size, next_focus,
- offset_inset=0,
- coming_from='below')
-
- elif key == self.config.get_keybind('half_page_down'):
- if len(lb.body.positions()) <= 0:
- return None
- last = len(lb.body.positions())
- next_focus = lb.focus_position + (size[1] // 2)
- if next_focus >= last:
- next_focus = last - 1
- lb.change_focus(size, next_focus,
- offset_inset=0,
- coming_from='above')
-
- elif key == self.config.get_keybind('half_page_up'):
- if len(lb.body.positions()) <= 0:
- return None
- if 'bottom' in lb.ends_visible(size):
- last = len(lb.body.positions())
- next_focus = last - size[1] - (size[1] // 2)
- else:
- next_focus = lb.focus_position - (size[1] // 2)
- if next_focus < 0:
- next_focus = 0
- lb.change_focus(size, next_focus,
- offset_inset=0,
- coming_from='below')
-
- elif key == self.config.get_keybind('bottom'):
- if len(lb.body.positions()) <= 0:
- return None
- lb.change_focus(size, (len(lb.body.positions()) - 1),
- offset_inset=0,
- coming_from='above')
-
- elif key == self.config.get_keybind('top'):
- if len(lb.body.positions()) <= 0:
- return None
- lb.change_focus(size, 0,
- offset_inset=0,
- coming_from='below')
-
- elif key == self.config.get_keybind('view_next_note'):
- if self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if len(self.view_titles.body.positions()) <= 0:
- return None
- last = len(self.view_titles.body.positions())
- if self.view_titles.focus_position == (last - 1):
- return None
- self.view_titles.focus_position += 1
- lb.update_note_view(
- self.view_titles.note_list[self.view_titles.focus_position].note['localkey'])
- self.gui_switch_frame_body(self.view_note)
-
- elif key == self.config.get_keybind('view_prev_note'):
- if self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if len(self.view_titles.body.positions()) <= 0:
- return None
- if self.view_titles.focus_position == 0:
- return None
- self.view_titles.focus_position -= 1
- lb.update_note_view(
- self.view_titles.note_list[self.view_titles.focus_position].note['localkey'])
- self.gui_switch_frame_body(self.view_note)
-
- elif key == self.config.get_keybind('status'):
- if self.status_bar == 'yes':
- self.status_bar = 'no'
- else:
- self.status_bar = self.config.get_config('status_bar')
-
- elif key == self.config.get_keybind('create_note'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
- return key
-
- self.gui_clear()
- content = self.exec_cmd_on_note(None)
- self.gui_reset()
-
- if content:
- self.log('New note created')
- self.ndb.create_note(content)
- self.gui_update_view()
- self.ndb.sync_worker_go()
-
- elif key == self.config.get_keybind('edit_note') or \
- key == self.config.get_keybind('view_note_ext') or \
- key == self.config.get_keybind('view_note_json'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
- self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- if len(lb.body.positions()) <= 0:
- return None
- note = lb.note_list[lb.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- if key == self.config.get_keybind('edit_note'):
- note = lb.note
- else:
- note = lb.old_note if lb.old_note else lb.note
-
- self.gui_clear()
- if key == self.config.get_keybind('edit_note'):
- content = self.exec_cmd_on_note(note)
- elif key == self.config.get_keybind('view_note_ext'):
- content = self.exec_cmd_on_note(note, cmd=self.get_pager())
- else: # key == self.config.get_keybind('view_note_json')
- content = self.exec_cmd_on_note(note, cmd=self.get_pager(), raw=True)
-
- self.gui_reset()
-
- if not content:
- return None
-
- md5_old = hashlib.md5(note['content'].encode('utf-8')).digest()
- md5_new = hashlib.md5(content.encode('utf-8')).digest()
-
- if md5_old != md5_new:
- self.log('Note updated')
- self.ndb.set_note_content(note['localkey'], content)
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- lb.update_note_title()
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- lb.update_note_view()
- self.ndb.sync_worker_go()
- else:
- self.log('Note unchanged')
-
- elif key == self.config.get_keybind('view_note'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
- return key
-
- if len(lb.body.positions()) <= 0:
- return None
- self.view_note.update_note_view(
- lb.note_list[lb.focus_position].note['localkey'])
- self.gui_switch_frame_body(self.view_note)
-
- elif key == self.config.get_keybind('pipe_note'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
- self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- if len(lb.body.positions()) <= 0:
- return None
- note = lb.note_list[lb.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- note = lb.old_note if lb.old_note else lb.note
-
- self.gui_footer_input_set(
- urwid.AttrMap(
- user_input.UserInput(
- self.config,
- key,
- '',
- self.gui_pipe_input,
- None),
- 'user_input_bar'))
- self.gui_footer_focus_input()
- self.master_frame.keypress = self.gui_footer_input_get().keypress
-
- elif key == self.config.get_keybind('note_delete'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
- self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- if len(lb.body.positions()) <= 0:
- return None
- note = lb.note_list[lb.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- note = lb.note
-
- self.gui_footer_input_set(
- urwid.AttrMap(
- user_input.UserInput(
- self.config,
- 'Delete (y/n): ',
- '',
- self.gui_yes_no_input,
- [ self.delete_note_callback, note['localkey'] ]),
- 'user_input_bar'))
- self.gui_footer_focus_input()
- self.master_frame.keypress = self.gui_footer_input_get().keypress
-
- elif key == self.config.get_keybind('note_favorite'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
- self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- if len(lb.body.positions()) <= 0:
- return None
- note = lb.note_list[lb.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- note = lb.note
-
- favorite = not note['favorite']
-
- self.ndb.set_note_favorite(note['localkey'], favorite)
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- lb.update_note_title()
-
- self.ndb.sync_worker_go()
-
- elif key == self.config.get_keybind('note_category'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
- self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
- if len(lb.body.positions()) <= 0:
- return None
- note = lb.note_list[lb.focus_position].note
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
- note = lb.note
-
- self.gui_footer_input_set(
- urwid.AttrMap(
- user_input.UserInput(
- self.config,
- 'Category: ',
- note['category'],
- self.gui_category_input,
- None),
- 'user_input_bar'))
- self.gui_footer_focus_input()
- self.master_frame.keypress = self.gui_footer_input_get().keypress
-
- elif key == self.config.get_keybind('search_gstyle') or \
- key == self.config.get_keybind('search_regex') or \
- key == self.config.get_keybind('search_prev_gstyle') or \
- key == self.config.get_keybind('search_prev_regex'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
- self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- if self.gui_body_get().__class__ == view_note.ViewNote:
- if key == self.config.get_keybind('search_prev_gstyle') or \
- key == self.config.get_keybind('search_prev_regex'):
- self.view_note.search_direction = 'backward'
- else:
- self.view_note.search_direction = 'forward'
-
- options = [
- 'gstyle' if key == self.config.get_keybind('search_gstyle')
- or key == self.config.get_keybind('search_prev_gstyle')
- else 'regex',
- 'backward' if key == self.config.get_keybind('search_prev_gstyle')
- or key == self.config.get_keybind('search_prev_regex')
- else 'forward'
- ]
-
- caption = '{}{}'.format('(regex) ' if options[0] == 'regex' else '', '/' if options[1] == 'forward' else '?')
-
- self.gui_footer_input_set(
- urwid.AttrMap(
- user_input.UserInput(
- self.config,
- caption,
- '',
- self.gui_search_input,
- options),
- 'user_input_bar'))
- self.gui_footer_focus_input()
- self.master_frame.keypress = self.gui_footer_input_get().keypress
-
- elif key == self.config.get_keybind('search_next'):
- if self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- self.view_note.search_note_view_next()
-
- elif key == self.config.get_keybind('search_prev'):
- if self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- self.view_note.search_note_view_prev()
-
- elif key == self.config.get_keybind('clear_search'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
- return key
-
- self.view_titles.update_note_list(None, sort_mode=self.current_sort_mode)
- self.gui_body_set(self.view_titles)
-
- elif key == self.config.get_keybind('sort_date'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
- return key
-
- self.current_sort_mode = 'date'
- self.view_titles.sort_note_list('date')
-
- elif key == self.config.get_keybind('sort_alpha'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
- return key
-
- self.current_sort_mode = 'alpha'
- self.view_titles.sort_note_list('alpha')
-
- elif key == self.config.get_keybind('sort_categories'):
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
- return key
-
- self.current_sort_mode = 'categories'
- self.view_titles.sort_note_list('categories')
-
- elif key == self.config.get_keybind('copy_note_text'):
- if self.gui_body_get().__class__ != view_note.ViewNote:
- return key
-
- self.view_note.copy_note_text()
-
- else:
- return lb.keypress(size, key)
-
- self.gui_update_status_bar()
- return None
-
- def gui_init_view(self, loop, view_note):
- self.master_frame.keypress = self.gui_frame_keypress
- self.gui_body_set(self.view_titles)
-
- if view_note:
- # note that title view set first to prime the view stack
- self.gui_switch_frame_body(self.view_note)
-
- self.thread_sync.start()
-
- def gui_clear(self):
- self.nncli_loop.widget = urwid.Filler(urwid.Text(''))
- self.nncli_loop.draw_screen()
-
- def gui_reset(self):
- 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
-
- # NOTE: this was originally causing hangs on exit with urllib2
- # should not be a problem now since using the requests library
- # ref https://github.com/insanum/sncli/issues/18#issuecomment-105517773
- if self.ndb.verify_all_saved():
- # clear the screen and exit the urwid run loop
- self.gui_clear()
- raise urwid.ExitMainLoop()
- else:
- self.log(u'WARNING: Not all notes saved to disk (wait for sync worker)')
+ """Sync notes with the server"""
+ self.ndb.sync_now(self.config.state.do_server_sync)
def gui(self, key):
-
- self.do_gui = True
-
- self.last_view = []
- self.status_bar = self.config.get_config('status_bar')
-
- self.log_alarms = 0
- self.log_lock = threading.Lock()
-
- self.thread_sync = threading.Thread(target=self.ndb.sync_worker,
- args=[self.do_server_sync])
- self.thread_sync.setDaemon(True)
-
- self.view_titles = \
- view_titles.ViewTitles(self.config,
- {
- 'ndb' : self.ndb,
- 'search_string' : None,
- 'log' : self.log
- })
- self.view_note = \
- view_note.ViewNote(self.config,
- {
- 'ndb' : self.ndb,
- 'id' : key, # initial key to view or None
- 'log' : self.log
- })
-
- self.view_log = view_log.ViewLog(self.config)
- self.view_help = view_help.ViewHelp(self.config)
-
- palette = \
- [
- ('default',
- self.config.get_color('default_fg'),
- self.config.get_color('default_bg') ),
- ('status_bar',
- self.config.get_color('status_bar_fg'),
- self.config.get_color('status_bar_bg') ),
- ('log',
- self.config.get_color('log_fg'),
- self.config.get_color('log_bg') ),
- ('user_input_bar',
- self.config.get_color('user_input_bar_fg'),
- self.config.get_color('user_input_bar_bg') ),
- ('note_focus',
- self.config.get_color('note_focus_fg'),
- self.config.get_color('note_focus_bg') ),
- ('note_title_day',
- self.config.get_color('note_title_day_fg'),
- self.config.get_color('note_title_day_bg') ),
- ('note_title_week',
- self.config.get_color('note_title_week_fg'),
- self.config.get_color('note_title_week_bg') ),
- ('note_title_month',
- self.config.get_color('note_title_month_fg'),
- self.config.get_color('note_title_month_bg') ),
- ('note_title_year',
- self.config.get_color('note_title_year_fg'),
- self.config.get_color('note_title_year_bg') ),
- ('note_title_ancient',
- self.config.get_color('note_title_ancient_fg'),
- self.config.get_color('note_title_ancient_bg') ),
- ('note_date',
- self.config.get_color('note_date_fg'),
- self.config.get_color('note_date_bg') ),
- ('note_flags',
- self.config.get_color('note_flags_fg'),
- self.config.get_color('note_flags_bg') ),
- ('note_category',
- self.config.get_color('note_category_fg'),
- self.config.get_color('note_category_bg') ),
- ('note_content',
- self.config.get_color('note_content_fg'),
- self.config.get_color('note_content_bg') ),
- ('note_content_focus',
- self.config.get_color('note_content_focus_fg'),
- self.config.get_color('note_content_focus_bg') ),
- ('note_content_old',
- self.config.get_color('note_content_old_fg'),
- self.config.get_color('note_content_old_bg') ),
- ('note_content_old_focus',
- self.config.get_color('note_content_old_focus_fg'),
- self.config.get_color('note_content_old_focus_bg') ),
- ('help_focus',
- self.config.get_color('help_focus_fg'),
- self.config.get_color('help_focus_bg') ),
- ('help_header',
- self.config.get_color('help_header_fg'),
- self.config.get_color('help_header_bg') ),
- ('help_config',
- self.config.get_color('help_config_fg'),
- self.config.get_color('help_config_bg') ),
- ('help_value',
- self.config.get_color('help_value_fg'),
- self.config.get_color('help_value_bg') ),
- ('help_descr',
- self.config.get_color('help_descr_fg'),
- self.config.get_color('help_descr_bg') )
- ]
-
- self.master_frame = urwid.Frame(body=urwid.Filler(urwid.Text('')),
- header=None,
- footer=urwid.Pile([ urwid.Pile([]),
- urwid.Pile([]) ]),
- focus_part='body')
-
- self.nncli_loop = urwid.MainLoop(self.master_frame,
- palette,
- handle_mouse=False)
-
- self.nncli_loop.set_alarm_in(0, self.gui_init_view,
- True if key else False)
-
- self.nncli_loop.run()
+ """Method to initialize and display the GUI"""
+ self.config.state.do_gui = True
+ self.ndb.log = self.nncli_gui.log
+ self.nncli_gui.run()
def cli_list_notes(self, regex, search_string):
-
- note_list, match_regex, all_notes_cnt = \
+ """List the notes on the command line"""
+ note_list, _, _ = \
self.ndb.filter_notes(
search_string,
search_mode='regex' if regex else 'gstyle',
sort_mode=self.config.get_config('sort_mode'))
- for n in note_list:
- flags = utils.get_note_flags(n.note)
- print((str(n.key) + \
+ for nnote in note_list:
+ flags = utils.get_note_flags(nnote.note)
+ print((str(nnote.key) + \
' [' + flags + '] ' + \
- utils.get_note_title(n.note)))
+ utils.get_note_title(nnote.note)))
def cli_note_dump(self, key):
-
+ """Dump a note to the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('ERROR: Key does not exist')
+ self.logger.log('ERROR: Key does not exist')
return
- w = 60
- sep = '+' + '-'*(w+2) + '+'
- t = time.localtime(float(note['modified']))
- mod_time = time.strftime('%a, %d %b %Y %H:%M:%S', t)
+ width = 60
+ sep = '+' + '-' * (width + 2) + '+'
+ localtime = time.localtime(float(note['modified']))
+ mod_time = time.strftime('%a, %d %b %Y %H:%M:%S', localtime)
title = utils.get_note_title(note)
flags = utils.get_note_flags(note)
- category = utils.get_note_category(note)
+ category = utils.get_note_category(note)
print(sep)
- print(('| {:<' + str(w) + '} |').format((' Title: ' + title)[:w]))
- print(('| {:<' + str(w) + '} |').format((' Key: ' + str(note.get('id', 'Localkey: {}'.format(note.get('localkey'))))[:w])))
- print(('| {:<' + str(w) + '} |').format((' Date: ' + mod_time)[:w]))
- print(('| {:<' + str(w) + '} |').format((' Category: ' + category)[:w]))
- print(('| {:<' + str(w) + '} |').format((' Flags: [' + flags + ']')[:w]))
+ print(('| {:<' + str(width) + '} |').format(
+ (' Title: ' + title)[:width]))
+ print(('| {:<' + str(width) + '} |').format(
+ (' Key: ' +
+ str(note.get(
+ 'id',
+ 'Localkey: {}'.format(note.get('localkey'))
+ )
+ )[:width]
+ )))
+ print(('| {:<' + str(width) + '} |').format(
+ (' Date: ' + mod_time)[:width]))
+ print(('| {:<' + str(width) + '} |').format(
+ (' Category: ' + category)[:width]))
+ print(('| {:<' + str(width) + '} |').format(
+ (' Flags: [' + flags + ']')[:width]))
print(sep)
print((note['content']))
def cli_dump_notes(self, regex, search_string):
-
- note_list, match_regex, all_notes_cnt = \
+ """Dump multiple notes to the command line"""
+ note_list, _, _ = \
self.ndb.filter_notes(
search_string,
search_mode='regex' if regex else 'gstyle',
sort_mode=self.config.get_config('sort_mode'))
- for n in note_list:
- self.cli_note_dump(n.key)
+ for note in note_list:
+ self.cli_note_dump(note.key)
def cli_note_create(self, from_stdin, title):
-
+ """Create a new note from the command line"""
if from_stdin:
content = ''.join(sys.stdin)
else:
- content = self.exec_cmd_on_note(None)
+ content = exec_cmd_on_note(None, self.config, self.nncli_gui,
+ self.logger)
if title:
content = title + '\n\n' + content if content else ''
if content:
- self.log('New note created')
+ self.logger.log('New note created')
self.ndb.create_note(content)
self.sync_notes()
def cli_note_import(self, from_stdin):
-
+ """Import a note from the command line"""
if from_stdin:
raw = ''.join(sys.stdin)
else:
- raw = self.exec_cmd_on_note(None)
+ raw = exec_cmd_on_note(None, self.config, self.nncli_gui,
+ self.logger)
if raw:
try:
note = json.loads(raw)
- self.log('New note created')
+ self.logger.log('New note created')
self.ndb.import_note(note)
self.sync_notes()
- except json.decoder.JSONDecodeError as e:
- self.log('(IMPORT) Decoding JSON has failed: {}'.format(e))
+ except json.decoder.JSONDecodeError as ex:
+ self.logger.log(
+ '(IMPORT) Decoding JSON has failed: {}'.format(ex))
sys.exit(1)
- except ValueError as e:
- self.log('(IMPORT) ValueError: {}'.format(e))
+ except ValueError as ex:
+ self.logger.log('(IMPORT) ValueError: {}'.format(ex))
sys.exit(1)
def cli_note_export(self, key):
-
+ """Export a note to the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('ERROR: Key does not exist')
+ self.logger.log('ERROR: Key does not exist')
return
print(json.dumps(note, indent=2))
def cli_export_notes(self, regex, search_string):
-
- note_list, match_regex, all_notes_cnt = \
+ """Export multiple notes to the command line"""
+ note_list, _, _ = \
self.ndb.filter_notes(
search_string,
search_mode='regex' if regex else 'gstyle',
@@ -984,13 +179,14 @@ def cli_export_notes(self, regex, search_string):
print(json.dumps(notes_data, indent=2))
def cli_note_edit(self, key):
-
+ """Edit a note from the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('ERROR: Key does not exist')
+ self.logger.log('ERROR: Key does not exist')
return
- content = self.exec_cmd_on_note(note)
+ content = exec_cmd_on_note(note, self.config, self.nncli_gui,
+ self.logger)
if not content:
return
@@ -998,65 +194,66 @@ def cli_note_edit(self, key):
md5_new = hashlib.md5(content.encode('utf-8')).digest()
if md5_old != md5_new:
- self.log('Note updated')
+ self.logger.log('Note updated')
self.ndb.set_note_content(note['localkey'], content)
self.sync_notes()
else:
- self.log('Note unchanged')
+ self.logger.log('Note unchanged')
def cli_note_delete(self, key, delete):
-
+ """Delete a note from the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('ERROR: Key does not exist')
+ self.logger.log('ERROR: Key does not exist')
return
self.ndb.set_note_deleted(key, delete)
self.sync_notes()
def cli_note_favorite(self, key, favorite):
-
+ """Favorite a note from the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('ERROR: Key does not exist')
+ self.logger.log('ERROR: Key does not exist')
return
self.ndb.set_note_favorite(key, favorite)
self.sync_notes()
def cli_note_category_get(self, key):
-
+ """Get a note category from the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('ERROR: Key does not exist')
- return
+ self.logger.log('ERROR: Key does not exist')
+ return ''
category = utils.get_note_category(note)
return category
def cli_note_category_set(self, key, category):
-
+ """Set a note category from the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('Error: Key does not exist')
+ self.logger.log('Error: Key does not exist')
return
self.ndb.set_note_category(key, category.lower())
self.sync_notes()
def cli_note_category_rm(self, key):
-
+ """Remove a note category from the command line"""
note = self.ndb.get_note(key)
if not note:
- self.log('Error: Key does not exist')
+ self.logger.log('Error: Key does not exist')
return
old_category = self.cli_note_category_get(key)
if old_category:
self.cli_note_category_set(key, '')
-def SIGINT_handler(signum, frame):
+def sigint_handler(signum, frame):
+ """Handle sigint"""
print('\nSignal caught, bye!')
sys.exit(1)
-signal.signal(signal.SIGINT, SIGINT_handler)
+signal.signal(signal.SIGINT, sigint_handler)
diff --git a/nncli/notes_db.py b/nncli/notes_db.py
@@ -12,9 +12,11 @@ class WriteError(RuntimeError):
pass
class NotesDB():
- """NotesDB will take care of the local notes database and syncing with SN.
"""
- def __init__(self, config, log, update_view):
+ NotesDB will take care of the local notes database and syncing with
+ NextCloud Notes
+ """
+ def __init__(self, config, log, update_view=None):
self.config = config
self.log = log
self.update_view = update_view
@@ -62,6 +64,9 @@ def __init__(self, config, log, update_view):
self.config.get_config('nn_password'),
self.config.get_config('nn_host'))
+ def set_update_view(self, update_view):
+ self.update_view = update_view
+
def filtered_notes_sort(self, filtered_notes, sort_mode='date'):
if sort_mode == 'date':
if self.config.get_config('favorite_ontop') == 'yes':
diff --git a/nncli/utils.py b/nncli/utils.py
@@ -1,6 +1,83 @@
# -*- coding: utf-8 -*-
-import datetime, random, re
+import datetime
+import random
+import re
+import shlex
+
+import subprocess
+from subprocess import CalledProcessError
+
+from . import temp
+
+def get_editor(config, logger):
+ """Get the editor"""
+ editor = config.get_config('editor')
+ if not editor:
+ logger.log('No editor configured!')
+ return None
+ return editor
+
+def get_pager(config, logger):
+ """Get the pager"""
+ pager = config.get_config('pager')
+ if not pager:
+ logger.log('No pager configured!')
+ return None
+ return pager
+
+def exec_cmd_on_note(note, config, gui, logger, cmd=None, raw=False):
+ """Execute an external command to operate on the note"""
+
+ if not cmd:
+ cmd = get_editor(config, logger)
+ if not cmd:
+ return None
+
+ tfile = temp.tempfile_create(
+ note if note else None,
+ raw=raw,
+ tempdir=config.get_config('tempdir')
+ )
+ fname = temp.tempfile_name(tfile)
+
+ if config.state.do_gui:
+ focus_position = 0
+ try:
+ focus_position = gui.gui_body_get().focus_position
+ except IndexError:
+ pass
+
+ subs = {'fname': fname, 'line': focus_position + 1}
+ cmd_list = [c.format(**subs) for c in shlex.split(cmd)]
+
+ # if the filename wasn't able to be subbed, append it
+ # this makes it fully backwards compatible with previous configs
+ if '{fname}' not in cmd:
+ cmd_list.append(fname)
+
+ logger.log("EXECUTING: {}".format(cmd_list))
+
+ try:
+ subprocess.check_call(cmd_list)
+ except CalledProcessError as ex:
+ logger.log('Command error: %s' % ex)
+ temp.tempfile_delete(tfile)
+ return None
+
+ content = None
+ if not raw:
+ content = temp.tempfile_content(tfile)
+ if not content or content == '\n':
+ content = None
+
+ temp.tempfile_delete(tfile)
+
+ if config.state.do_gui:
+ gui.nncli_loop.screen.clear()
+ gui.nncli_loop.draw_screen()
+
+ return content
def generate_random_key():
"""Generate random 30 digit (15 byte) hex string.
diff --git a/nncli/view_help.py b/nncli/view_help.py
@@ -77,14 +77,14 @@ def create_config_help_lines(self):
'help_header',
'help_focus'))
for c in self.config.configs:
- if c in [ 'sn_username', 'sn_password' ]: continue
+ if c in [ 'nn_username', 'nn_password' ]: continue
lines.append(
urwid.AttrMap(urwid.AttrMap(
urwid.Text(
[
('help_descr', ('{:>' + str(self.descr_width) + '} ').format(self.config.get_config_descr(c))),
('help_config', ('{:>' + str(self.config_width) + '} ').format('cfg_' + c)),
- ('help_value', "'" + self.config.get_config(c) + "'")
+ ('help_value', "'" + str(self.config.get_config(c)) + "'")
]
),
attr_map = None,
diff --git a/tests/test_nncli.py b/tests/test_nncli.py
@@ -12,6 +12,7 @@
def mock_nncli(mocker):
mocker.patch('logging.getLogger')
mocker.patch('nncli.nncli.NotesDB')
+ mocker.patch('nncli.nncli.NncliGui')
mocker.patch('os.mkdir')
mocker.patch.object(RotatingFileHandler, '_open')
mocker.patch('subprocess.check_output')
@@ -28,56 +29,19 @@ def assert_initialized():
RotatingFileHandler._open.assert_called_once()
os.mkdir.assert_called_once()
-def test_init_no_tempdir(mocker, mock_nncli):
- mock_get_config(mocker, ['what', '', 'duh', 'duh', 'duh'])
- nn = nncli.nncli.Nncli(False)
- assert_initialized()
- assert nn.tempdir == None
- os.mkdir.assert_called_with('duh')
-
def test_init(mocker, mock_nncli):
- mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh'])
+ mock_get_config(mocker, ['what', 'what', 'duh', 'duh', 'duh'])
nn = nncli.nncli.Nncli(False)
assert_initialized()
- assert nn.tempdir == 'blah'
def test_init_notesdb_fail(mocker, mock_nncli):
- mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh'])
+ mock_get_config(mocker, ['what', 'what', 'duh', 'duh', 'duh'])
mocker.patch('nncli.nncli.NotesDB',
new=mocker.MagicMock(side_effect=SystemExit)
)
with pytest.raises(SystemExit):
nn = nncli.nncli.Nncli(False)
-def test_get_editor(mocker, mock_nncli):
- mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh', 'vim', ''])
- nn = nncli.nncli.Nncli(False)
- assert_initialized()
- assert nn.get_editor() == 'vim'
- assert nn.get_editor() == None
-
-def test_get_pager(mocker, mock_nncli):
- mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh', 'less', ''])
- nn = nncli.nncli.Nncli(False)
- assert_initialized()
- assert nn.get_editor() == 'less'
- assert nn.get_editor() == None
-
-def test_get_diff(mocker, mock_nncli):
- mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh', 'diff', ''])
- nn = nncli.nncli.Nncli(False)
- assert_initialized()
- assert nn.get_editor() == 'diff'
- assert nn.get_editor() == None
-
-@pytest.mark.skip
-def test_exec_cmd_on_note(mocker, mock_nncli):
- mocker.patch.object(
- 'nncli.nncli.Nncli',
- get_editor,
- new=mocker.MagicMock(return_value='vim'))
- mocker.patch('nncli.temp.tempfile_create')
-
@pytest.mark.skip
def test_exec_diff_on_note():
pass