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 +5 -0
- data/bin/fnote +23 -18
- data/lib/fuzzy_notes.rb +1 -1
- data/lib/fuzzy_notes/{password_protected.rb → authentication.rb} +6 -1
- data/lib/fuzzy_notes/cipher.rb +82 -32
- data/lib/fuzzy_notes/evernote_sync.rb +38 -26
- data/lib/fuzzy_notes/fuzzy_finder.rb +20 -11
- data/lib/fuzzy_notes/logger.rb +10 -9
- data/lib/fuzzy_notes/notes.rb +128 -57
- metadata +11 -11
data/TODO
CHANGED
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("-
|
26
|
-
opts.on("
|
27
|
-
opts.on("-
|
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
|
-
|
34
|
-
|
37
|
+
# parse args
|
38
|
+
#
|
35
39
|
begin
|
36
40
|
optparse.parse!(ARGV)
|
37
|
-
|
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
|
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
|
-
|
59
|
-
|
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[:
|
66
|
+
:color => (options[:no_color] || config[:no_color]) ? false : true,
|
64
67
|
:keywords => ARGV)
|
65
68
|
|
66
|
-
|
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]
|
data/lib/fuzzy_notes.rb
CHANGED
data/lib/fuzzy_notes/cipher.rb
CHANGED
@@ -2,48 +2,63 @@ require 'gibberish'
|
|
2
2
|
|
3
3
|
class FuzzyNotes::Cipher
|
4
4
|
include FuzzyNotes::Logger
|
5
|
-
include FuzzyNotes::
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
56
|
+
def process_data(data, cipher)
|
40
57
|
begin
|
41
|
-
|
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 "#{
|
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 "#{
|
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
|
10
|
-
include FuzzyNotes::PasswordProtected
|
9
|
+
include FuzzyNotes::Authentication
|
11
10
|
|
12
|
-
USER_STORE_URL = '
|
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 "#{
|
30
|
-
log.
|
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 "#{
|
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 "#{
|
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
|
-
|
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
|
-
|
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
|
-
|
87
|
-
|
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 =
|
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 "#{
|
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 "#{
|
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
|
-
|
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 #{
|
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, :
|
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)
|
20
|
-
|
21
|
-
|
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, @
|
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? { |
|
47
|
+
@keywords ? @keywords.any? { |keyword| /#{keyword}/ === file_path } : false
|
43
48
|
end
|
44
49
|
|
45
50
|
def full_text_match?(file_path)
|
46
|
-
|
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
|
data/lib/fuzzy_notes/logger.rb
CHANGED
@@ -27,16 +27,17 @@ end
|
|
27
27
|
|
28
28
|
module FuzzyNotes::Logger
|
29
29
|
module Colors
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
data/lib/fuzzy_notes/notes.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
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 => @
|
31
|
+
:extensions => @valid_extensions,
|
35
32
|
:full_text_search => params[:full_text_search] })
|
36
|
-
@all_notes, @matching_notes = finder.
|
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
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
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
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
91
|
-
|
92
|
-
|
93
|
-
|
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 =
|
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(:
|
120
|
+
print_notes(:all_if_empty => true)
|
105
121
|
end
|
106
122
|
|
107
123
|
def evernote_sync
|
108
|
-
unless
|
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
|
121
|
-
@
|
122
|
-
|
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
|
-
#
|
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
|
170
|
+
def bashify_note_paths(paths)
|
129
171
|
paths.map {|n| "\"#{n}\""}.join(' ')
|
130
172
|
end
|
131
173
|
|
132
|
-
def
|
133
|
-
|
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
|
137
|
-
|
190
|
+
def matching_notes(params = {})
|
191
|
+
(@matching_notes.empty? && params[:all_if_empty]) ? @all_notes : @matching_notes
|
138
192
|
end
|
139
193
|
|
140
|
-
|
141
|
-
|
142
|
-
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70119415052580
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: gibberish
|
27
|
-
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: *
|
35
|
+
version_requirements: *70119415052080
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: evernote
|
38
|
-
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: *
|
46
|
+
version_requirements: *70119415051600
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sanitize
|
49
|
-
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: *
|
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
|