rbnotes 0.4.7 → 0.4.12

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,9 +9,13 @@ module Rbnotes
9
9
  module ErrMsg
10
10
  MISSING_ARGUMENT = "missing argument: %s"
11
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"
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"
15
19
  end
16
20
 
17
21
  # :startdoc:
@@ -64,4 +68,52 @@ module Rbnotes
64
68
  super(ErrMsg::UNKNOWN_KEYWORD % keyword)
65
69
  end
66
70
  end
71
+
72
+ ##
73
+ # An error raised when an invalid timestamp pattern was specified.
74
+
75
+ class InvalidTimestampPatternError < Error
76
+ def initialize(pattern)
77
+ super(ErrMsg::INVALID_TIMESTAMP_PATTERN % pattern)
78
+ end
79
+ end
80
+
81
+ ##
82
+ # An error raised when the specified configuration file does not
83
+ # exist.
84
+
85
+ class NoConfFileError < Error
86
+ def initialize(filename)
87
+ super(ErrMsg::NO_CONF_FILE % filename)
88
+ end
89
+ end
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
+
67
119
  end
@@ -0,0 +1,101 @@
1
+ module Rbnotes
2
+ ##
3
+ # Calculates statistics of the repository.
4
+ class Statistics
5
+ include Enumerable
6
+
7
+ def initialize(conf)
8
+ @repo = Textrepo.init(conf)
9
+ @values = construct_values(@repo)
10
+ end
11
+
12
+ def total_report
13
+ puts @repo.entries.size
14
+ end
15
+
16
+ def yearly_report
17
+ self.each_year { |year, monthly_values|
18
+ num_of_notes = monthly_values.map { |_mon, values| values.size }.sum
19
+ puts "#{year}: #{num_of_notes}"
20
+ }
21
+ end
22
+
23
+ def monthly_report
24
+ self.each { |year, mon, values|
25
+ num_of_notes = values.size
26
+ puts "#{year}/#{mon}: #{num_of_notes}"
27
+ }
28
+ end
29
+
30
+ def each(&block)
31
+ if block.nil?
32
+ @values.map { |year, monthly_values|
33
+ monthly_values.each { |mon, values|
34
+ [year, mon, values]
35
+ }
36
+ }.to_enum(:each)
37
+ else
38
+ @values.each { |year, monthly_values|
39
+ monthly_values.each { |mon, values|
40
+ yield [year, mon, values]
41
+ }
42
+ }
43
+ end
44
+ end
45
+
46
+ def years
47
+ @values.keys
48
+ end
49
+
50
+ def months(year)
51
+ @values[year] || []
52
+ end
53
+
54
+ def each_year(&block)
55
+ if block.nil?
56
+ @values.map { |year, monthly_values|
57
+ [year, monthly_values]
58
+ }.to_enum(:each)
59
+ else
60
+ @values.each { |year, monthly_values|
61
+ yield [year, monthly_values]
62
+ }
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def construct_values(repo)
69
+ values = {}
70
+ repo.each { |timestamp, text|
71
+ value = StatisticValue.new(timestamp, text)
72
+ y = value.year
73
+ m = value.mon
74
+ values[y] ||= {}
75
+ values[y][m] ||= []
76
+
77
+ values[y][m] << value
78
+ }
79
+ values
80
+ end
81
+
82
+ class StatisticValue
83
+
84
+ attr_reader :lines
85
+
86
+ def initialize(timestamp, text)
87
+ @timestamp = timestamp
88
+ @lines = text.size
89
+ end
90
+
91
+ def year
92
+ @timestamp[:year]
93
+ end
94
+
95
+ def mon
96
+ @timestamp[:mon]
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -99,27 +99,22 @@ module Rbnotes
99
99
 
100
100
  def read_timestamp(args)
101
101
  str = args.shift || read_arg($stdin)
102
+ raise NoArgumentError if str.nil?
102
103
  Textrepo::Timestamp.parse_s(str)
103
104
  end
104
105
 
105
106
  ##
106
- # Reads an argument from the IO object. Typically, it is intended
107
- # to be used with STDIN.
107
+ # Generates multiple Textrepo::Timestamp objects from the command
108
+ # line arguments. When no argument is given, try to read from
109
+ # STDIN.
108
110
  #
109
111
  # :call-seq:
110
- # read_arg(IO) -> String
112
+ # read_multiple_timestamps(args) -> [String]
111
113
 
112
- def read_arg(io)
113
- # assumes the reading line looks like:
114
- #
115
- # foo bar baz ...
116
- #
117
- # then, only the first string is interested
118
- begin
119
- io.gets.split(":")[0].rstrip
120
- rescue NoMethodError => _
121
- nil
122
- end
114
+ def read_multiple_timestamps(args)
115
+ strings = args.size < 1 ? read_multiple_args($stdin) : args
116
+ raise NoArgumentError if (strings.nil? || strings.empty?)
117
+ strings.map { |str| Textrepo::Timestamp.parse_s(str) }
123
118
  end
124
119
 
125
120
  ##
@@ -143,6 +138,8 @@ module Rbnotes
143
138
  # - "yeasterday" (or "ye")
144
139
  # - "this_week" (or "tw")
145
140
  # - "last_week" (or "lw")
141
+ # - "this_month" (or "tm")
142
+ # - "last_month" (or "lm")
146
143
  #
147
144
  # :call-seq:
148
145
  # expand_keyword_in_args(Array of Strings) -> Array of Strings
@@ -154,7 +151,8 @@ module Rbnotes
154
151
  while args.size > 0
155
152
  arg = args.shift
156
153
  if ["today", "to", "yesterday", "ye",
157
- "this_week", "tw", "last_week", "lw"].include?(arg)
154
+ "this_week", "tw", "last_week", "lw",
155
+ "this_month", "tm", "last_month", "lm"].include?(arg)
158
156
  patterns.concat(Rbnotes.utils.expand_keyword(arg))
159
157
  else
160
158
  patterns << arg
@@ -180,6 +178,10 @@ module Rbnotes
180
178
  patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
181
179
  when "last_week", "lw"
182
180
  patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
181
+ when "this_month", "tm"
182
+ patterns.concat(dates_in_this_month.map { |d| timestamp_pattern(d) })
183
+ when "last_month", "lm"
184
+ patterns.concat(dates_in_last_month.map { |d| timestamp_pattern(d) })
183
185
  else
184
186
  raise UnknownKeywordError, keyword
185
187
  end
@@ -190,25 +192,28 @@ module Rbnotes
190
192
  # Makes a headline with the timestamp and subject of the notes, it
191
193
  # looks like as follows:
192
194
  #
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)
195
+ # |<--------------- console column size -------------------->|
196
+ # | |+-- timestamp ---+ +-subject (the 1st line of note) -+
197
+ # | | | |
198
+ # | |20101010001000_123: I love Macintosh. [EOL]
199
+ # | |20100909090909_999: This is very very long looong subj[EOL]
200
+ # |<->| | |
201
+ # ^--- pad ++
202
+ # ^--- delimiter (2 characters)
200
203
  #
201
204
  # The subject part will truncate when it is long.
202
205
 
203
- def make_headline(timestamp, text)
206
+ def make_headline(timestamp, text, pad = nil)
204
207
  _, column = IO.console_size
205
208
  delimiter = ": "
206
209
  timestamp_width = timestamp.to_s.size
207
210
  subject_width = column - timestamp_width - delimiter.size - 1
211
+ subject_width -= pad.size unless pad.nil?
208
212
 
209
213
  subject = remove_heading_markup(text[0])
210
214
 
211
215
  ts_part = "#{timestamp.to_s} "[0..(timestamp_width - 1)]
216
+ ts_part.prepend(pad) unless pad.nil?
212
217
  sj_part = truncate_str(subject, subject_width)
213
218
 
214
219
  ts_part + delimiter + sj_part
@@ -217,6 +222,7 @@ module Rbnotes
217
222
  ##
218
223
  # Finds all notes those timestamps match to given patterns in the
219
224
  # given repository. Returns an Array contains Timestamp objects.
225
+ # The returned Array is sorted by Timestamp.
220
226
  #
221
227
  # :call-seq:
222
228
  # find_notes(Array of timestamp patterns, Textrepo::Repository)
@@ -227,9 +233,55 @@ module Rbnotes
227
233
  }.flatten.sort{ |a, b| b <=> a }.uniq
228
234
  end
229
235
 
236
+ ##
237
+ # Enumerates all timestamp patterns in a week which contains a
238
+ # given timestamp as a day of the week.
239
+ #
240
+ # :call-seq:
241
+ # timestamp_patterns_in_week(timestamp) -> [Array of Strings]
242
+
243
+ def timestamp_patterns_in_week(timestamp)
244
+ dates_in_week(start_date_in_the_week(timestamp.time)).map { |date| timestamp_pattern(date) }
245
+ end
246
+
230
247
  # :stopdoc:
231
248
 
232
249
  private
250
+
251
+ ##
252
+ # Reads an argument from the IO object. Typically, it is intended
253
+ # to be used with STDIN.
254
+ #
255
+ # :call-seq:
256
+ # read_arg(IO) -> String
257
+
258
+ def read_arg(io)
259
+ read_multiple_args(io)[0]
260
+ end
261
+
262
+ ##
263
+ # Reads arguments from the IO object. Typically, it is intended
264
+ # to be used with STDIN.
265
+ #
266
+ # :call-seq:
267
+ # read_multiple_arg(IO) -> [String]
268
+
269
+ def read_multiple_args(io)
270
+ strings = io.readlines
271
+ strings.map { |str|
272
+ # assumes the reading line looks like:
273
+ #
274
+ # foo bar baz ...
275
+ #
276
+ # then, only the first string is interested
277
+ begin
278
+ str.split(":")[0].rstrip
279
+ rescue NoMethodError => _
280
+ nil
281
+ end
282
+ }.compact
283
+ end
284
+
233
285
  def search_in_path(name)
234
286
  search_paths = ENV["PATH"].split(":")
235
287
  found = search_paths.map { |path|
@@ -268,14 +320,18 @@ module Rbnotes
268
320
  end
269
321
 
270
322
  def start_date_in_this_week
271
- today = Time.now
272
- Date.new(today.year, today.mon, today.day).prev_day(wday(today))
323
+ start_date_in_the_week(Time.now)
273
324
  end
274
325
 
275
326
  def start_date_in_last_week
276
327
  start_date_in_this_week.prev_day(7)
277
328
  end
278
329
 
330
+ def start_date_in_the_week(time)
331
+ parts = [:year, :mon, :day].map { |sym| time.send(sym) }
332
+ Date.new(*parts).prev_day(wday(time))
333
+ end
334
+
279
335
  def wday(time)
280
336
  (time.wday - 1) % 7
281
337
  end
@@ -286,6 +342,36 @@ module Rbnotes
286
342
  dates
287
343
  end
288
344
 
345
+ def dates_in_this_month
346
+ today = Time.now
347
+ first_date = date(Time.new(today.year, today.mon, 1))
348
+ dates_in_month(first_date)
349
+ end
350
+
351
+ def dates_in_last_month
352
+ today = Time.now
353
+ first_date_of_this_month = date(Time.new(today.year, today.mon, 1))
354
+ dates_in_month(first_date_of_this_month.prev_month)
355
+ end
356
+
357
+ def dates_in_month(first_date)
358
+ days = days_in_month(first_date.mon, leap: first_date.leap?)
359
+ dates = [first_date]
360
+ 1.upto(days - 1) { |i| dates << first_date.next_day(i) }
361
+ dates
362
+ end
363
+
364
+ DAYS = {
365
+ # 1 2 3 4 5 6 7 8 9 10 11 12
366
+ # Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
367
+ false => [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
368
+ true => [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
369
+ }
370
+
371
+ def days_in_month(mon, leap: false)
372
+ DAYS[leap][mon]
373
+ end
374
+
289
375
  def truncate_str(str, size)
290
376
  count = 0
291
377
  result = ""
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.4.7"
3
- RELEASE = "2020-11-15"
2
+ VERSION = "0.4.12"
3
+ RELEASE = "2020-12-18"
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.7
4
+ version: 0.4.12
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-15 00:00:00.000000000 Z
11
+ date: 2020-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: textrepo
@@ -58,11 +58,16 @@ files:
58
58
  - bin/setup
59
59
  - conf/config.yml
60
60
  - conf/config_deve.yml
61
+ - conf/config_deve_fzf_no_opts.yml
62
+ - conf/config_deve_no_picker.yml
63
+ - conf/config_deve_peco.yml
61
64
  - conf/config_test.yml
65
+ - etc/zsh/_rbnotes
62
66
  - exe/rbnotes
63
67
  - lib/rbnotes.rb
64
68
  - lib/rbnotes/commands.rb
65
69
  - lib/rbnotes/commands/add.rb
70
+ - lib/rbnotes/commands/commands.rb
66
71
  - lib/rbnotes/commands/delete.rb
67
72
  - lib/rbnotes/commands/export.rb
68
73
  - lib/rbnotes/commands/help.rb
@@ -71,9 +76,11 @@ files:
71
76
  - lib/rbnotes/commands/pick.rb
72
77
  - lib/rbnotes/commands/search.rb
73
78
  - lib/rbnotes/commands/show.rb
79
+ - lib/rbnotes/commands/statistics.rb
74
80
  - lib/rbnotes/commands/update.rb
75
81
  - lib/rbnotes/conf.rb
76
82
  - lib/rbnotes/error.rb
83
+ - lib/rbnotes/statistics.rb
77
84
  - lib/rbnotes/utils.rb
78
85
  - lib/rbnotes/version.rb
79
86
  - rbnotes.gemspec