rbnotes 0.4.11 → 0.4.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = ""