fuzzy_notes 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/TODO CHANGED
@@ -1,3 +1,8 @@
1
1
  TODO
2
+ - README
3
+ - ask for missing params during evernote AUTH
4
+ - debugging output
5
+ - figure out good output spacing
2
6
  - test suite
3
7
  - 1.9 testing
8
+ - add a per-note password option for enc/dec
data/bin/fnote CHANGED
@@ -9,36 +9,38 @@ require 'rubygems'
9
9
  require 'fuzzy_notes'
10
10
 
11
11
  CONFIG_PATH = "#{ENV['HOME']}/.fuzzy_notes"
12
+ OPT_ACTIONS = [:print, :list, :info, :encrypt, :decrypt, :update_evernotes].freeze
13
+ OPT_KEYWORDS_REQUIRED = [:print, :edit, :encrypt, :decrypt].freeze
12
14
 
13
15
  options = {}
14
16
  optparse = OptionParser.new do |opts|
15
17
  opts.banner = "Usage: fnote [options] [keyword1, keyword2...]"
16
18
 
17
19
  opts.on("-c", "--config [CONFIG]", "Specify config file") { |opt| options[:config] = opt}
20
+ opts.on("-n", "--note-path [PATH]", "Specify config file") { |opt| options[:note_path] = opt}
21
+ opts.on("--editor [EDITOR]", "Editor of choice") { |opt| options[:editor] = opt}
22
+ opts.on("-s", "--search", "Perform a full text search when matching notes") { |opt| options[:search] = true}
18
23
  opts.on("-p", "--print", "Dump matching notes to stdout") { |opt| options[:print] = true }
19
- opts.on("-l", "--list", "List matching notes") { |opt| options[:list] = true }
24
+ opts.on("-l", "--list", "List all or matching notes") { |opt| options[:list] = true }
20
25
  opts.on("-i", "--info", "Show statistics for matching notes") { |opt| options[:info] = true }
21
- opts.on("-s", "--search", "Perform a full text search when matching notes") { |opt| options[:search] = true }
22
- opts.on("-v", "--verbose", "Enable debug output") { |opt| options[:verbose] = true }
23
26
  opts.on("-e", "--encrypt", "Encrypt matching notes") { |opt| options[:encrypt] = true }
24
27
  opts.on("-d", "--decrypt", "Decrypt matching notes") { |opt| options[:decrypt] = true }
25
- opts.on("-n", "--no-color", "Turn off ANSI color") { |opt| options[:color] = false}
26
- opts.on("-u", "--evernote-update", "Synchronize evernote directory") { |opt| options[:evernote_update] = true }
27
- opts.on("-h", "--help", "Show this message") {
28
+ opts.on("-v", "--verbose", "Enable debug output") { |opt| options[:verbose] = true }
29
+ opts.on("--no-color", "Turn off ANSI color") { |opt| options[:no_color] = true}
30
+ opts.on("-u", "--update-evernotes", "Synchronize evernote directory") { |opt| options[:update_evernotes] = true }
31
+ opts.on("-h", "--help", "Show usage") {
28
32
  puts opts
29
33
  exit
30
34
  }
31
35
  end
32
36
 
33
- need_extra_args = [:print, :edit, :encrypt, :decrypt]
34
-
37
+ # parse args
38
+ #
35
39
  begin
36
40
  optparse.parse!(ARGV)
37
- # edit is the default action
38
- options[:edit] = true if options.values.compact.empty?
39
-
41
+ options[:edit] = true if options.values_at(*OPT_ACTIONS).compact.empty?
40
42
  # check for required args
41
- if need_extra_args.any? {|opt| options[opt]} && ARGV.empty?
43
+ if OPT_KEYWORDS_REQUIRED.any? {|opt| options[opt]} && ARGV.empty?
42
44
  puts optparse
43
45
  exit
44
46
  end
@@ -51,22 +53,25 @@ end
51
53
  config_path = \
52
54
  File.exists?(options[:config].to_s) && options[:config] ||
53
55
  File.exists?(CONFIG_PATH) && CONFIG_PATH
54
-
55
56
  config = config_path ? YAML::load_file(config_path) : {}
56
57
  log.info("config file not found, using defaults") if config.empty?
57
58
 
58
- notes = FuzzyNotes::Notes.new(:editor => config[:editor],
59
- :note_paths => config[:note_paths],
59
+ # grab notes
60
+ #
61
+ notes = FuzzyNotes::Notes.new(:editor => options[:editor] || config[:editor],
62
+ :note_paths => [options[:note_path] || config[:note_paths]].flatten,
60
63
  :evernote_params => config[:evernote],
61
64
  :full_text_search => options[:search] || config[:full_text_search],
62
65
  :log_level => (options[:verbose] || config[:verbose]) ? :debug : :info,
63
- :color => (options[:color] == false || config[:color] == false) ? false : true,
66
+ :color => (options[:no_color] || config[:no_color]) ? false : true,
64
67
  :keywords => ARGV)
65
68
 
66
- notes.evernote_sync if options[:evernote_update]
67
-
69
+ # perform action
70
+ #
68
71
  if options[:list]
69
72
  notes.list
73
+ elsif options[:update_evernotes]
74
+ notes.evernote_sync
70
75
  elsif options[:info]
71
76
  notes.info
72
77
  elsif options[:print]
@@ -4,7 +4,7 @@ require 'rubygems'
4
4
  module FuzzyNotes; end
5
5
 
6
6
  require 'fuzzy_notes/logger'
7
- require 'fuzzy_notes/password_protected'
7
+ require 'fuzzy_notes/authentication'
8
8
  require 'fuzzy_notes/cipher'
9
9
  require 'fuzzy_notes/evernote_sync'
10
10
  require 'fuzzy_notes/fuzzy_finder'
@@ -1,4 +1,9 @@
1
- module FuzzyNotes::PasswordProtected
1
+ module FuzzyNotes::Authentication
2
+
3
+ def get_username
4
+ printf 'Enter username: '
5
+ gets
6
+ end
2
7
 
3
8
  def get_password
4
9
  printf 'Enter password (will not be shown):'
@@ -2,48 +2,63 @@ require 'gibberish'
2
2
 
3
3
  class FuzzyNotes::Cipher
4
4
  include FuzzyNotes::Logger
5
- include FuzzyNotes::PasswordProtected
5
+ include FuzzyNotes::Authentication
6
6
 
7
7
  PLAINTEXT_EXT = 'txt'
8
8
  CIPHERTEXT_EXT = 'enc'
9
+ TMP_FILE_PREFIX = 'fuzzy_notes'
10
+ ENCRYPT_METHOD_MATCHER = /\Aencrypt_(data|file|files|from_tempfile|from_tempfiles)\Z/
11
+ DECRYPT_METHOD_MATCHER = /\Adecrypt_(data|file|files|to_tempfile|to_tempfiles)\Z/
9
12
 
10
- def encrypt(file_paths, opts = {})
11
- @action = :enc
12
- apply_cipher(file_paths, opts)
13
- end
13
+ private
14
+
15
+ def method_missing(method_sym, *args)
16
+ method_name = method_sym.to_s
17
+ log.debug "args: #{args.inspect}"
18
+ last_arg = args.last
19
+ opts = last_arg.is_a?(Hash) ? last_arg : {}
20
+
21
+ case method_name
22
+ when ENCRYPT_METHOD_MATCHER
23
+ @action = :enc
24
+ when DECRYPT_METHOD_MATCHER
25
+ @action = :dec
26
+ else super
27
+ end
14
28
 
15
- def decrypt(file_paths, opts = {})
16
- @action = :dec
17
- apply_cipher(file_paths, opts)
29
+ case $1
30
+ when 'data'
31
+ process_data(args.first, Gibberish::AES.new(get_password))
32
+ when 'file'
33
+ process_file(args.first, Gibberish::AES.new(get_password), opts)
34
+ when 'files'
35
+ cipher = Gibberish::AES.new(get_password)
36
+ args.first.map { |path| process_file(path, cipher, opts) }
37
+ when 'to_tempfile', 'from_tempfile'
38
+ self.send(method_sym, args.first, Gibberish::AES.new(get_password))
39
+ when 'to_tempfiles', 'from_tempfiles'
40
+ cipher = Gibberish::AES.new(get_password)
41
+ args.first.map { |file_path| self.send(method_name[0..-2], *file_path, cipher) }
42
+ end
18
43
  end
19
44
 
20
- private
45
+ def process_file(path, cipher, opts = {})
46
+ return unless path && valid_filename?(path)
47
+ content = File.read(path)
48
+ return unless valid_content?(content)
49
+ log.info "#{decrypt? ? 'de' : 'en'}crypting file #{PATH_COLOR} #{path}"
21
50
 
22
- def apply_cipher(file_paths, opts)
23
- cipher = Gibberish::AES.new(get_password)
24
- case file_paths
25
- when Array
26
- file_paths.each do |path|
27
- if encrypt? && self.class.encrypted?(path) ||
28
- decrypt? && !self.class.encrypted?(path)
29
- log.warn "#{Colors::PATH} #{path} #{Colors::DEFAULT} is #{encrypt? ? 'already' : 'not'} encrypted, skipping"
30
- next
31
- end
32
- process_file(path, cipher, opts)
33
- end
34
- when String
35
- process_file(file_paths, cipher, opts)
36
- end
51
+ processed_content = process_data(content, cipher)
52
+ replace_file!(path, processed_content) if processed_content && opts[:replace]
53
+ processed_content
37
54
  end
38
55
 
39
- def process_file(path, cipher, opts)
56
+ def process_data(data, cipher)
40
57
  begin
41
- log.debug "#{@action} '#{path}'"
42
- content = cipher.send(@action, File.read(path))
43
- replace_file!(path, content) if opts[:replace]
44
- content
58
+ cipher.send(@action, data) unless data.blank?
45
59
  rescue OpenSSL::Cipher::CipherError => e
46
60
  log.error e
61
+ nil
47
62
  end
48
63
  end
49
64
 
@@ -51,13 +66,49 @@ private
51
66
  dirname = File.dirname(path)
52
67
  filename = File.basename(path, '.*')
53
68
 
54
- log.info "#{Colors::CREATE} writing #{decrypt? ? 'un' : ''}encrypted file: #{Colors::PATH} #{dirname}/#{filename}.#{extension}"
69
+ log.info "#{CREATE_COLOR} writing #{decrypt? ? 'un' : ''}encrypted file: #{PATH_COLOR} #{dirname}/#{filename}.#{extension}"
55
70
  File.open("#{dirname}/#{filename}.#{extension}", 'w') { |f| f << contents }
56
71
 
57
- log.info "#{Colors::DELETE} deleting #{decrypt? ? '' : 'un'}encrypted file: #{Colors::PATH} #{path}"
72
+ log.info "#{DELETE_COLOR} deleting #{decrypt? ? '' : 'un'}encrypted file: #{PATH_COLOR} #{path}"
58
73
  File.delete(path)
59
74
  end
60
75
 
76
+ def decrypt_to_tempfile(file_path, cipher)
77
+ return unless file_path
78
+ content = process_file(file_path, cipher)
79
+ content && Tempfile.open(TMP_FILE_PREFIX) do |tmp_file|
80
+ tmp_file << content
81
+ tmp_file.path
82
+ end
83
+ end
84
+
85
+ def encrypt_from_tempfile(tmp_file, file_path, cipher)
86
+ return unless tmp_file && file_path
87
+ content = File.read(tmp_file)
88
+ return unless valid_content?(content)
89
+
90
+ log.info "#{CREATE_COLOR} writing encrypted file: #{PATH_COLOR} #{file_path}"
91
+ File.open(file_path, 'w') do |file|
92
+ file << process_data(content, cipher)
93
+ end
94
+ end
95
+
96
+ def valid_filename?(path)
97
+ if encrypt? && self.class.encrypted?(path) || decrypt? && !self.class.encrypted?(path)
98
+ log.warn "#{PATH_COLOR} #{path} #{DEFAULT_COLOR} is #{encrypt? ? 'already' : 'not'} encrypted, skipping"
99
+ return false
100
+ end
101
+ true
102
+ end
103
+
104
+ def valid_content?(content)
105
+ if content.blank?
106
+ log.warn "file is empty, skipping"
107
+ return false
108
+ end
109
+ true
110
+ end
111
+
61
112
  def extension
62
113
  decrypt? ? PLAINTEXT_EXT : CIPHERTEXT_EXT
63
114
  end
@@ -74,5 +125,4 @@ private
74
125
  File.extname(path)[1..-1] == CIPHERTEXT_EXT
75
126
  end
76
127
 
77
-
78
128
  end
@@ -6,13 +6,12 @@ require 'ostruct'
6
6
 
7
7
  class FuzzyNotes::EvernoteSync
8
8
  include FuzzyNotes::Logger
9
- include Colors
10
- include FuzzyNotes::PasswordProtected
9
+ include FuzzyNotes::Authentication
11
10
 
12
- USER_STORE_URL = 'https://evernote.com/edam/user'
13
- NOTE_STORE_URL = 'http://evernote.com/edam/note'
14
- NOTE_EXT = 'html'
15
- MAX_NOTES = 1000
11
+ USER_STORE_URL = 'http://evernote.com/edam/user'
12
+ NOTE_STORE_URL = 'http://evernote.com/edam/note'
13
+ NOTE_EXT = 'html'
14
+ MAX_NOTES = 1000
16
15
 
17
16
  # opts should be a hash with the following keys:
18
17
  # :username, :password, :consumer_key, :consumer_secret
@@ -26,16 +25,17 @@ MAX_NOTES = 1000
26
25
 
27
26
  def sync
28
27
  return unless authenticated? && valid_sync_path?
29
- log.info "#{IMPORT} synchronizing with Evernote account..."
30
- log.indent(2) { log.info "#{IMPORT} checking for updates..." }
28
+ log.info "syncing evernote directory #{PATH_COLOR} #{@path}"
29
+ log.info "#{IMPORT_COLOR} synchronizing with Evernote account..."
30
+ log.indent(2) { log.info "#{IMPORT_COLOR} checking for updates..." }
31
31
  log.indent(4) do
32
32
  notebook_structs = fetch_notebooks
33
- log.info "#{IMPORT} syncing Evernote deletions..."
33
+ log.info "#{IMPORT_COLOR} syncing Evernote deletions..."
34
34
  log.indent(2) do
35
35
  propagate_evernote_deletions(notebook_structs)
36
36
  end
37
37
  notebook_structs.each do |notebook_struct|
38
- log.info "#{IMPORT} syncing notebook #{NOTE} #{notebook_struct.name}"
38
+ log.info "#{IMPORT_COLOR} syncing notebook #{NOTE_COLOR} #{notebook_struct.name}"
39
39
  log.indent(2) do
40
40
  create_local_notebook(notebook_struct)
41
41
  sync_notes(notebook_struct)
@@ -75,26 +75,33 @@ private
75
75
  def propagate_evernote_deletions(notebook_structs)
76
76
  evernote_dir_entries = Dir["#{@path}/*"]
77
77
  evernote_dir_entries.each do |notebook_path|
78
- notebook_name = File.basename(notebook_path)
79
- notebook_match = notebook_structs.find { |ns| sanitize_filename(ns.name) == notebook_name }
78
+ notebook_match = notebook_structs.find { |ns| sanitize_filename(ns.name) == File.basename(notebook_path) }
80
79
  unless notebook_match
81
- log.info "#{DELETE} notebook #{NOTE} #{notebook_name} #{DELETE} has been deleted from Evernote"
82
- verify_deletion(notebook_path)
80
+ delete_notebook!(notebook_path)
83
81
  else
84
82
  note_entries = Dir["#{notebook_path}/*"]
85
83
  note_entries.each do |note_path|
86
- note_title = File.basename(note_path, '.*')
87
- unless notebook_match.notes.any? { |n| sanitize_filename(n.title) == note_title }
88
- log.info "#{DELETE} note #{NOTE} #{note_title} #{DELETE} has been deleted from Evernote"
89
- verify_deletion(note_path)
84
+ unless notebook_match.notes.any? { |n| sanitize_filename(n.title) == File.basename(note_path, '.*') }
85
+ delete_note!(note_path)
90
86
  end
91
87
  end
92
88
  end
93
89
  end
94
90
  end
95
91
 
92
+ def delete_notebook!(notebook_path)
93
+ log.info "#{DELETE_COLOR} notebook #{NOTE_COLOR} #{File.basename(notebook_path)} #{DELETE_COLOR} has been deleted from Evernote"
94
+ verify_deletion(notebook_path)
95
+ end
96
+
97
+ def delete_note!(note_path)
98
+ log.info "#{DELETE_COLOR} note #{NOTE_COLOR} #{File.basename(note_path, '.*')} #{DELETE_COLOR} has been deleted from Evernote"
99
+ verify_deletion(note_path)
100
+ end
101
+
96
102
  def sync_notes(notebook_struct)
97
- note_updates = notebook_struct.notes.inject([[],[]]) do |(import, export), note|
103
+ note_updates = \
104
+ notebook_struct.notes.inject([[],[]]) do |(import, export), note|
98
105
  if needs_import?(notebook_struct, note)
99
106
  import << fetch_note_with_content(note)
100
107
  elsif needs_export?(notebook_struct, note)
@@ -109,7 +116,7 @@ private
109
116
  def import_notes(notebook_struct, notes)
110
117
  notes.each do |note|
111
118
  note_path = get_note_path(notebook_struct, note)
112
- log.info "#{IMPORT} importing note #{NOTE} #{note.title} #{DEFAULT} with content length #{NUMBER} #{note.contentLength}"
119
+ log.info "#{IMPORT_COLOR} importing note #{NOTE_COLOR} #{note.title} #{DEFAULT_COLOR} with content length #{NUMBER_COLOR} #{note.contentLength}"
113
120
  File.open(note_path, 'w') { |f| f << note.content }
114
121
  end
115
122
  end
@@ -117,7 +124,7 @@ private
117
124
  def export_notes(notebook_struct, notes)
118
125
  notes.each do |note|
119
126
  note.content = local_note_content(notebook_struct, note)
120
- log.info "#{EXPORT} exporting note #{NOTE} #{note.title} #{DEFAULT} with content length #{NUMBER} #{note.content.length}"
127
+ log.info "#{EXPORT_COLOR} exporting note #{NOTE_COLOR} #{note.title} #{DEFAULT_COLOR} with content length #{NUMBER_COLOR} #{note.content.length}"
121
128
  begin
122
129
  @note_store.updateNote(@token, note)
123
130
  rescue Evernote::EDAM::Error::EDAMUserException => e
@@ -194,7 +201,7 @@ private
194
201
  def verify_deletion(path)
195
202
  r = nil
196
203
  until r =~ /(Y|y|N|n)/ do
197
- printf "Are you sure you want to delete #{path}? (Y/N) "
204
+ log.info "Are you sure you want to delete #{path}? (Y/N) "
198
205
  r = gets
199
206
  end
200
207
 
@@ -210,18 +217,23 @@ private
210
217
  name
211
218
  end
212
219
 
220
+ def self.evernote?(path)
221
+ File.extname(path)[1..-1] == NOTE_EXT
222
+ end
223
+
213
224
  # authentication helpers
214
225
 
226
+ #TODO: ask for all missing params
215
227
  def authenticate(params)
216
228
  params.merge!(:password => get_password)
217
229
  user_store = Evernote::UserStore.new(USER_STORE_URL, params)
218
230
  begin
219
- user_store.authenticate
231
+ token = user_store.authenticate
232
+ log.info "Evernote authentication was successful for #{USER_COLOR} #{params[:username]}"
233
+ token
220
234
  rescue Evernote::UserStore::AuthenticationFailure
221
- log.error "Evernote authentication failed for #{USER} #{params[:username]}"
235
+ log.error "Evernote authentication failed for #{USER_COLOR} #{params[:username]}"
222
236
  return
223
- ensure
224
- log.info "Evernote authentication was successful for #{USER} #{params[:username]}"
225
237
  end
226
238
  end
227
239
 
@@ -3,7 +3,7 @@ require 'find'
3
3
  class FuzzyNotes::FuzzyFinder
4
4
  include FuzzyNotes::Logger
5
5
 
6
- attr_reader :path, :all_files, :matching_files, :keywords, :extensions, :full_text_search
6
+ attr_reader :path, :all_files, :files_matching_extension, :files_matching_all, :keywords, :extensions, :full_text_search
7
7
 
8
8
  def initialize(path, params = {})
9
9
  @path = path
@@ -16,9 +16,12 @@ class FuzzyNotes::FuzzyFinder
16
16
  def refresh
17
17
  clear_results
18
18
  Find.find(*@path) do |file_path|
19
- if !File.directory?(file_path) && extension_match?(file_path)
20
- @all_files << file_path
21
- @matching_files << file_path if file_match_proc.call(file_path)
19
+ if !File.directory?(file_path)
20
+ @all_files << file_path
21
+ if extension_match?(file_path)
22
+ @files_matching_extension << file_path
23
+ @files_matching_all << file_path if file_match_proc.call(file_path)
24
+ end
22
25
  end
23
26
  end
24
27
  end
@@ -26,11 +29,7 @@ class FuzzyNotes::FuzzyFinder
26
29
  private
27
30
 
28
31
  def clear_results
29
- @all_files, @matching_files = [], []
30
- end
31
-
32
- def file_match_proc
33
- method(@search_type ? :full_text_match? : :file_name_match?)
32
+ @all_files, @files_matching_extension, @files_matching_all = [], [], []
34
33
  end
35
34
 
36
35
  def extension_match?(file_path)
@@ -38,17 +37,27 @@ private
38
37
  !@extensions || @extensions.any? {|ext| /\.#{ext}$/ === file_name }
39
38
  end
40
39
 
40
+ def file_match_proc
41
+ method(@search_type ? :full_text_match? : :file_name_match?)
42
+ end
43
+
44
+ # search procs
45
+
41
46
  def file_name_match?(file_path)
42
- @keywords ? @keywords.any? { |name| /#{name}/ === file_path } : false
47
+ @keywords ? @keywords.any? { |keyword| /#{keyword}/ === file_path } : false
43
48
  end
44
49
 
45
50
  def full_text_match?(file_path)
46
- if @keywords && !@keywords.empty?
51
+ unless @keywords.blank? || encrypted_file?(file_path)
47
52
  file_contents = File.read(file_path)
48
53
  @keywords.any? { |key| /#{key}/m === file_contents }
49
54
  else false
50
55
  end
51
56
  end
52
57
 
58
+ def encrypted_file?(path)
59
+ /\.#{FuzzyNotes::Cipher::CIPHERTEXT_EXT}$/ === path
60
+ end
61
+
53
62
 
54
63
  end
@@ -27,16 +27,17 @@ end
27
27
 
28
28
  module FuzzyNotes::Logger
29
29
  module Colors
30
- PATH = "$blue"
31
- USER = "$green"
32
- NOTE = "$cyan"
33
- NUMBER = "$red"
34
- CREATE = "$green"
35
- DELETE = "$red"
36
- IMPORT = "$green"
37
- EXPORT = "$red"
38
- DEFAULT = "$reset"
30
+ PATH_COLOR = "$blue"
31
+ USER_COLOR = "$green"
32
+ NOTE_COLOR = "$cyan"
33
+ NUMBER_COLOR = "$red"
34
+ CREATE_COLOR = "$green"
35
+ DELETE_COLOR = "$red"
36
+ IMPORT_COLOR = "$green"
37
+ EXPORT_COLOR = "$red"
38
+ DEFAULT_COLOR = "$reset"
39
39
  end
40
+ include Colors
40
41
 
41
42
  def log
42
43
  FuzzyNotes::Log.log
@@ -1,10 +1,11 @@
1
+ require 'tempfile'
2
+
1
3
  class FuzzyNotes::Notes
2
4
  include FuzzyNotes::Logger
3
5
 
4
6
  module Defaults
5
- LOG_LEVEL = 1
7
+ LOG_LEVEL = :info
6
8
  EDITOR = 'vim'
7
- COLOR = true
8
9
  KEYWORDS = []
9
10
  NOTE_PATHS = [ "#{ENV['HOME']}/notes" ]
10
11
  VALID_EXTENSIONS = [ 'txt',
@@ -15,54 +16,43 @@ module Defaults
15
16
  def self.const_missing(*args); end
16
17
  end
17
18
 
18
- OPTS = [:log_level, :color, :editor, :note_paths, :valid_extensions, :keywords, :evernote_params].freeze
19
+ VALID_PARAMS = [:log_level, :color, :editor, :note_paths, :valid_extensions, :keywords, :evernote_params].freeze
19
20
 
20
21
  attr_reader :matching_notes, :all_notes
21
22
 
22
23
  def initialize(params = {})
23
24
  parse_init_params(params)
24
25
  FuzzyNotes::Log.init_log(@log_level, @color)
25
- log.debug "init attributes: \n#{inspect_instance_vars}"
26
-
27
- unless note_paths_valid?
28
- log.error "no valid note paths found, exiting"
29
- exit
30
- end
26
+ log.debug "init params: \n#{inspect_instance_vars}"
27
+ @note_paths = prune_invalid_note_paths!
31
28
 
32
29
  finder = FuzzyNotes::FuzzyFinder.new(@note_paths,
33
30
  { :keywords => @keywords,
34
- :extensions => @extensions,
31
+ :extensions => @valid_extensions,
35
32
  :full_text_search => params[:full_text_search] })
36
- @all_notes, @matching_notes = finder.all_files, finder.matching_files
33
+ @all_notes, @matching_notes = finder.files_matching_extension, finder.files_matching_all
34
+ @cipher = FuzzyNotes::Cipher.new
37
35
  end
38
36
 
39
- # initialize params or use defaults
40
- #
41
- def parse_init_params(params)
42
- OPTS.each do |param|
43
- klass = self.class
44
- klass.send(:attr_reader, param)
45
- default_const = param.to_s.upcase
46
- instance_variable_set("@#{param}", params.include?(param) ? params[param] : Defaults.const_get(default_const) )
47
- end
48
- end
49
- private :parse_init_params
50
-
51
37
  # dump all matching notes to stdout
52
38
  #
53
39
  def cat
40
+ unless encrypted_notes.empty?
41
+ print_notes(:encrypted => true)
42
+ decrypted_notes = @cipher.decrypt_files(encrypted_notes)
43
+ end
44
+
54
45
  matching_notes.each do |note_path|
55
46
  contents = \
56
47
  if FuzzyNotes::Cipher.encrypted?(note_path)
57
- log.info "decrypting note #{Colors::PATH} #{note_path}"
58
- FuzzyNotes::Cipher.new.decrypt(note_path)
59
- elsif evernote?(note_path)
48
+ decrypted_notes.shift
49
+ elsif FuzzyNotes::EvernoteSync.evernote?(note_path)
60
50
  FuzzyNotes::EvernoteSync.sanitize_evernote(note_path)
61
51
  else
62
52
  File.read(note_path)
63
53
  end
64
54
 
65
- unless contents.blank?
55
+ if contents
66
56
  log.info "=== #{note_path} ===\n\n"
67
57
  puts "#{contents}\n"
68
58
  end
@@ -72,74 +62,155 @@ end
72
62
  # edit all matching notes in EDITOR
73
63
  #
74
64
  def edit
75
- exec("#{editor} #{bashify_paths(matching_notes)}") unless matching_notes.empty?
65
+ notes_to_edit = \
66
+ unless encrypted_notes.empty?
67
+ print_notes(:encrypted => true)
68
+ decrypted_tempfiles = @cipher.decrypt_to_tempfiles(encrypted_notes)
69
+ successfully_decrypted_files = decrypted_tempfiles.compact
70
+ plaintext_notes + successfully_decrypted_files
71
+ else plaintext_notes
72
+ end
73
+
74
+ # edit decrypted files
75
+ unless notes_to_edit.empty?
76
+ system("#{editor} #{bashify_note_paths(notes_to_edit)}")
77
+ end
78
+
79
+ # reencrypt decrypted notes
80
+ unless encrypted_notes.empty? || successfully_decrypted_files.empty?
81
+ log.info "#{CREATE_COLOR} re-encrypting edited notes:"
82
+ tempfiles_notes = decrypted_tempfiles.zip(encrypted_notes)
83
+ log.indent do
84
+ tempfiles_notes.each do |(tmpfile, note_path)|
85
+ log.info "#{PATH_COLOR} #{note_path}" if note_path
86
+ end
87
+ end
88
+ log.indent { @cipher.encrypt_from_tempfiles(tempfiles_notes) }
89
+ end
76
90
  end
77
91
 
78
92
  # encrypt matching notes
79
93
  #
80
94
  def encrypt
81
- return if matching_notes.empty?
82
- log.info "encrypting matching notes:"
83
- print_notes
84
- log.indent(2) { FuzzyNotes::Cipher.new.encrypt(matching_notes, :replace => true) }
95
+ return if plaintext_notes.empty?
96
+ print_notes(:plaintext => true)
97
+ log.indent do
98
+ @cipher.encrypt_files(plaintext_notes, :replace => true)
99
+ end
85
100
  end
86
101
 
87
102
  # decrypt matching notes
88
103
  #
89
104
  def decrypt
90
- return if matching_notes.empty?
91
- log.info "decrypting matching notes:"
92
- print_notes
93
- log.indent(2) { FuzzyNotes::Cipher.new.decrypt(matching_notes, :replace => true) }
105
+ return if encrypted_notes.empty?
106
+ print_notes(:encrypted => true)
107
+ log.indent do
108
+ @cipher.decrypt_files(encrypted_notes, :replace => true)
109
+ end
94
110
  end
95
111
 
96
112
  # view WC info for all/matching notes
97
113
  #
98
114
  def info
99
- paths = bashify_paths(matching_notes.empty? ? all_notes : matching_notes)
115
+ paths = bashify_note_paths(matching_notes(:all_if_empty => true))
100
116
  puts `wc $(find #{paths} -type f)`
101
117
  end
102
118
 
103
119
  def list
104
- print_notes(:all => true)
120
+ print_notes(:all_if_empty => true)
105
121
  end
106
122
 
107
123
  def evernote_sync
108
- unless @evernote_params
109
- log.error("no evernote configuration found!")
110
- return
111
- end
112
-
113
- log.info "syncing evernote directory #{Colors::PATH} #{@evernote_params[:note_path]}"
124
+ return unless evernote_params_found?
114
125
  FuzzyNotes::EvernoteSync.new(@evernote_params).sync
115
- log.print_blank_line
116
126
  end
117
127
 
118
128
  private
119
129
 
120
- def note_paths_valid?
121
- @note_paths.any? do |p|
122
- File.directory?(p) || log.warn("note path #{Colors::PATH} #{p} does not exist")
130
+ def encrypted_notes
131
+ @encrypted_notes ||= matching_notes.select { |note_path| FuzzyNotes::Cipher.encrypted?(note_path) }
132
+ end
133
+
134
+ def evernote_notes
135
+ @evernote_notes ||= matching_notes.select { |note_path| FuzzyNotes::EvernoteSync.evernote?(note_path) }
136
+ end
137
+
138
+ def plaintext_notes
139
+ @plaintext_notes ||= matching_notes.select { |note_path| !FuzzyNotes::Cipher.encrypted?(note_path) &&
140
+ !FuzzyNotes::EvernoteSync.evernote?(note_path) }
141
+ end
142
+
143
+ def prune_invalid_note_paths!
144
+ valid_paths = []
145
+ @note_paths.each do |path|
146
+ if File.directory?(path)
147
+ valid_paths << path
148
+ else
149
+ log.warn("note path #{PATH_COLOR} #{path} #{DEFAULT_COLOR} does not exist")
150
+ end
123
151
  end
152
+ if valid_paths.empty?
153
+ log.error "no valid note paths found, exiting"
154
+ exit
155
+ end
156
+ valid_paths
124
157
  end
125
158
 
126
- # bash style, space seperated fashion
159
+ # TODO: grab creds from user
160
+ def evernote_params_found?
161
+ unless @evernote_params
162
+ log.error("required evernote configuration not found!")
163
+ false
164
+ else true
165
+ end
166
+ end
167
+
168
+ # bash helpers
127
169
  #
128
- def bashify_paths(paths)
170
+ def bashify_note_paths(paths)
129
171
  paths.map {|n| "\"#{n}\""}.join(' ')
130
172
  end
131
173
 
132
- def inspect_instance_vars
133
- instance_variables.inject("") { |s, ivar| s << " #{ivar} => #{eval(ivar.to_s).inspect}\n" }
174
+ def print_notes(params = {})
175
+ notes = []
176
+
177
+ notes << encrypted_notes if params[:encrypted]
178
+ notes << evernote_notes if params[:evernote]
179
+ notes << plaintext_notes if params[:plaintext]
180
+ if notes.empty?
181
+ notes << matching_notes(:all_if_empty => true)
182
+ end
183
+
184
+ notes.flatten!
185
+ keys = params.keys.reject { |k| k == :all_if_empty }
186
+ log.info "#{keys.join(',')} notes:" unless keys.empty?
187
+ log.indent { notes.each { |note| log.info "#{PATH_COLOR} #{note}" } }
134
188
  end
135
189
 
136
- def evernote?(path)
137
- File.extname(path)[1..-1] == FuzzyNotes::EvernoteSync::NOTE_EXT
190
+ def matching_notes(params = {})
191
+ (@matching_notes.empty? && params[:all_if_empty]) ? @all_notes : @matching_notes
138
192
  end
139
193
 
140
- def print_notes(params = {})
141
- notes = (matching_notes.empty? && params[:all]) ? all_notes : matching_notes
142
- log.indent(2) { notes.each { |note| log.info "#{Colors::PATH} #{note}" } }
194
+ # initialize to params or use defaults
195
+ #
196
+ def parse_init_params(params)
197
+ VALID_PARAMS.each do |param|
198
+ klass = self.class
199
+ klass.send(:attr_reader, param)
200
+ default_const = param.to_s.upcase
201
+
202
+ value = \
203
+ if params.include?(param)
204
+ params[param]
205
+ else Defaults.const_get(default_const)
206
+ end
207
+ instance_variable_set("@#{param}", value)
208
+ end
143
209
  end
144
210
 
211
+ def inspect_instance_vars
212
+ instance_variables.inject("") { |s, ivar| s << " #{ivar} => #{eval(ivar.to_s).inspect}\n" }
213
+ end
214
+
215
+
145
216
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fuzzy_notes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-08-01 00:00:00.000000000Z
12
+ date: 2011-09-26 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: buffered_logger
16
- requirement: &70150343147260 !ruby/object:Gem::Requirement
16
+ requirement: &70119415052580 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.1.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70150343147260
24
+ version_requirements: *70119415052580
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: gibberish
27
- requirement: &70150343146760 !ruby/object:Gem::Requirement
27
+ requirement: &70119415052080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70150343146760
35
+ version_requirements: *70119415052080
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: evernote
38
- requirement: &70150343146280 !ruby/object:Gem::Requirement
38
+ requirement: &70119415051600 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70150343146280
46
+ version_requirements: *70119415051600
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sanitize
49
- requirement: &70150343145800 !ruby/object:Gem::Requirement
49
+ requirement: &70119415051120 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70150343145800
57
+ version_requirements: *70119415051120
58
58
  description: A note manager with fuzzy path search, full text search, evernote sync,
59
59
  and encryption capabilities
60
60
  email: rut216@gmail.com
@@ -63,12 +63,12 @@ executables:
63
63
  extensions: []
64
64
  extra_rdoc_files: []
65
65
  files:
66
+ - lib/fuzzy_notes/authentication.rb
66
67
  - lib/fuzzy_notes/cipher.rb
67
68
  - lib/fuzzy_notes/evernote_sync.rb
68
69
  - lib/fuzzy_notes/fuzzy_finder.rb
69
70
  - lib/fuzzy_notes/logger.rb
70
71
  - lib/fuzzy_notes/notes.rb
71
- - lib/fuzzy_notes/password_protected.rb
72
72
  - lib/fuzzy_notes.rb
73
73
  - bin/fnote
74
74
  - README