rbnotes 0.4.5 → 0.4.6

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
  SHA256:
3
- metadata.gz: 840d2acc4c93d5b03d833b6a518c9740518e025aeb3bb2b6751864d5cf9e9d1c
4
- data.tar.gz: 759f8bdc29d64e32c73d7e02540603c3bdf84b6bdf3ac91b7a76bf8e3cf2005d
3
+ metadata.gz: e8c8badba84cecd738d81b16ec9539c6b8f51199433da1808bcf07fffd45635c
4
+ data.tar.gz: ab85b5e5d963b88014ffeb780c8da1beb2116e55789e13dfcd2fdea3183651e1
5
5
  SHA512:
6
- metadata.gz: b47f361e68f7e5275b981848035621fd31342a994d6bd5d90002f91417f4806ba364714b4ea86728f993e43e74868bf08508701531361c4e785830cc772a5b2e
7
- data.tar.gz: '09cea0ff486e4e2a8521d732296e64a520c7d076088dd63c05cc4a9c219a2be28e65e50234859bab0d2772a92fb466b1ffc580525fb3565e8d68f7f7f81f8eec'
6
+ metadata.gz: 6bcbd5ce75daf93183480fe1f00ab72d801b70c894bb9ba70ecc8f03cc7cdc3f2173c71dc0a7c6d119cfcbb9a0c63dff5b8c05a527c1df8dfbbe186509a4e41c
7
+ data.tar.gz: 72a593af5a18ce7482952df1ceb9d2eb6132d2072bb3adc70075b08118e0594d0f294738e74ddf0087adb2a06946f145cef4ff71b4425aeb855aeaf68e211ac9
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
  ## [Unreleased]
8
8
  Nothing to record here.
9
9
 
10
+ ## [0.4.6] - 2020-11-13
11
+ ### Added
12
+ - Add a new command `pick` to select a note with picker program. (#59)
13
+
10
14
  ## [0.4.5] - 2020-11-12
11
15
  ### Changed
12
16
  - Add a feature to accept multiple args for `list`. (#57)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbnotes (0.4.5)
4
+ rbnotes (0.4.6)
5
5
  textrepo (~> 0.5.4)
6
6
  unicode-display_width (~> 1.7)
7
7
 
@@ -4,4 +4,5 @@
4
4
  :repository_name: "notes"
5
5
  :repository_base: "~"
6
6
  :pager: "bat -l md"
7
+ :picker: "fzf"
7
8
  :editor: "/usr/local/bin/emacsclient"
@@ -4,4 +4,5 @@
4
4
  :repository_name: "notes"
5
5
  :repository_base: "tmp"
6
6
  :pager: "bat -l md"
7
+ :picker: "fzf"
7
8
  :editor: "/usr/local/bin/emacsclient"
@@ -8,4 +8,10 @@ module Rbnotes
8
8
  require_relative "rbnotes/conf"
9
9
  require_relative "rbnotes/utils"
10
10
  require_relative "rbnotes/commands"
11
+
12
+ class << self
13
+ def utils
14
+ Utils.instance
15
+ end
16
+ end
11
17
  end
@@ -27,7 +27,6 @@ module Rbnotes::Commands
27
27
  # If none of the above editor is available, the command fails.
28
28
 
29
29
  class Add < Command
30
- include ::Rbnotes::Utils
31
30
 
32
31
  def description # :nodoc:
33
32
  "Add a new note"
@@ -52,10 +51,10 @@ module Rbnotes::Commands
52
51
  stamp = @opts[:timestamp] || Textrepo::Timestamp.new(Time.now)
53
52
 
54
53
  candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
55
- editor = find_program(candidates)
54
+ editor = Rbnotes.utils.find_program(candidates)
56
55
  raise Rbnotes::NoEditorError, candidates if editor.nil?
57
56
 
58
- tmpfile = run_with_tmpfile(editor, stamp.to_s)
57
+ tmpfile = Rbnotes.utils.run_with_tmpfile(editor, stamp.to_s)
59
58
 
60
59
  unless FileTest.exist?(tmpfile)
61
60
  puts "Cancel adding, since nothing to store"
@@ -13,7 +13,7 @@ module Rbnotes::Commands
13
13
  end
14
14
 
15
15
  def execute(args, conf)
16
- stamp = Rbnotes::Utils.read_timestamp(args)
16
+ stamp = Rbnotes.utils.read_timestamp(args)
17
17
 
18
18
  repo = Textrepo.init(conf)
19
19
  begin
@@ -21,7 +21,7 @@ module Rbnotes::Commands
21
21
  # execute([a String as timestring], Rbnotes::Conf or Hash) -> nil
22
22
 
23
23
  def execute(args, conf)
24
- stamp = Rbnotes::Utils.read_timestamp(args)
24
+ stamp = Rbnotes.utils.read_timestamp(args)
25
25
 
26
26
  repo = Textrepo.init(conf)
27
27
  begin
@@ -1,7 +1,3 @@
1
- require "date"
2
- require "unicode/display_width"
3
- require "io/console/size"
4
-
5
1
  module Rbnotes::Commands
6
2
 
7
3
  ##
@@ -55,15 +51,11 @@ module Rbnotes::Commands
55
51
  # execute(Array, Rbnotes::Conf or Hash) -> nil
56
52
 
57
53
  def execute(args, conf)
58
- patterns = args.size > 0 ? convert_keyword(args) : [nil]
59
-
54
+ patterns = Rbnotes.utils.expand_keyword_in_args(args)
60
55
  @repo = Textrepo.init(conf)
61
56
  # newer stamp shoud be above
62
- stamps = patterns.map { |pat|
63
- @repo.entries(pat)
64
- }.flatten.sort{|a, b| b <=> a}.uniq
65
- stamps.each { |timestamp|
66
- puts make_headline(timestamp)
57
+ Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
58
+ puts Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
67
59
  }
68
60
  end
69
61
 
@@ -95,97 +87,5 @@ KEYWORD:
95
87
  HELP
96
88
  end
97
89
 
98
- # :stopdoc:
99
-
100
- private
101
- TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
102
-
103
- ##
104
- # Makes a headline with the timestamp and subject of the notes, it
105
- # looks like as follows:
106
- #
107
- # |<------------------ console column size ------------------->|
108
- # +-- timestamp ---+ +- subject (the 1st line of each note) -+
109
- # | | | |
110
- # 20101010001000_123: I love Macintosh. [EOL]
111
- # 20100909090909_999: This is very very long long loooong subje[EOL]
112
- # ++
113
- # ^--- delimiter (2 characters)
114
- #
115
- # The subject part will truncate when it is long.
116
-
117
- def make_headline(timestamp)
118
- _, column = IO.console_size
119
- delimiter = ": "
120
- subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
121
-
122
- subject = remove_heading_markup(@repo.read(timestamp)[0])
123
-
124
- ts_part = "#{timestamp.to_s} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
125
- sj_part = truncate_str(subject, subject_width)
126
-
127
- ts_part + delimiter + sj_part
128
- end
129
-
130
- def truncate_str(str, size)
131
- count = 0
132
- result = ""
133
- str.each_char { |c|
134
- count += Unicode::DisplayWidth.of(c)
135
- break if count > size
136
- result << c
137
- }
138
- result
139
- end
140
-
141
- def remove_heading_markup(str)
142
- str.sub(/^#+ +/, '')
143
- end
144
-
145
- def convert_keyword(args)
146
- patterns = []
147
- while args.size > 0
148
- arg = args.shift
149
- case arg.to_s
150
- when "today", "to"
151
- patterns << Textrepo::Timestamp.new(Time.now).to_s[0..7]
152
- when "yesterday", "ye"
153
- t = Time.now
154
- patterns << Date.new(t.year, t.mon, t.day).prev_day.strftime("%Y%m%d")
155
- when "this_week", "tw"
156
- patterns.concat(dates_in_this_week)
157
- when "last_week", "lw"
158
- patterns.concat(dates_in_last_week)
159
- else
160
- patterns << arg
161
- end
162
- end
163
- patterns.sort!.uniq
164
- end
165
-
166
- # week day for Monday start calendar
167
- def wday(time)
168
- (time.wday - 1) % 7
169
- end
170
-
171
- def dates_in_this_week
172
- to = Time.now
173
- start = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
174
- dates_in_week(start)
175
- end
176
-
177
- def dates_in_last_week
178
- to = Time.now
179
- start_of_this_week = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
180
- dates_in_week(start_of_this_week.prev_day(7))
181
- end
182
-
183
- def dates_in_week(start_date)
184
- dates = [start_date]
185
- 1.upto(6) { |i| dates << start_date.next_day(i) }
186
- dates.map { |d| d.strftime("%Y%m%d") }
187
- end
188
-
189
- # :startdoc:
190
90
  end
191
91
  end
@@ -0,0 +1,47 @@
1
+ module Rbnotes::Commands
2
+
3
+ ##
4
+ # Picks a timestamp with a picker program, like `fzf`.
5
+
6
+ class Pick < Command
7
+
8
+ def description # :nodoc:
9
+ "Pick a timestamp with a picker program"
10
+ end
11
+
12
+ def execute(args, conf)
13
+ patterns = Rbnotes.utils.expand_keyword_in_args(args)
14
+ @repo = Textrepo.init(conf)
15
+
16
+ list = []
17
+ Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
18
+ list << Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
19
+ }
20
+
21
+ picker = conf[:picker]
22
+ unless picker.nil?
23
+ require 'open3'
24
+ result = Open3.pipeline_rw(picker) { |stdin, stdout, _|
25
+ stdin.puts list
26
+ stdin.close
27
+ stdout.read
28
+ }
29
+ puts result
30
+ else
31
+ puts list
32
+ end
33
+ end
34
+
35
+ def help # :nodoc:
36
+ puts <<HELP
37
+ usage:
38
+ #{Rbnotes::NAME} pick
39
+
40
+ Pick a timestamp with a picker program, like `fzf`. This command
41
+ refers the configuration setting of ":picker". If no picker program
42
+ is specified, it will behave as same as "list" command.
43
+
44
+ HELP
45
+ end
46
+ end
47
+ end
@@ -19,7 +19,7 @@ module Rbnotes::Commands
19
19
  end
20
20
 
21
21
  def execute(args, conf)
22
- stamp = Rbnotes::Utils.read_timestamp(args)
22
+ stamp = Rbnotes.utils.read_timestamp(args)
23
23
 
24
24
  repo = Textrepo.init(conf)
25
25
  content = repo.read(stamp)
@@ -18,7 +18,6 @@ module Rbnotes::Commands
18
18
  # none of editors is available, the execution fails.
19
19
 
20
20
  class Update < Command
21
- include ::Rbnotes::Utils
22
21
 
23
22
  def description # :nodoc:
24
23
  "Update the content of a note"
@@ -44,8 +43,8 @@ module Rbnotes::Commands
44
43
  end
45
44
  end
46
45
 
47
- target_stamp = Rbnotes::Utils.read_timestamp(args)
48
- editor = find_editor(conf[:editor])
46
+ target_stamp = Rbnotes.utils.read_timestamp(args)
47
+ editor = Rbnotes.utils.find_editor(conf[:editor])
49
48
  repo = Textrepo.init(conf)
50
49
 
51
50
  text = nil
@@ -55,7 +54,7 @@ module Rbnotes::Commands
55
54
  raise Rbnotes::MissingTimestampError, target_stamp
56
55
  end
57
56
 
58
- tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
57
+ tmpfile = Rbnotes.utils.run_with_tmpfile(editor, target_stamp.to_s, text)
59
58
  text = File.readlines(tmpfile, :chomp => true)
60
59
 
61
60
  unless text.empty?
@@ -11,6 +11,7 @@ module Rbnotes
11
11
  MISSING_TIMESTAMP = "missing timestamp: %s"
12
12
  NO_EDITOR = "No editor is available: %s"
13
13
  PROGRAM_ABORT = "External program was aborted: %s"
14
+ UNKNOWN_KEYWORD = "Unknown keyword: %s"
14
15
  end
15
16
 
16
17
  # :startdoc:
@@ -53,4 +54,14 @@ module Rbnotes
53
54
  super(ErrMsg::PROGRAM_ABORT % cmdline.join(" "))
54
55
  end
55
56
  end
57
+
58
+ ##
59
+ # An eeror raised when an unknown keyword was specified as a
60
+ # timestamp string pattern.
61
+
62
+ class UnknownKeywordError < Error
63
+ def initialize(keyword)
64
+ super(ErrMsg::UNKNOWN_KEYWORD % keyword)
65
+ end
66
+ end
56
67
  end
@@ -1,12 +1,18 @@
1
+ require "singleton"
1
2
  require "pathname"
2
3
  require "tmpdir"
4
+ require "date"
5
+ require "io/console/size"
6
+
7
+ require "unicode/display_width"
3
8
 
4
9
  module Rbnotes
5
10
  ##
6
11
  # Defines several utility methods those are intended to be used in
7
12
  # Rbnotes classes.
8
13
  #
9
- module Utils
14
+ class Utils
15
+ include Singleton
10
16
 
11
17
  ##
12
18
  # Finds a external editor program which is specified with the
@@ -26,7 +32,6 @@ module Rbnotes
26
32
  def find_editor(preferred_editor)
27
33
  find_program([preferred_editor, ENV["EDITOR"], "nano", "vi"].compact)
28
34
  end
29
- module_function :find_editor
30
35
 
31
36
  ##
32
37
  # Finds a executable program in given names. When the executable
@@ -60,7 +65,6 @@ module Rbnotes
60
65
  }
61
66
  nil
62
67
  end
63
- module_function :find_program
64
68
 
65
69
  ##
66
70
  # Executes the program with passing the given filename as argument.
@@ -84,7 +88,6 @@ module Rbnotes
84
88
  raise ProgramAbortError, [prog, tmpfile] unless rc
85
89
  tmpfile
86
90
  end
87
- module_function :run_with_tmpfile
88
91
 
89
92
  ##
90
93
  # Generates a Textrepo::Timestamp object from a String which comes
@@ -98,7 +101,6 @@ module Rbnotes
98
101
  str = args.shift || read_arg($stdin)
99
102
  Textrepo::Timestamp.parse_s(str)
100
103
  end
101
- module_function :read_timestamp
102
104
 
103
105
  ##
104
106
  # Reads an argument from the IO object. Typically, it is intended
@@ -119,7 +121,110 @@ module Rbnotes
119
121
  nil
120
122
  end
121
123
  end
122
- module_function :read_arg
124
+
125
+ ##
126
+ # Parses the given arguments and expand keywords if found. Each
127
+ # of the arguments is assumed to represent a timestamp pattern (or
128
+ # a keyword to be expand into several timestamp pattern). Returns
129
+ # an Array of timestamp partterns (each pattern is a String
130
+ # object).
131
+ #
132
+ # A timestamp pattern looks like:
133
+ #
134
+ # (a) full qualified timestamp (with suffix): "20201030160200"
135
+ # (b) year and date part: "20201030"
136
+ # (c) year and month part: "202010"
137
+ # (d) year part only: "2020"
138
+ # (e) date part only: "1030"
139
+ #
140
+ # KEYWORD:
141
+ #
142
+ # - "today" (or "to")
143
+ # - "yeasterday" (or "ye")
144
+ # - "this_week" (or "tw")
145
+ # - "last_week" (or "lw")
146
+ #
147
+ # :call-seq:
148
+ # expand_keyword_in_args(Array of Strings) -> Array of Strings
149
+
150
+ def expand_keyword_in_args(args)
151
+ return [nil] if args.empty?
152
+
153
+ patterns = []
154
+ while args.size > 0
155
+ arg = args.shift
156
+ if ["today", "to", "yesterday", "ye",
157
+ "this_week", "tw", "last_week", "lw"].include?(arg)
158
+ patterns.concat(Rbnotes.utils.expand_keyword(arg))
159
+ else
160
+ patterns << arg
161
+ end
162
+ end
163
+ patterns.sort.uniq
164
+ end
165
+
166
+ ##
167
+ # Expands a keyword to timestamp strings.
168
+ #
169
+ # :call-seq:
170
+ # expand_keyword(keyword as String) -> Array of timestamp Strings
171
+
172
+ def expand_keyword(keyword)
173
+ patterns = []
174
+ case keyword
175
+ when "today", "to"
176
+ patterns << timestamp_pattern(date_of_today)
177
+ when "yesterday", "ye"
178
+ patterns << timestamp_pattern(date_of_yesterday)
179
+ when "this_week", "tw"
180
+ patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
181
+ when "last_week", "lw"
182
+ patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
183
+ else
184
+ raise UnknownKeywordError, keyword
185
+ end
186
+ patterns
187
+ end
188
+
189
+ ##
190
+ # Makes a headline with the timestamp and subject of the notes, it
191
+ # looks like as follows:
192
+ #
193
+ # |<------------------ console column size ------------------->|
194
+ # +-- timestamp ---+ +- subject (the 1st line of each note) -+
195
+ # | | | |
196
+ # 20101010001000_123: I love Macintosh. [EOL]
197
+ # 20100909090909_999: This is very very long long loooong subje[EOL]
198
+ # ++
199
+ # ^--- delimiter (2 characters)
200
+ #
201
+ # The subject part will truncate when it is long.
202
+
203
+ def make_headline(timestamp, text)
204
+ _, column = IO.console_size
205
+ delimiter = ": "
206
+ subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
207
+
208
+ subject = remove_heading_markup(text[0])
209
+
210
+ ts_part = "#{timestamp.to_s} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
211
+ sj_part = truncate_str(subject, subject_width)
212
+
213
+ ts_part + delimiter + sj_part
214
+ end
215
+
216
+ ##
217
+ # Finds all notes those timestamps match to given patterns in the
218
+ # given repository. Returns an Array contains Timestamp objects.
219
+ #
220
+ # :call-seq:
221
+ # find_notes(Array of timestamp patterns, Textrepo::Repository)
222
+
223
+ def find_notes(timestamp_patterns, repo)
224
+ timestamp_patterns.map { |pat|
225
+ repo.entries(pat)
226
+ }.flatten.sort{ |a, b| b <=> a }.uniq
227
+ end
123
228
 
124
229
  # :stopdoc:
125
230
 
@@ -132,11 +237,72 @@ module Rbnotes
132
237
  }
133
238
  found.compact[0]
134
239
  end
135
- module_function :search_in_path
136
240
 
137
241
  def add_extension(basename)
138
242
  "#{basename}.md"
139
243
  end
140
- module_function :add_extension
244
+
245
+ def timestamp_pattern(date)
246
+ date.strftime("%Y%m%d")
247
+ end
248
+
249
+ def date_of_today
250
+ date(Time.now)
251
+ end
252
+
253
+ def date_of_yesterday
254
+ date(Time.now).prev_day
255
+ end
256
+
257
+ def date(time)
258
+ Date.new(time.year, time.mon, time.day)
259
+ end
260
+
261
+ def dates_in_this_week
262
+ dates_in_week(start_date_in_this_week)
263
+ end
264
+
265
+ def dates_in_last_week
266
+ dates_in_week(start_date_in_last_week)
267
+ end
268
+
269
+ def start_date_in_this_week
270
+ today = Time.now
271
+ Date.new(today.year, today.mon, today.day).prev_day(wday(today))
272
+ end
273
+
274
+ def start_date_in_last_week
275
+ start_date_in_this_week.prev_day(7)
276
+ end
277
+
278
+ def wday(time)
279
+ (time.wday - 1) % 7
280
+ end
281
+
282
+ def dates_in_week(start_date)
283
+ dates = [start_date]
284
+ 1.upto(6) { |i| dates << start_date.next_day(i) }
285
+ dates
286
+ end
287
+
288
+ TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
289
+
290
+ def truncate_str(str, size)
291
+ count = 0
292
+ result = ""
293
+ str.each_char { |c|
294
+ count += Unicode::DisplayWidth.of(c)
295
+ break if count > size
296
+ result << c
297
+ }
298
+ result
299
+ end
300
+
301
+ def remove_heading_markup(str)
302
+ str.sub(/^#+ +/, '')
303
+ end
304
+
305
+ # :startdoc:
306
+
141
307
  end
142
308
  end
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.4.5"
3
- RELEASE = "2020-11-12"
2
+ VERSION = "0.4.6"
3
+ RELEASE = "2020-11-13"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbnotes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - mnbi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-12 00:00:00.000000000 Z
11
+ date: 2020-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: textrepo
@@ -68,6 +68,7 @@ files:
68
68
  - lib/rbnotes/commands/help.rb
69
69
  - lib/rbnotes/commands/import.rb
70
70
  - lib/rbnotes/commands/list.rb
71
+ - lib/rbnotes/commands/pick.rb
71
72
  - lib/rbnotes/commands/search.rb
72
73
  - lib/rbnotes/commands/show.rb
73
74
  - lib/rbnotes/commands/update.rb