cnote 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9df0e76707bb7a63ff3b121e9e0b4fa6143b66c7
4
- data.tar.gz: 9950e8a6fff2d2c7de76fc9883a211b65312696e
3
+ metadata.gz: 54afc4391700ef178bc0ed861e49cdbcfe30cc73
4
+ data.tar.gz: a5aab3d03e17f4ae079f137e024ba6c6bf733373
5
5
  SHA512:
6
- metadata.gz: ee7ab59688e6773eb02255983795aca1c3957f6bd49ffc8fada57862017121d833cff46d974eef1b937bb92c806878d1b755bef8e4a7365bb7ac876c813b1d84
7
- data.tar.gz: 26d390e8ac70d6179dd256f6fa0e37a262fc1ff4dc26ee956c88c98b87a48b75066b04f37c37792d5edf3d13bb8e585a1f886410a4f1277c1902f8a6b691f877
6
+ metadata.gz: c881765e7c0c228e0e01f9c12c56ba06f9341eb0c9e184fff491d6422f61485e23a0b289f34c2f92e10a22e22ae6fae3d8959733b33a26a29f4abd3116b8854b
7
+ data.tar.gz: 624d176e63393ffa9e5f91124418ecf4780306e7227c14e84d9af043d923ccbda5ae85f5af3d5684244c06acd869efbffd0ee7681991b5738324f1e143b1bb82
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ *.gem
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/Gemfile CHANGED
@@ -2,11 +2,5 @@ source "https://rubygems.org"
2
2
 
3
3
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
- # Added at 2017-09-09 01:27:23 -0700 by tony:
6
- gem "awesome_print", "~> 1.8"
7
-
8
- # Added at 2017-09-09 03:13:22 -0700 by tony:
9
- gem "colorize", "~> 0.8.1"
10
-
11
5
  # Specify your gem's dependencies in cnote.gemspec
12
6
  gemspec
data/classes/config.rb ADDED
@@ -0,0 +1,66 @@
1
+ class Config
2
+ attr_reader :note_path
3
+
4
+ def initialize(path)
5
+ path = File.expand_path path
6
+
7
+ if !File.exists? path
8
+ puts 'Welcome, new user!'
9
+
10
+ @note_path = get_note_path
11
+
12
+ File.open(path, 'w') do |file|
13
+ file.write(YAML.dump(to_hash))
14
+ end
15
+
16
+ puts "Okay, we're ready to go!"
17
+ else
18
+ conf = YAML.load(File.read(path))
19
+
20
+ @note_path = conf['note_path']
21
+ @editor = conf['editor']
22
+ @cursor = conf['prompt_cursor']
23
+ end
24
+ end
25
+
26
+ def get_note_path
27
+ path = nil
28
+
29
+ while !path or !File.exists? path
30
+ print "Enter a path for your note folder: "
31
+
32
+ path = File.expand_path gets.chomp
33
+
34
+ if File.exists? path
35
+ if !File.directory? path
36
+ puts "Hey, that's not a folder!"
37
+ end
38
+ else
39
+ puts "That folder doesn't exist yet. Do you want to create it?"
40
+ case gets.strip.downcase
41
+ when "y", "yes", "yeah", "sure", "ok", "okay", "alright", "yep", "yup"
42
+ FileUtils.mkdir_p path
43
+ puts "Done!"
44
+ else
45
+ puts "Okay."
46
+ end
47
+ end
48
+ end
49
+
50
+ return path
51
+ end
52
+
53
+ def editor
54
+ @editor || ENV['EDITOR']
55
+ end
56
+
57
+ def cursor
58
+ @cursor || '>'
59
+ end
60
+
61
+ def to_hash
62
+ {
63
+ 'note_path' => @note_path
64
+ }
65
+ end
66
+ end
data/classes/note.rb ADDED
@@ -0,0 +1,117 @@
1
+ require 'time'
2
+
3
+ class Note
4
+ attr_reader :title,
5
+ :content,
6
+ :tags,
7
+ :filename,
8
+ :path,
9
+ :modified,
10
+ :created
11
+
12
+ attr_writer :created
13
+
14
+ def initialize(path)
15
+ @meta_regex = /^<!\-{3}(.*)\-{2}>/
16
+
17
+ @content = ''
18
+ @tags = []
19
+ @filename = File.basename(path)
20
+ @path = path
21
+
22
+ refresh
23
+
24
+ @modified = File.mtime(@path) if !@modified
25
+ @created = @modified if !@created
26
+
27
+ @title = 'Untitled' if !@title
28
+ end
29
+
30
+ def refresh
31
+ File.open(@path, 'r') do |file|
32
+ file.each_line do |line|
33
+ line = line.strip
34
+ if @meta_regex =~ line
35
+ parse_meta($~[1])
36
+ elsif !@title
37
+ if line != ''
38
+ @title = line.gsub(/#|[^a-z0-9\s\.\-]/i, '').strip
39
+ end
40
+ else
41
+ @content << line + "\n"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def add_tags(tags)
48
+ @tags = @tags.concat(tags)
49
+ @modified = Time.new
50
+ write_meta
51
+ end
52
+
53
+ def remove_tags(tags)
54
+ @tags = @tags - tags
55
+ @modified = Time.new
56
+ write_meta
57
+ end
58
+
59
+ def excerpt
60
+ @content.gsub(/[#*\-~]/i, '').strip.slice(0, 80)
61
+ end
62
+
63
+ def time_fmt(time)
64
+ time.strftime('%A, %B %e %Y, %l:%M:%S%p')
65
+ end
66
+
67
+ def update
68
+ @modified = Time.new
69
+ write_meta
70
+ end
71
+
72
+ private def parse_meta(meta)
73
+ key, value = meta.split(':', 2).map { |v| v.strip }
74
+
75
+ case key.downcase
76
+ when 'tags'
77
+ @tags = value.split(',').map { |v| v.strip }
78
+ when 'created'
79
+ puts value
80
+ @created = Time.parse(value)
81
+ when 'modified'
82
+ puts value
83
+ @modified = Time.parse(value)
84
+ end
85
+ end
86
+
87
+ private def write_meta
88
+ meta_regex = /<!\-{3}.+:(.*)\-{2}>/
89
+
90
+ File.open(@path, 'r') do |file|
91
+ contents = file.read
92
+
93
+ contents.gsub!(meta_regex, '')
94
+
95
+ trailing_empty = 0
96
+ contents.lines.reverse.each do |line|
97
+ if line.strip == ''
98
+ trailing_empty += 1
99
+ else
100
+ break
101
+ end
102
+ end
103
+
104
+ # Leave two empty lines before metadata.
105
+ contents = contents.lines.slice(0, contents.lines.length - trailing_empty).join('')
106
+
107
+ contents += "\n\n"
108
+ contents += "<!--- created: #{@created} -->\n"
109
+ contents += "<!--- modified: #{@modified} -->\n"
110
+ contents += "<!--- tags: #{@tags.join(', ')} -->\n"
111
+
112
+ File.open(@path, 'w') do |file|
113
+ file.write(contents)
114
+ end
115
+ end
116
+ end
117
+ end
data/classes/notes.rb ADDED
@@ -0,0 +1,348 @@
1
+ require 'ap'
2
+ require 'colorize'
3
+ require 'fileutils'
4
+ require 'time'
5
+ require_relative 'note'
6
+
7
+ class Notes
8
+ def initialize(config)
9
+ @config = config
10
+ @notes = Dir[File.join(@config.note_path, '**', '*')].select do |file|
11
+ File.extname(file) == '.md'
12
+ end.map do |file|
13
+ Note.new(file)
14
+ end
15
+ end
16
+
17
+ #/================================\#
18
+ # REPL type thing #
19
+ #\================================/#
20
+
21
+ def await_command(message = nil)
22
+ puts message if message
23
+ print "#{@config.cursor} ".bold.magenta
24
+ input = STDIN.gets.chomp
25
+
26
+ # Strip and process
27
+ action, *params = input.strip.gsub(/\s{2,}/, ' ').split(' ')
28
+ run_command(action || 'help', params)
29
+ end
30
+
31
+ def run_command(action, params)
32
+ case action.downcase
33
+ when 'new', 'create', 'n', 'c'
34
+ create(params)
35
+ when 'edit', 'open', 'e', 'o'
36
+ open(params)
37
+ when 'delete', 'd', 'rm'
38
+ delete(params)
39
+ when 'peek', 'p'
40
+ peek(params)
41
+ when 'tag', 't'
42
+ tag(params)
43
+ when 'untag', 'ut'
44
+ untag(params)
45
+ when 'search', 'find', 's', 'f'
46
+ search(params.join(' '))
47
+ when 'list', 'l', 'ls'
48
+ list
49
+ when 'help', 'h'
50
+ help
51
+ when 'quit', 'exit', 'close', 'q'
52
+ exit
53
+ else
54
+ puts "Sorry, didn't quite get that..."
55
+ help
56
+ end
57
+
58
+ await_command # Drop back to REPL
59
+ end
60
+
61
+ #/================================\#
62
+ # The Commands #
63
+ #\================================/#
64
+
65
+ def search(term)
66
+ term = term.downcase # Search is case insensitive
67
+ matches = @notes
68
+
69
+ if term.include? '+t '
70
+ term, tags = term.split('+t ')
71
+ tags = tags.split(' ')
72
+ puts "\n Searching: '#{term.strip}' with tags: #{tags}"
73
+ matches = matches.select do |note|
74
+ has_all_tags(note, tags)
75
+ end
76
+ elsif term.include? '-t '
77
+ term, tags = term.split('-t ')
78
+ tags = tags.split(' ')
79
+ puts "\n Searching: '#{term.strip}' without tags: #{tags}"
80
+ matches = matches.select do |note|
81
+ has_none_tags(note, tags)
82
+ end
83
+ end
84
+
85
+ term.strip!
86
+
87
+ @filtered = matches.select do |note|
88
+ note.title.downcase.include?(term) || note.content.downcase.include?(term)
89
+ end
90
+
91
+ # TODO: Sort by most relevant
92
+ # TODO: Highlight keywords where found
93
+ len = @filtered.length
94
+
95
+ print_list("Found #{len} Match#{'es' if len != 1}", @filtered)
96
+ end
97
+
98
+ def create(params)
99
+ if params.first
100
+ dirname = File.dirname(params.first)
101
+ new_filename = File.basename(params.first, File.extname(params.first)) + '.md'
102
+ rel_path = ''
103
+ tags = []
104
+
105
+ if params.include? '+t'
106
+ tags = params.slice(params.index('+t') + 1, params.length)
107
+ puts "CREATING WITH TAGS: #{tags}"
108
+ end
109
+
110
+ if dirname != '.'
111
+ rel_path = dirname
112
+ .gsub(@config.note_path, '')
113
+ .gsub(File.basename(params.first), '')
114
+ end
115
+
116
+ full_path = File.join(@config.note_path, rel_path, new_filename)
117
+
118
+ if File.exists?(full_path)
119
+ if confirm("#{'Whoa!'.bold.red} That file already exists. Overwrite it?")
120
+ File.delete(full_path)
121
+ @notes.each do |note|
122
+ if note.path == full_path
123
+ @notes.delete(note)
124
+ puts 'Removed!'
125
+ end
126
+ end
127
+ else
128
+ return
129
+ end
130
+ end
131
+
132
+ system "#{@config.editor} '#{full_path}'"
133
+
134
+ note = Note.new(full_path)
135
+ note.add_tags(tags) if tags.length > 0
136
+ note.created = Time.new
137
+ note.update
138
+
139
+ @notes << Note.new(full_path)
140
+
141
+ print_list('Created', [note])
142
+ @filtered = [note]
143
+ else
144
+ puts "Please enter a filename as the first parameter"
145
+ end
146
+ end
147
+
148
+ def open(params)
149
+ num = params.first.to_i
150
+ note = @filtered[num - 1]
151
+
152
+ if note
153
+ system "#{@config.editor} '#{note.path}'"
154
+ note.update
155
+ else
156
+ puts "Hey! There is no note #{num}! Nice try."
157
+ end
158
+ end
159
+
160
+ def delete(params)
161
+ num = params.first.to_i
162
+ note = @filtered[num - 1]
163
+
164
+ if note
165
+ if confirm("You're #{'sure'.italic} you want to delete note #{num.to_s.bold.white} with title #{note.title.bold.white}?")
166
+ FileUtils.rm(note.path)
167
+ @notes.delete(note)
168
+ @filtered.delete(note)
169
+ puts "Deleted!"
170
+ else
171
+ puts "Whew! That was close."
172
+ end
173
+ else
174
+ puts "Looks like my job is done here, since note #{num} doesn't exist anyway!"
175
+ end
176
+ end
177
+
178
+ def peek(params)
179
+ note = @filtered[params.first.to_i - 1]
180
+ if note
181
+ puts
182
+ puts '-' * 40
183
+ puts note.title.bold.white
184
+ puts note.content.split("\n").slice(0, 10)
185
+ puts
186
+ puts "... (cont'd) ...".italic.gray
187
+ puts '-' * 40
188
+ puts
189
+ else
190
+ puts "Note doesn't exist!"
191
+ end
192
+ end
193
+
194
+ def tag(params)
195
+ notes = multi_note(params)
196
+
197
+ notes.each do |note|
198
+ tags = params.slice(1, params.length)
199
+ note.add_tags(tags)
200
+ end
201
+
202
+ print_list('Changed', notes)
203
+
204
+ @filtered = notes
205
+
206
+ puts "Added #{params.length - 1} tag#{'s' if params.length != 2} to #{notes.length} note#{'s' if notes.length != 1}."
207
+ end
208
+
209
+ def untag(params)
210
+ notes = multi_note(params)
211
+
212
+ notes.each do |note|
213
+ tags = params.slice(1, params.length)
214
+ note.remove_tags(tags)
215
+ end
216
+
217
+ print_list('Changed', notes)
218
+
219
+ @filtered = notes
220
+
221
+ puts "Removed #{params.length - 1} tag#{'s' if params.length != 2} from #{notes.length} note#{'s' if notes.length != 1}."
222
+ end
223
+
224
+ def help
225
+ puts
226
+ puts "Enter a command with the structure:"
227
+ puts " #{'>'.bold.magenta} action parameter(s)"
228
+ puts
229
+ puts "Actions:"
230
+ puts " - #{'new'.bold.white} #{'filename'.italic}"
231
+ puts " - #{'edit'.bold.white} #{'note_number'.italic}"
232
+ puts " - #{'delete'.bold.white} #{'note_number'.italic}"
233
+ puts " - #{'peek'.bold.white} #{'note_number'.italic}"
234
+ puts " - #{'tag'.bold.white} #{'note_number'.italic}"
235
+ puts " - #{'untag'.bold.white} #{'note_number'.italic}"
236
+ puts " - #{'search'.bold.white} #{'search_term'.italic}"
237
+ puts " - #{'list'.bold.white}"
238
+ puts " - #{'exit'.bold.white}"
239
+ puts " - #{'help'.bold.white}"
240
+ puts
241
+ puts "Alternate actions:"
242
+ puts " Most actions also have aliases that do the same thing."
243
+ puts " These are listed for each command:"
244
+ puts " - new: create, c, n"
245
+ puts " - edit: e, open, o"
246
+ puts " - delete: d, rm"
247
+ puts " - peek: p"
248
+ puts " - tag: t"
249
+ puts " - untag: ut"
250
+ puts " - search: find, f, s"
251
+ puts " - list: l, ls"
252
+ puts " - exit: quit, q, close"
253
+ puts " - help: h"
254
+ puts
255
+ end
256
+
257
+ def list
258
+ @filtered = recently_edited_first(@notes)
259
+ print_list('All Notes', @filtered)
260
+ end
261
+
262
+ #/================================\#
263
+ # Utilities #
264
+ #\================================/#
265
+
266
+ private def print_list(title, notes)
267
+ path_length = @config.note_path.split('/').length
268
+ i = 0
269
+
270
+ puts
271
+ puts " #{title}".bold
272
+ puts " #{'-' * title.length}"
273
+ puts
274
+
275
+ notes.each do |note|
276
+ i += 1
277
+ puts "#{i}.".ljust(4) + note.title.bold
278
+ puts " #{note.path.gsub(@config.note_path, '')}".italic.light_magenta
279
+ if note.tags.length > 0
280
+ tags = note.tags.map { |tag| tag.yellow }
281
+ puts " tags: " + "[#{tags.join('] [')}]"
282
+ else
283
+ puts " <no tags>".gray
284
+ end
285
+ puts ' modified: ' + note.modified.strftime('%a, %b %e %Y, %l:%M%P').italic
286
+ puts ' created: ' + note.created.strftime('%a, %b %e %Y, %l:%M%P').italic
287
+ puts
288
+ end
289
+
290
+ puts " Listed #{i.to_s.bold} Notes"
291
+ puts
292
+ end
293
+
294
+ private def confirm(message = 'Confirm')
295
+ print "#{message} [y/n]"
296
+ case gets.chomp.strip.downcase
297
+ when 'y', 'yes', 'yeah', 'sure', 'yep', 'okay', 'aye'
298
+ return true
299
+ when 'n', 'no', 'nope', 'nay'
300
+ return false
301
+ else
302
+ return confirm("Sorry, didn't quite get that...")
303
+ end
304
+ end
305
+
306
+ private def multi_note(params)
307
+ notes = []
308
+
309
+ params.first.split(',').each do |num|
310
+ note = @filtered[num.to_i - 1]
311
+ if note
312
+ notes << note
313
+ else
314
+ puts "Note #{num} doesn't exist!"
315
+ end
316
+ end
317
+
318
+ notes
319
+ end
320
+
321
+ private def recently_edited_first(notes)
322
+ notes.sort_by { |note| note.modified }.reverse
323
+ end
324
+
325
+ private def has_all_tags(note, tags)
326
+ has = true
327
+ note_tags = note.tags
328
+ tags.each do |tag|
329
+ if !note_tags.include? tag
330
+ has = false
331
+ break
332
+ end
333
+ end
334
+ has
335
+ end
336
+
337
+ private def has_none_tags(note, tags)
338
+ doesnt_have = true
339
+ note_tags = note.tags
340
+ tags.each do |tag|
341
+ if note_tags.include? tag
342
+ doesnt_have = false
343
+ break
344
+ end
345
+ end
346
+ doesnt_have
347
+ end
348
+ end
data/gems.locked ADDED
@@ -0,0 +1,14 @@
1
+ GEM
2
+ specs:
3
+ awesome_print (1.8.0)
4
+ colorize (0.8.1)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ awesome_print (~> 1.8)
11
+ colorize (~> 0.8.1)
12
+
13
+ BUNDLED WITH
14
+ 1.15.4
data/gems.rb ADDED
@@ -0,0 +1,6 @@
1
+
2
+ # Added at 2017-09-09 01:27:23 -0700 by tony:
3
+ gem "awesome_print", "~> 1.8"
4
+
5
+ # Added at 2017-09-09 03:13:22 -0700 by tony:
6
+ gem "colorize", "~> 0.8.1"
data/lib/cnote.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require "colorize"
2
- require_relative "cnote/config"
3
- require_relative "cnote/notes"
4
- require_relative "cnote/version"
2
+ require "cnote/config"
3
+ require "cnote/notes"
4
+ require "cnote/version"
5
5
 
6
6
  # module Cnote
7
7
  # Your code goes here...
@@ -0,0 +1,69 @@
1
+ require "yaml"
2
+ require "fileutils"
3
+
4
+ class Config
5
+ attr_reader :note_path
6
+
7
+ def initialize(path)
8
+ path = File.expand_path path
9
+
10
+ if !File.exists? path
11
+ puts "Welcome, new user!"
12
+
13
+ @note_path = get_note_path
14
+
15
+ File.open(path, "w") do |file|
16
+ file.write(YAML.dump(to_hash))
17
+ end
18
+
19
+ puts "Okay, we're ready to go!"
20
+ else
21
+ conf = YAML.load(File.read(path))
22
+
23
+ @note_path = conf["note_path"]
24
+ @editor = conf["editor"]
25
+ @cursor = conf["prompt"]
26
+ end
27
+ end
28
+
29
+ def get_note_path
30
+ path = nil
31
+
32
+ while !path or !File.exists? path
33
+ print "Enter a path for your note folder: "
34
+
35
+ path = File.expand_path gets.chomp
36
+
37
+ if File.exists? path
38
+ if !File.directory? path
39
+ puts "Hey, that's not a folder!"
40
+ end
41
+ else
42
+ puts "That folder doesn't exist yet. Do you want to create it?"
43
+ case gets.strip.downcase
44
+ when "y", "yes", "yeah", "sure", "ok", "okay", "alright", "yep", "yup"
45
+ FileUtils.mkdir_p path
46
+ puts "Done!"
47
+ else
48
+ puts "Okay."
49
+ end
50
+ end
51
+ end
52
+
53
+ return path
54
+ end
55
+
56
+ def editor
57
+ @editor || ENV["EDITOR"]
58
+ end
59
+
60
+ def cursor
61
+ @cursor || ">"
62
+ end
63
+
64
+ def to_hash
65
+ {
66
+ "note_path" => @note_path
67
+ }
68
+ end
69
+ end
data/lib/cnote/note.rb ADDED
@@ -0,0 +1,115 @@
1
+ require "time"
2
+
3
+ class Note
4
+ attr_reader :title,
5
+ :content,
6
+ :tags,
7
+ :filename,
8
+ :path,
9
+ :modified,
10
+ :created
11
+
12
+ attr_writer :created
13
+
14
+ def initialize(path)
15
+ @meta_regex = /^<!\-{3}(.*)\-{2}>/
16
+
17
+ @content = ""
18
+ @tags = []
19
+ @filename = File.basename(path)
20
+ @path = path
21
+
22
+ refresh
23
+
24
+ @modified = File.mtime(@path) if !@modified
25
+ @created = @modified if !@created
26
+
27
+ @title = "Untitled" if !@title
28
+ end
29
+
30
+ def refresh
31
+ File.open(@path, "r") do |file|
32
+ file.each_line do |line|
33
+ line = line.strip
34
+ if @meta_regex =~ line
35
+ parse_meta($~[1])
36
+ elsif !@title
37
+ if line != ""
38
+ @title = line.gsub(/#|[^a-z0-9\s\.\-]/i, "").strip
39
+ end
40
+ else
41
+ @content << line + "\n"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def add_tags(tags)
48
+ @tags = @tags.concat(tags)
49
+ @modified = Time.new
50
+ write_meta
51
+ end
52
+
53
+ def remove_tags(tags)
54
+ @tags = @tags - tags
55
+ @modified = Time.new
56
+ write_meta
57
+ end
58
+
59
+ def excerpt
60
+ @content.gsub(/[#*\-~]/i, "").strip.slice(0, 80)
61
+ end
62
+
63
+ def time_fmt(time)
64
+ time.strftime("%A, %B %e %Y, %l:%M:%S%p")
65
+ end
66
+
67
+ def update
68
+ @modified = Time.new
69
+ write_meta
70
+ end
71
+
72
+ private def parse_meta(meta)
73
+ key, value = meta.split(":", 2).map { |v| v.strip }
74
+
75
+ case key.downcase
76
+ when "tags"
77
+ @tags = value.split(",").map { |v| v.strip }
78
+ when "created"
79
+ @created = Time.parse(value)
80
+ when "modified"
81
+ @modified = Time.parse(value)
82
+ end
83
+ end
84
+
85
+ private def write_meta
86
+ meta_regex = /<!\-{3}.+:(.*)\-{2}>/
87
+
88
+ File.open(@path, "r") do |file|
89
+ contents = file.read
90
+
91
+ contents.gsub!(meta_regex, "")
92
+
93
+ trailing_empty = 0
94
+ contents.lines.reverse.each do |line|
95
+ if line.strip == ""
96
+ trailing_empty += 1
97
+ else
98
+ break
99
+ end
100
+ end
101
+
102
+ # Leave two empty lines before metadata.
103
+ contents = contents.lines.slice(0, contents.lines.length - trailing_empty).join("")
104
+
105
+ contents += "\n\n"
106
+ contents += "<!--- created: #{@created} -->\n"
107
+ contents += "<!--- modified: #{@modified} -->\n"
108
+ contents += "<!--- tags: #{@tags.join(", ")} -->\n"
109
+
110
+ File.open(@path, "w") do |file|
111
+ file.write(contents)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,349 @@
1
+ require "ap"
2
+ require "colorize"
3
+ require "fileutils"
4
+ require "time"
5
+ require "cnote/note"
6
+
7
+ class Notes
8
+ def initialize(config)
9
+ @config = config
10
+ @notes = Dir[File.join(@config.note_path, "**", "*")].select do |file|
11
+ File.extname(file) == ".md"
12
+ end.map do |file|
13
+ Note.new(file)
14
+ end
15
+ @filtered = @notes
16
+ end
17
+
18
+ #/================================\#
19
+ # REPL type thing #
20
+ #\================================/#
21
+
22
+ def await_command(message = nil)
23
+ puts message if message
24
+ print "#{@config.cursor} ".magenta
25
+ input = STDIN.gets.chomp
26
+
27
+ # Strip and process
28
+ action, *params = input.strip.gsub(/\s{2,}/, " ").split(" ")
29
+ run_command(action || "help", params)
30
+ end
31
+
32
+ def run_command(action, params)
33
+ case action.downcase
34
+ when "new", "create", "n", "c"
35
+ create(params)
36
+ when "edit", "open", "e", "o"
37
+ open(params)
38
+ when "delete", "d", "rm"
39
+ delete(params)
40
+ when "peek", "p"
41
+ peek(params)
42
+ when "tag", "t"
43
+ tag(params)
44
+ when "untag", "ut"
45
+ untag(params)
46
+ when "search", "find", "s", "f"
47
+ search(params.join(" "))
48
+ when "list", "l", "ls"
49
+ list
50
+ when "help", "h"
51
+ help
52
+ when "quit", "exit", "close", "q"
53
+ exit
54
+ else
55
+ puts "Sorry, didn't quite get that..."
56
+ help
57
+ end
58
+
59
+ await_command # Drop back to REPL
60
+ end
61
+
62
+ #/================================\#
63
+ # The Commands #
64
+ #\================================/#
65
+
66
+ def search(term)
67
+ term = term.downcase # Search is case insensitive
68
+ matches = @notes
69
+
70
+ if term.include? "+t "
71
+ term, tags = term.split("+t ")
72
+ tags = tags.split(" ")
73
+ puts "\n Searching: '#{term.strip}' with tags: #{tags}"
74
+ matches = matches.select do |note|
75
+ has_all_tags(note, tags)
76
+ end
77
+ elsif term.include? "-t "
78
+ term, tags = term.split("-t ")
79
+ tags = tags.split(" ")
80
+ puts "\n Searching: '#{term.strip}' without tags: #{tags}"
81
+ matches = matches.select do |note|
82
+ has_none_tags(note, tags)
83
+ end
84
+ end
85
+
86
+ term.strip!
87
+
88
+ @filtered = matches.select do |note|
89
+ note.title.downcase.include?(term) || note.content.downcase.include?(term)
90
+ end
91
+
92
+ # TODO: Sort by most relevant
93
+ # TODO: Highlight keywords where found
94
+ len = @filtered.length
95
+
96
+ print_list("Found #{len} Match#{"es" if len != 1}", @filtered)
97
+ end
98
+
99
+ def create(params)
100
+ if params.first
101
+ dirname = File.dirname(params.first)
102
+ new_filename = File.basename(params.first, File.extname(params.first)) + ".md"
103
+ rel_path = ""
104
+ tags = []
105
+
106
+ if params.include? "+t"
107
+ tags = params.slice(params.index("+t") + 1, params.length)
108
+ puts "CREATING WITH TAGS: #{tags}"
109
+ end
110
+
111
+ if dirname != "."
112
+ rel_path = dirname
113
+ .gsub(@config.note_path, "")
114
+ .gsub(File.basename(params.first), "")
115
+ end
116
+
117
+ full_path = File.join(@config.note_path, rel_path, new_filename)
118
+
119
+ if File.exists?(full_path)
120
+ if confirm("#{"Whoa!".bold.red} That file already exists. Overwrite it?")
121
+ File.delete(full_path)
122
+ @notes.each do |note|
123
+ if note.path == full_path
124
+ @notes.delete(note)
125
+ puts "Removed!"
126
+ end
127
+ end
128
+ else
129
+ return
130
+ end
131
+ end
132
+
133
+ system "#{@config.editor} '#{full_path}'"
134
+
135
+ note = Note.new(full_path)
136
+ note.add_tags(tags) if tags.length > 0
137
+ note.created = Time.new
138
+ note.update
139
+
140
+ @notes << Note.new(full_path)
141
+
142
+ print_list("Created", [note])
143
+ @filtered = [note]
144
+ else
145
+ puts "Please enter a filename as the first parameter"
146
+ end
147
+ end
148
+
149
+ def open(params)
150
+ num = params.first.to_i
151
+ note = @filtered[num - 1]
152
+
153
+ if note
154
+ system "#{@config.editor} '#{note.path}'"
155
+ note.update
156
+ else
157
+ puts "Hey! There is no note #{num}! Nice try."
158
+ end
159
+ end
160
+
161
+ def delete(params)
162
+ num = params.first.to_i
163
+ note = @filtered[num - 1]
164
+
165
+ if note
166
+ if confirm("You're #{"sure".italic} you want to delete note #{num.to_s.bold.white} with title #{note.title.bold.white}?")
167
+ FileUtils.rm(note.path)
168
+ @notes.delete(note)
169
+ @filtered.delete(note)
170
+ puts "Deleted!"
171
+ else
172
+ puts "Whew! That was close."
173
+ end
174
+ else
175
+ puts "Looks like my job is done here, since note #{num} doesn't exist anyway!"
176
+ end
177
+ end
178
+
179
+ def peek(params)
180
+ note = @filtered[params.first.to_i - 1]
181
+ if note
182
+ puts
183
+ puts "-" * 40
184
+ puts note.title.bold.white
185
+ puts note.content.split("\n").slice(0, 10)
186
+ puts
187
+ puts "... (cont'd) ...".italic.gray
188
+ puts "-" * 40
189
+ puts
190
+ else
191
+ puts "Note doesn't exist!"
192
+ end
193
+ end
194
+
195
+ def tag(params)
196
+ notes = multi_note(params)
197
+
198
+ notes.each do |note|
199
+ tags = params.slice(1, params.length)
200
+ note.add_tags(tags)
201
+ end
202
+
203
+ print_list("Changed", notes)
204
+
205
+ @filtered = notes
206
+
207
+ puts "Added #{params.length - 1} tag#{"s" if params.length != 2} to #{notes.length} note#{"s" if notes.length != 1}."
208
+ end
209
+
210
+ def untag(params)
211
+ notes = multi_note(params)
212
+
213
+ notes.each do |note|
214
+ tags = params.slice(1, params.length)
215
+ note.remove_tags(tags)
216
+ end
217
+
218
+ print_list("Changed", notes)
219
+
220
+ @filtered = notes
221
+
222
+ puts "Removed #{params.length - 1} tag#{"s" if params.length != 2} from #{notes.length} note#{"s" if notes.length != 1}."
223
+ end
224
+
225
+ def help
226
+ puts
227
+ puts "Enter a command with the structure:"
228
+ puts " #{@config.cursor} action parameter(s)"
229
+ puts
230
+ puts "Actions:"
231
+ puts " - #{"new".bold.white} #{"filename".italic}"
232
+ puts " - #{"edit".bold.white} #{"note_number".italic}"
233
+ puts " - #{"delete".bold.white} #{"note_number".italic}"
234
+ puts " - #{"peek".bold.white} #{"note_number".italic}"
235
+ puts " - #{"tag".bold.white} #{"note_number".italic}"
236
+ puts " - #{"untag".bold.white} #{"note_number".italic}"
237
+ puts " - #{"search".bold.white} #{"search_term".italic}"
238
+ puts " - #{"list".bold.white}"
239
+ puts " - #{"exit".bold.white}"
240
+ puts " - #{"help".bold.white}"
241
+ puts
242
+ puts "Alternate actions:"
243
+ puts " Most actions also have aliases that do the same thing."
244
+ puts " These are listed for each command:"
245
+ puts " - new: create, c, n"
246
+ puts " - edit: e, open, o"
247
+ puts " - delete: d, rm"
248
+ puts " - peek: p"
249
+ puts " - tag: t"
250
+ puts " - untag: ut"
251
+ puts " - search: find, f, s"
252
+ puts " - list: l, ls"
253
+ puts " - exit: quit, q, close"
254
+ puts " - help: h"
255
+ puts
256
+ end
257
+
258
+ def list
259
+ @filtered = recently_edited_first(@notes)
260
+ print_list("All Notes", @filtered)
261
+ end
262
+
263
+ #/================================\#
264
+ # Utilities #
265
+ #\================================/#
266
+
267
+ private def print_list(title, notes)
268
+ path_length = @config.note_path.split("/").length
269
+ i = 0
270
+
271
+ puts
272
+ puts " #{title}".bold
273
+ puts " #{"-" * title.length}"
274
+ puts
275
+
276
+ notes.each do |note|
277
+ i += 1
278
+ puts "#{i}.".ljust(4) + note.title.bold
279
+ puts " #{note.path.gsub(@config.note_path, "")}".italic.light_magenta
280
+ if note.tags.length > 0
281
+ tags = note.tags.map { |tag| tag.yellow }
282
+ puts " tags: " + "[#{tags.join('] [')}]"
283
+ else
284
+ puts " <no tags>".gray
285
+ end
286
+ puts " modified: " + note.modified.strftime("%a, %b %e %Y, %l:%M%P").italic
287
+ puts " created: " + note.created.strftime("%a, %b %e %Y, %l:%M%P").italic
288
+ puts
289
+ end
290
+
291
+ puts " Listed #{i.to_s.bold} Notes"
292
+ puts
293
+ end
294
+
295
+ private def confirm(message = "Confirm")
296
+ print "#{message} [y/n]: "
297
+ case gets.chomp.strip.downcase
298
+ when "y", "yes", "yeah", "sure", "yep", "okay", "aye"
299
+ return true
300
+ when "n", "no", "nope", "nay"
301
+ return false
302
+ else
303
+ return confirm("Sorry, didn't quite get that...")
304
+ end
305
+ end
306
+
307
+ private def multi_note(params)
308
+ notes = []
309
+
310
+ params.first.split(",").each do |num|
311
+ note = @filtered[num.to_i - 1]
312
+ if note
313
+ notes << note
314
+ else
315
+ puts "Note #{num} doesn't exist!"
316
+ end
317
+ end
318
+
319
+ notes
320
+ end
321
+
322
+ private def recently_edited_first(notes)
323
+ notes.sort_by { |note| note.modified }.reverse
324
+ end
325
+
326
+ private def has_all_tags(note, tags)
327
+ has = true
328
+ note_tags = note.tags
329
+ tags.each do |tag|
330
+ if !note_tags.include? tag
331
+ has = false
332
+ break
333
+ end
334
+ end
335
+ has
336
+ end
337
+
338
+ private def has_none_tags(note, tags)
339
+ doesnt_have = true
340
+ note_tags = note.tags
341
+ tags.each do |tag|
342
+ if note_tags.include? tag
343
+ doesnt_have = false
344
+ break
345
+ end
346
+ end
347
+ doesnt_have
348
+ end
349
+ end
data/lib/cnote/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cnote
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
data/main.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'ap'
2
+ require 'yaml'
3
+ require 'fileutils'
4
+ require_relative 'classes/config'
5
+ require_relative 'classes/notes'
6
+
7
+ command = (ARGV[0] || '').strip.downcase
8
+ config = Config.new('~/.cnote.yaml')
9
+
10
+ def help
11
+ puts "Try one of these:"
12
+ puts " cnote list"
13
+ puts " cnote search [term]"
14
+ end
15
+
16
+ case command
17
+ when 'list'
18
+ notes = Notes.new(config)
19
+ notes.list
20
+ when 'search', 'find'
21
+ notes = Notes.new(config)
22
+ notes.search(ARGV.slice(1, ARGV.length).join(' '))
23
+ when 'help'
24
+ help
25
+ else
26
+ # Start REPL
27
+ notes = Notes.new(config)
28
+ notes.await_command
29
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cnote
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony McCoy
@@ -97,10 +97,19 @@ files:
97
97
  - Rakefile
98
98
  - bin/console
99
99
  - bin/setup
100
+ - classes/config.rb
101
+ - classes/note.rb
102
+ - classes/notes.rb
100
103
  - cnote.gemspec
101
104
  - exe/cnote
105
+ - gems.locked
106
+ - gems.rb
102
107
  - lib/cnote.rb
108
+ - lib/cnote/config.rb
109
+ - lib/cnote/note.rb
110
+ - lib/cnote/notes.rb
103
111
  - lib/cnote/version.rb
112
+ - main.rb
104
113
  homepage: https://www.tonymccoy.me/cnote
105
114
  licenses:
106
115
  - MIT