rbnotes 0.4.11 → 0.4.16

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.
@@ -9,19 +9,35 @@ module Rbnotes::Commands
9
9
  "Pick a timestamp with a picker program"
10
10
  end
11
11
 
12
+ DEFAULT_BEHAVIOR = "today" # :nodoc:
13
+
12
14
  def execute(args, conf)
13
- patterns = Rbnotes.utils.expand_keyword_in_args(args)
15
+ @opts = {}
16
+ parse_opts(args)
17
+
18
+ if args.empty?
19
+ default_behavior = conf[:list_default] || DEFAULT_BEHAVIOR
20
+ args << default_behavior
21
+ end
22
+
23
+ utils = Rbnotes.utils
24
+ patterns = utils.read_timestamp_patterns(args, enum_week: @opts[:enum_week])
25
+
14
26
  @repo = Textrepo.init(conf)
15
27
 
16
28
  list = []
17
- Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
18
- list << Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
29
+ utils.find_notes(patterns, @repo).each { |timestamp|
30
+ list << utils.make_headline(timestamp, @repo.read(timestamp))
19
31
  }
20
32
 
21
33
  picker = conf[:picker]
22
34
  unless picker.nil?
35
+ picker_opts = conf[:picker_option]
36
+ cmds = [picker]
37
+ cmds.concat(picker_opts.split) unless picker_opts.nil?
38
+
23
39
  require 'open3'
24
- result = Open3.pipeline_rw(picker) { |stdin, stdout, _|
40
+ result = Open3.pipeline_rw(cmds) { |stdin, stdout, _|
25
41
  stdin.puts list
26
42
  stdin.close
27
43
  stdout.read
@@ -43,5 +59,25 @@ is specified, it will behave as same as "list" command.
43
59
 
44
60
  HELP
45
61
  end
62
+
63
+ # :stopdoc:
64
+
65
+ private
66
+
67
+ def parse_opts(args)
68
+ while args.size > 0
69
+ arg = args.shift
70
+ case arg
71
+ when "-w", "--week"
72
+ @opts[:enum_week] = true
73
+ else
74
+ args.unshift(arg)
75
+ break
76
+ end
77
+ end
78
+ end
79
+
80
+ # :startdoc:
81
+
46
82
  end
47
83
  end
@@ -5,13 +5,17 @@ module Rbnotes::Commands
5
5
  # argument must be a string which can be converted into
6
6
  # Textrepo::Timestamp object.
7
7
  #
8
+ # Accepts an option with `-n NUMBER` (or `--num-of-lines`), to show
9
+ # the first NUMBER lines of the content of each note.
10
+ #
8
11
  # A string for Textrepo::Timestamp must be:
9
12
  #
10
13
  # "20201106112600" : year, date, time and sec
11
14
  # "20201106112600_012" : with suffix
12
15
  #
13
16
  # If no argument is passed, reads the standard input for arguments.
14
-
17
+ # If a specified timestamp does not exist in the repository as a key,
18
+ # Rbnotes::MissingTimestampError will occur.
15
19
  class Show < Command
16
20
 
17
21
  def description # :nodoc:
@@ -19,10 +23,26 @@ module Rbnotes::Commands
19
23
  end
20
24
 
21
25
  def execute(args, conf)
26
+ @opts = {}
27
+ parse_opts(args)
28
+
22
29
  stamps = Rbnotes.utils.read_multiple_timestamps(args)
23
30
  repo = Textrepo.init(conf)
24
31
 
25
- content = stamps.map { |stamp| [stamp, repo.read(stamp)] }.to_h
32
+ content = stamps.map { |stamp|
33
+ begin
34
+ text = repo.read(stamp)
35
+ rescue Textrepo::MissingTimestampError => _
36
+ raise Rbnotes::MissingTimestampError, stamp
37
+ end
38
+
39
+ lines = text.size
40
+ if @opts[:num_of_lines].to_i > 0
41
+ lines = [@opts[:num_of_lines], lines].min
42
+ end
43
+
44
+ [stamp, text[0, lines]]
45
+ }.to_h
26
46
 
27
47
  pager = conf[:pager]
28
48
  unless pager.nil?
@@ -35,11 +55,14 @@ module Rbnotes::Commands
35
55
  def help # :nodoc:
36
56
  puts <<HELP
37
57
  usage:
38
- #{Rbnotes::NAME} show [TIMESTAMP...]
58
+ #{Rbnotes::NAME} show [(-n|--num-of-lines) NUMBER] [TIMESTAMP...]
39
59
 
40
60
  Show the content of given notes. TIMESTAMP must be a fully qualified
41
61
  one, such "20201016165130" or "20201016165130_012" if it has a suffix.
42
62
 
63
+ Accept an option with `-n NUMBER` (or `--num-of-lines`), to show the
64
+ first NUMBER lines of the content of each note.
65
+
43
66
  The command try to read its argument from the standard input when no
44
67
  argument was passed in the command line.
45
68
  HELP
@@ -49,6 +72,25 @@ HELP
49
72
 
50
73
  private
51
74
 
75
+ def parse_opts(args)
76
+ while args.size > 0
77
+ arg = args.shift
78
+ case arg
79
+ when "-n", "--num-of-lines"
80
+ num_of_lines = args.shift
81
+ raise ArgumentError, "missing number: %s" % args.unshift(arg) if num_of_lines.nil?
82
+
83
+ num_of_lines = num_of_lines.to_i
84
+ raise ArgumentError, "illegal number (must be greater than 0): %d" % num_of_lines unless num_of_lines > 0
85
+
86
+ @opts[:num_of_lines] = num_of_lines
87
+ else
88
+ args.unshift(arg)
89
+ break
90
+ end
91
+ end
92
+ end
93
+
52
94
  def puts_with_pager(pager, output)
53
95
  require "open3"
54
96
  Open3.pipeline_w(pager) { |stdin|
@@ -9,20 +9,14 @@ module Rbnotes::Commands
9
9
  end
10
10
 
11
11
  def execute(args, conf)
12
+ @opts = {}
13
+ parse_opts(args)
14
+
12
15
  report = :total
13
- while args.size > 0
14
- arg = args.shift
15
- case arg
16
- when "-y", "--yearly"
17
- report = :yearly
18
- break
19
- when "-m", "--monthly"
20
- report = :monthly
21
- break
22
- else
23
- args.unshift(arg)
24
- raise ArgumentError, "invalid option or argument: %s" % args.join(" ")
25
- end
16
+ if @opts[:yearly]
17
+ report = :yearly
18
+ elsif @opts[:monthly]
19
+ report = :monthly
26
20
  end
27
21
 
28
22
  stats = Rbnotes::Statistics.new(conf)
@@ -51,5 +45,30 @@ In the version #{Rbnotes::VERSION}, only number of notes is supported.
51
45
  HELP
52
46
  end
53
47
 
48
+ # :stopdoc:
49
+
50
+ private
51
+
52
+ def parse_opts(args)
53
+ while args.size > 0
54
+ arg = args.shift
55
+ case arg
56
+ when "-y", "--yearly"
57
+ @opts[:yearly] = true
58
+ @opts[:monthly] = false
59
+ break
60
+ when "-m", "--monthly"
61
+ @opts[:yearly] = false
62
+ @opts[:monthly] = true
63
+ break
64
+ else
65
+ args.unshift(arg)
66
+ raise ArgumentError, "invalid option or argument: %s" % args.join(" ")
67
+ end
68
+ end
69
+ end
70
+
71
+ # :startdoc:
72
+
54
73
  end
55
74
  end
@@ -32,16 +32,7 @@ module Rbnotes::Commands
32
32
 
33
33
  def execute(args, conf)
34
34
  @opts = {}
35
- while args.size > 0
36
- arg = args.shift
37
- case arg
38
- when "-k", "--keep"
39
- @opts[:keep_timestamp] = true
40
- else
41
- args.unshift(arg)
42
- break
43
- end
44
- end
35
+ parse_opts(args)
45
36
 
46
37
  target_stamp = Rbnotes.utils.read_timestamp(args)
47
38
  editor = Rbnotes.utils.find_editor(conf[:editor])
@@ -100,5 +91,25 @@ editor program will be searched as same as add command. If none of
100
91
  editors is available, the execution fails.
101
92
  HELP
102
93
  end
94
+
95
+ # :stopdoc:
96
+
97
+ private
98
+
99
+ def parse_opts(args)
100
+ while args.size > 0
101
+ arg = args.shift
102
+ case arg
103
+ when "-k", "--keep"
104
+ @opts[:keep_timestamp] = true
105
+ else
106
+ args.unshift(arg)
107
+ break
108
+ end
109
+ end
110
+ end
111
+
112
+ # :startdoc:
113
+
103
114
  end
104
115
  end
data/lib/rbnotes/conf.rb CHANGED
@@ -31,19 +31,26 @@ module Rbnotes
31
31
 
32
32
  DIRNAME_COMMON_CONF = ".config"
33
33
 
34
- def initialize(conf_path = nil) # :nodoc:
35
- @conf_path = conf_path
34
+ def initialize(path = nil) # :nodoc:
36
35
  @conf = {}
37
36
 
38
- if use_default_values?
39
- @conf.merge!(DEFAULT_VALUES)
37
+ unless path.nil?
38
+ abspath = File.expand_path(path)
39
+ raise NoConfFileError, path unless FileTest.exist?(abspath)
40
+ @conf[:path] = abspath
40
41
  else
41
- @conf_path ||= default_conf_file
42
- raise NoConfFileError, @conf_path unless File.exist?(@conf_path)
43
-
44
- yaml_str = File.open(@conf_path, "r") { |f| f.read }
45
- @conf = YAML.load(yaml_str)
42
+ @conf[:path] = default_conf_path
46
43
  end
44
+
45
+ values =
46
+ if FileTest.exist?(@conf[:path])
47
+ yaml_str = File.open(@conf[:path], "r") { |f| f.read }
48
+ YAML.load(yaml_str)
49
+ else
50
+ DEFAULT_VALUES
51
+ end
52
+ @conf.merge!(values)
53
+ @conf[:config_home] = config_home
47
54
  end
48
55
 
49
56
  def_delegators(:@conf,
@@ -91,7 +98,7 @@ module Rbnotes
91
98
  :test => "_test",
92
99
  }
93
100
 
94
- def base_path
101
+ def config_home
95
102
  path = nil
96
103
  xdg, user = ["XDG_CONFIG_HOME", "HOME"].map{|n| ENV[n]}
97
104
  if xdg
@@ -99,15 +106,11 @@ module Rbnotes
99
106
  else
100
107
  path = File.join(user, DIRNAME_COMMON_CONF, DIRNAME_RBNOTES)
101
108
  end
102
- return path
103
- end
104
-
105
- def default_conf_file
106
- File.join(base_path, FILENAME_CONF)
109
+ path
107
110
  end
108
111
 
109
- def use_default_values?
110
- @conf_path.nil? && !File.exist?(default_conf_file)
112
+ def default_conf_path
113
+ File.join(config_home, FILENAME_CONF)
111
114
  end
112
115
 
113
116
  # :startdoc:
data/lib/rbnotes/error.rb CHANGED
@@ -7,13 +7,15 @@ module Rbnotes
7
7
  # :stopdoc:
8
8
 
9
9
  module ErrMsg
10
- MISSING_ARGUMENT = "Missing argument: %s"
11
- MISSING_TIMESTAMP = "Missing timestamp: %s"
12
- NO_EDITOR = "No editor is available: %s"
13
- PROGRAM_ABORT = "External program was aborted: %s"
14
- UNKNOWN_KEYWORD = "Unknown keyword: %s"
15
- INVALID_TIMESTAMP_PATTERN = "Invalid timestamp pattern: %s"
16
- NO_CONF_FILE = "No configuration file: %s"
10
+ MISSING_ARGUMENT = "missing argument: %s"
11
+ MISSING_TIMESTAMP = "missing timestamp: %s"
12
+ NO_EDITOR = "no editor is available: %s"
13
+ PROGRAM_ABORT = "external program was aborted: %s"
14
+ UNKNOWN_KEYWORD = "unknown keyword: %s"
15
+ INVALID_TIMESTAMP_PATTERN = "invalid timestamp pattern: %s"
16
+ NO_CONF_FILE = "no configuration file: %s"
17
+ NO_TEMPLATE_FILE = "no template file: %s"
18
+ INVALID_TIMESTAMP_PATTERN_AS_DATE = "invalid timestamp pattern as date: %s"
17
19
  end
18
20
 
19
21
  # :startdoc:
@@ -86,4 +88,32 @@ module Rbnotes
86
88
  end
87
89
  end
88
90
 
91
+ ##
92
+ # An error raised when no arguments is spcified.
93
+
94
+ class NoArgumentError < Error
95
+ def initialize
96
+ super
97
+ end
98
+ end
99
+
100
+ ##
101
+ # An error raised when the specified template files does not exist.
102
+ #
103
+ class NoTemplateFileError < Error
104
+ def initialize(filepath)
105
+ super(ErrMsg::NO_TEMPLATE_FILE % filepath)
106
+ end
107
+ end
108
+
109
+ ##
110
+ # An error raised when the specified pattern cannot be converted
111
+ # into a date.
112
+ #
113
+ class InvalidTimestampPatternAsDateError < Error
114
+ def initialize(pattern)
115
+ super(ErrMsg::INVALID_TIMESTAMP_PATTERN_AS_DATE % pattern)
116
+ end
117
+ end
118
+
89
119
  end
data/lib/rbnotes/utils.rb CHANGED
@@ -7,6 +7,20 @@ require "io/console/size"
7
7
  require "unicode/display_width"
8
8
 
9
9
  module Rbnotes
10
+
11
+ class << self
12
+
13
+ ##
14
+ # Retrieves the singleton instance of Rbnotes::Utils class.
15
+ # Typical usage is as follows:
16
+ #
17
+ # Rbnotes.utils.find_editor("emacsclient")
18
+ #
19
+ def utils
20
+ Utils.instance
21
+ end
22
+ end
23
+
10
24
  ##
11
25
  # Defines several utility methods those are intended to be used in
12
26
  # Rbnotes classes.
@@ -89,6 +103,19 @@ module Rbnotes
89
103
  tmpfile
90
104
  end
91
105
 
106
+ # Acceptable delimiters to separate a timestamp string for human
107
+ # being to read and input easily.
108
+ #
109
+ # Here is some examples:
110
+ #
111
+ # - "2021-04-15 15:34:56" -> "20210415153456" (a timestamp string)
112
+ # - "2020-04-15_15:34:56" -> (same as above)
113
+ # - "2020-04-15-15-34-56" -> (same as above)
114
+ # - "2020 04 15 15 34 56" -> (same as above)
115
+ # - "2020-04-15" -> "20200415" (a timestamp pattern)
116
+
117
+ TIMESTAMP_DELIMITERS = /[-:_\s]/
118
+
92
119
  ##
93
120
  # Generates a Textrepo::Timestamp object from a String which comes
94
121
  # from the command line arguments. When no argument is given,
@@ -99,6 +126,9 @@ module Rbnotes
99
126
 
100
127
  def read_timestamp(args)
101
128
  str = args.shift || read_arg($stdin)
129
+ raise NoArgumentError if str.nil?
130
+
131
+ str = remove_delimiters_from_timestamp_string(str)
102
132
  Textrepo::Timestamp.parse_s(str)
103
133
  end
104
134
 
@@ -107,12 +137,91 @@ module Rbnotes
107
137
  # line arguments. When no argument is given, try to read from
108
138
  # STDIN.
109
139
  #
140
+ # When multiple strings those point the identical time are
141
+ # included the arguments (passed or read form STDIN), the
142
+ # redundant strings will be removed.
143
+ #
144
+ # The order of the arguments will be preserved into the return
145
+ # value, even if the redundant strings were removed.
146
+ #
110
147
  # :call-seq:
111
148
  # read_multiple_timestamps(args) -> [String]
112
149
 
113
150
  def read_multiple_timestamps(args)
114
151
  strings = args.size < 1 ? read_multiple_args($stdin) : args
115
- strings.map { |str| Textrepo::Timestamp.parse_s(str) }
152
+ raise NoArgumentError if (strings.nil? || strings.empty?)
153
+ strings.uniq.map { |str|
154
+ str = remove_delimiters_from_timestamp_string(str)
155
+ Textrepo::Timestamp.parse_s(str)
156
+ }
157
+ end
158
+
159
+ ##
160
+ # Reads timestamp patterns in an array of arguments. It supports
161
+ # keywords expansion and enumeration of week. The function is
162
+ # intended to be used from Commands::List#execute and
163
+ # Commands::Pick#execute.
164
+ #
165
+ def read_timestamp_patterns(args, enum_week: false)
166
+ patterns = nil
167
+ if enum_week
168
+ patterns = []
169
+ while args.size > 0
170
+ arg = args.shift
171
+ begin
172
+ patterns.concat(timestamp_patterns_in_week(arg.dup))
173
+ rescue InvalidTimestampPatternAsDateError => _e
174
+ raise InvalidTimestampPatternAsDateError, args.unshift(arg)
175
+ end
176
+ end
177
+ else
178
+ patterns = expand_keyword_in_args(args)
179
+ end
180
+ patterns
181
+ end
182
+
183
+ ##
184
+ # Enumerates all timestamp patterns in a week which contains a
185
+ # given timestamp as a day of the week.
186
+ #
187
+ # The argument must be one of the followings:
188
+ # - "yyyymodd" (eg. "20201220")
189
+ # - "yymoddhhmiss" (eg. "20201220120048")
190
+ # - "yymoddhhmiss_sfx" (eg. "20201220120048_012")
191
+ # - "modd" (eg. "1220") (assums in the current year)
192
+ # - nil (assumes today)
193
+ #
194
+ # :call-seq:
195
+ # timestamp_patterns_in_week(String) -> [Array of Strings]
196
+ #
197
+ def timestamp_patterns_in_week(arg)
198
+ date_str = nil
199
+
200
+ if arg
201
+ date_str = remove_delimiters_from_timestamp_string(arg)
202
+ else
203
+ date_str = Textrepo::Timestamp.now[0, 8]
204
+ end
205
+
206
+ case date_str.size
207
+ when "yyyymodd".size
208
+ # nothing to do
209
+ when "yyyymoddhhmiss".size, "yyyymoddhhmiss_sfx".size
210
+ date_str = date_str[0, 8]
211
+ when "modd".size
212
+ this_year = Time.now.year.to_s
213
+ date_str = "#{this_year}#{date_str}"
214
+ else
215
+ raise InvalidTimestampPatternAsDateError, arg
216
+ end
217
+
218
+ begin
219
+ date = Date.parse(date_str)
220
+ rescue Date::Error => _e
221
+ raise InvalidTimestampPatternAsDateError, arg
222
+ end
223
+
224
+ dates_in_week(date).map { |date| timestamp_pattern(date) }
116
225
  end
117
226
 
118
227
  ##
@@ -136,72 +245,54 @@ module Rbnotes
136
245
  # - "yeasterday" (or "ye")
137
246
  # - "this_week" (or "tw")
138
247
  # - "last_week" (or "lw")
248
+ # - "this_month" (or "tm")
249
+ # - "last_month" (or "lm")
250
+ # - "all"
139
251
  #
140
252
  # :call-seq:
141
253
  # expand_keyword_in_args(Array of Strings) -> Array of Strings
142
-
254
+ #
143
255
  def expand_keyword_in_args(args)
144
- return [nil] if args.empty?
145
-
146
256
  patterns = []
147
257
  while args.size > 0
148
258
  arg = args.shift
149
- if ["today", "to", "yesterday", "ye",
150
- "this_week", "tw", "last_week", "lw"].include?(arg)
151
- patterns.concat(Rbnotes.utils.expand_keyword(arg))
259
+ if arg == "all"
260
+ return [nil]
261
+ elsif KEYWORDS.include?(arg)
262
+ patterns.concat(expand_keyword(arg))
152
263
  else
153
264
  patterns << arg
154
265
  end
155
266
  end
156
- patterns.sort.uniq
157
- end
158
-
159
- ##
160
- # Expands a keyword to timestamp strings.
161
- #
162
- # :call-seq:
163
- # expand_keyword(keyword as String) -> Array of timestamp Strings
164
-
165
- def expand_keyword(keyword)
166
- patterns = []
167
- case keyword
168
- when "today", "to"
169
- patterns << timestamp_pattern(date_of_today)
170
- when "yesterday", "ye"
171
- patterns << timestamp_pattern(date_of_yesterday)
172
- when "this_week", "tw"
173
- patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
174
- when "last_week", "lw"
175
- patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
176
- else
177
- raise UnknownKeywordError, keyword
178
- end
179
- patterns
267
+ patterns.uniq.sort
180
268
  end
181
269
 
182
270
  ##
183
271
  # Makes a headline with the timestamp and subject of the notes, it
184
272
  # looks like as follows:
185
273
  #
186
- # |<------------------ console column size ------------------->|
187
- # +-- timestamp ---+ +- subject (the 1st line of each note) -+
188
- # | | | |
189
- # 20101010001000_123: I love Macintosh. [EOL]
190
- # 20100909090909_999: This is very very long long loooong subje[EOL]
191
- # ++
192
- # ^--- delimiter (2 characters)
274
+ # |<--------------- console column size -------------------->|
275
+ # | |+-- timestamp ---+ +-subject (the 1st line of note) -+
276
+ # | | | |
277
+ # | |20101010001000_123: I love Macintosh. [EOL]
278
+ # | |20100909090909_999: This is very very long looong subj[EOL]
279
+ # |<->| | |
280
+ # ^--- pad ++
281
+ # ^--- delimiter (2 characters)
193
282
  #
194
283
  # The subject part will truncate when it is long.
195
284
 
196
- def make_headline(timestamp, text)
285
+ def make_headline(timestamp, text, pad = nil)
197
286
  _, column = IO.console_size
198
287
  delimiter = ": "
199
288
  timestamp_width = timestamp.to_s.size
200
289
  subject_width = column - timestamp_width - delimiter.size - 1
290
+ subject_width -= pad.size unless pad.nil?
201
291
 
202
292
  subject = remove_heading_markup(text[0])
203
293
 
204
294
  ts_part = "#{timestamp.to_s} "[0..(timestamp_width - 1)]
295
+ ts_part.prepend(pad) unless pad.nil?
205
296
  sj_part = truncate_str(subject, subject_width)
206
297
 
207
298
  ts_part + delimiter + sj_part
@@ -210,6 +301,7 @@ module Rbnotes
210
301
  ##
211
302
  # Finds all notes those timestamps match to given patterns in the
212
303
  # given repository. Returns an Array contains Timestamp objects.
304
+ # The returned Array is sorted by Timestamp.
213
305
  #
214
306
  # :call-seq:
215
307
  # find_notes(Array of timestamp patterns, Textrepo::Repository)
@@ -220,17 +312,6 @@ module Rbnotes
220
312
  }.flatten.sort{ |a, b| b <=> a }.uniq
221
313
  end
222
314
 
223
- ##
224
- # Enumerates all timestamp patterns in a week which contains a
225
- # given timestamp as a day of the week.
226
- #
227
- # :call-seq:
228
- # timestamp_patterns_in_week(timestamp) -> [Array of Strings]
229
-
230
- def timestamp_patterns_in_week(timestamp)
231
- dates_in_week(start_date_in_the_week(timestamp.time)).map { |date| timestamp_pattern(date) }
232
- end
233
-
234
315
  # :stopdoc:
235
316
 
236
317
  private
@@ -269,6 +350,48 @@ module Rbnotes
269
350
  }.compact
270
351
  end
271
352
 
353
+ def remove_delimiters_from_timestamp_string(stamp_str) # :nodoc:
354
+ str = stamp_str.gsub(TIMESTAMP_DELIMITERS, "")
355
+ base_size = "yyyymiddhhmoss".size
356
+ if str.size > base_size # when suffix is specified
357
+ str = str[0...base_size] + "_" + str[base_size..-1]
358
+ end
359
+ str
360
+ end
361
+
362
+ ##
363
+ # Expands a keyword to timestamp strings.
364
+ #
365
+ # :call-seq:
366
+ # expand_keyword(keyword as String) -> Array of timestamp Strings
367
+ #
368
+ def expand_keyword(keyword)
369
+ patterns = []
370
+ case keyword
371
+ when "today", "to"
372
+ patterns << timestamp_pattern(Date.today)
373
+ when "yesterday", "ye"
374
+ patterns << timestamp_pattern(Date.today.prev_day)
375
+ when "this_week", "tw"
376
+ patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
377
+ when "last_week", "lw"
378
+ patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
379
+ when "this_month", "tm"
380
+ patterns.concat(dates_in_this_month.map { |d| timestamp_pattern(d) })
381
+ when "last_month", "lm"
382
+ patterns.concat(dates_in_last_month.map { |d| timestamp_pattern(d) })
383
+ else
384
+ raise UnknownKeywordError, keyword
385
+ end
386
+ patterns
387
+ end
388
+
389
+ KEYWORDS = %w(
390
+ today to yesterday ye
391
+ this_week tw last_week lw
392
+ this_month tm last_month lm
393
+ )
394
+
272
395
  def search_in_path(name)
273
396
  search_paths = ENV["PATH"].split(":")
274
397
  found = search_paths.map { |path|
@@ -286,49 +409,61 @@ module Rbnotes
286
409
  date.strftime("%Y%m%d")
287
410
  end
288
411
 
289
- def date_of_today
290
- date(Time.now)
291
- end
292
-
293
- def date_of_yesterday
294
- date(Time.now).prev_day
295
- end
296
-
297
412
  def date(time)
298
413
  Date.new(time.year, time.mon, time.day)
299
414
  end
300
415
 
301
416
  def dates_in_this_week
302
- dates_in_week(start_date_in_this_week)
417
+ dates_in_week(Date.today)
303
418
  end
304
419
 
305
420
  def dates_in_last_week
306
- dates_in_week(start_date_in_last_week)
421
+ dates_in_week(Date.today.prev_day(7))
422
+ end
423
+
424
+ def dates_in_week(date)
425
+ start_date = start_date_of_week(date)
426
+ dates = [start_date]
427
+ 1.upto(6) { |i| dates << start_date.next_day(i) }
428
+ dates
307
429
  end
308
430
 
309
- def start_date_in_this_week
310
- start_date_in_the_week(Time.now)
431
+ def start_date_of_week(date)
432
+ # week day in monday start calendar
433
+ date.prev_day((date.wday - 1) % 7)
311
434
  end
312
435
 
313
- def start_date_in_last_week
314
- start_date_in_this_week.prev_day(7)
436
+ def first_date_of_this_month
437
+ today = Time.now
438
+ date(Time.new(today.year, today.mon, 1))
315
439
  end
316
440
 
317
- def start_date_in_the_week(time)
318
- parts = [:year, :mon, :day].map { |sym| time.send(sym) }
319
- Date.new(*parts).prev_day(wday(time))
441
+ def dates_in_this_month
442
+ dates_in_month(first_date_of_this_month)
320
443
  end
321
444
 
322
- def wday(time)
323
- (time.wday - 1) % 7
445
+ def dates_in_last_month
446
+ dates_in_month(first_date_of_this_month.prev_month)
324
447
  end
325
448
 
326
- def dates_in_week(start_date)
327
- dates = [start_date]
328
- 1.upto(6) { |i| dates << start_date.next_day(i) }
449
+ def dates_in_month(first_date)
450
+ days = days_in_month(first_date.mon, leap: first_date.leap?)
451
+ dates = [first_date]
452
+ 1.upto(days - 1) { |i| dates << first_date.next_day(i) }
329
453
  dates
330
454
  end
331
455
 
456
+ DAYS = {
457
+ # 1 2 3 4 5 6 7 8 9 10 11 12
458
+ # Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
459
+ false => [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
460
+ true => [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
461
+ }
462
+
463
+ def days_in_month(mon, leap: false)
464
+ DAYS[leap][mon]
465
+ end
466
+
332
467
  def truncate_str(str, size)
333
468
  count = 0
334
469
  result = ""