rbnotes 0.4.5 → 0.4.6

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.
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