rbnotes 0.4.8 → 0.4.13
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 +4 -4
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +0 -1
- data/CHANGELOG.md +46 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +7 -7
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/conf/config.yml +1 -0
- data/conf/config_deve.yml +1 -0
- data/conf/config_deve_fzf_no_opts.yml +8 -0
- data/conf/config_deve_no_picker.yml +7 -0
- data/conf/config_deve_peco.yml +8 -0
- data/etc/zsh/_rbnotes +93 -0
- data/exe/rbnotes +11 -1
- data/lib/rbnotes.rb +1 -5
- data/lib/rbnotes/commands.rb +5 -3
- data/lib/rbnotes/commands/add.rb +26 -1
- data/lib/rbnotes/commands/commands.rb +121 -0
- data/lib/rbnotes/commands/help.rb +1 -38
- data/lib/rbnotes/commands/import.rb +26 -5
- data/lib/rbnotes/commands/list.rb +73 -10
- data/lib/rbnotes/commands/pick.rb +22 -4
- data/lib/rbnotes/commands/show.rb +60 -15
- data/lib/rbnotes/commands/statistics.rb +55 -0
- data/lib/rbnotes/commands/update.rb +1 -1
- data/lib/rbnotes/conf.rb +25 -11
- data/lib/rbnotes/error.rb +55 -3
- data/lib/rbnotes/statistics.rb +101 -0
- data/lib/rbnotes/utils.rb +207 -66
- data/lib/rbnotes/version.rb +2 -2
- data/rbnotes.gemspec +1 -1
- metadata +13 -6
- data/.travis.yml +0 -6
data/lib/rbnotes/conf.rb
CHANGED
|
@@ -31,17 +31,26 @@ module Rbnotes
|
|
|
31
31
|
|
|
32
32
|
DIRNAME_COMMON_CONF = ".config"
|
|
33
33
|
|
|
34
|
-
def initialize(
|
|
35
|
-
@conf_path = conf_path || File.join(base_path, FILENAME_CONF)
|
|
36
|
-
|
|
34
|
+
def initialize(path = nil) # :nodoc:
|
|
37
35
|
@conf = {}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
|
|
37
|
+
unless path.nil?
|
|
38
|
+
abspath = File.expand_path(path)
|
|
39
|
+
raise NoConfFileError, path unless FileTest.exist?(abspath)
|
|
40
|
+
@conf[:path] = abspath
|
|
41
41
|
else
|
|
42
|
-
@conf
|
|
42
|
+
@conf[:path] = default_conf_path
|
|
43
43
|
end
|
|
44
|
-
|
|
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
|
|
45
54
|
end
|
|
46
55
|
|
|
47
56
|
def_delegators(:@conf,
|
|
@@ -89,7 +98,7 @@ module Rbnotes
|
|
|
89
98
|
:test => "_test",
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
def
|
|
101
|
+
def config_home
|
|
93
102
|
path = nil
|
|
94
103
|
xdg, user = ["XDG_CONFIG_HOME", "HOME"].map{|n| ENV[n]}
|
|
95
104
|
if xdg
|
|
@@ -97,12 +106,17 @@ module Rbnotes
|
|
|
97
106
|
else
|
|
98
107
|
path = File.join(user, DIRNAME_COMMON_CONF, DIRNAME_RBNOTES)
|
|
99
108
|
end
|
|
100
|
-
|
|
109
|
+
path
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def default_conf_path
|
|
113
|
+
File.join(config_home, FILENAME_CONF)
|
|
101
114
|
end
|
|
102
|
-
end
|
|
103
115
|
|
|
104
116
|
# :startdoc:
|
|
105
117
|
|
|
118
|
+
end
|
|
119
|
+
|
|
106
120
|
class << self
|
|
107
121
|
##
|
|
108
122
|
# Gets the instance of Rbnotes::Conf. An optional argument is to
|
data/lib/rbnotes/error.rb
CHANGED
|
@@ -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 = "
|
|
13
|
-
PROGRAM_ABORT = "
|
|
14
|
-
UNKNOWN_KEYWORD = "
|
|
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
|
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.
|
|
@@ -99,27 +113,88 @@ module Rbnotes
|
|
|
99
113
|
|
|
100
114
|
def read_timestamp(args)
|
|
101
115
|
str = args.shift || read_arg($stdin)
|
|
116
|
+
raise NoArgumentError if str.nil?
|
|
102
117
|
Textrepo::Timestamp.parse_s(str)
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
##
|
|
106
|
-
#
|
|
107
|
-
#
|
|
121
|
+
# Generates multiple Textrepo::Timestamp objects from the command
|
|
122
|
+
# line arguments. When no argument is given, try to read from
|
|
123
|
+
# STDIN.
|
|
124
|
+
#
|
|
125
|
+
# When multiple strings those point the identical time are
|
|
126
|
+
# included the arguments (passed or read form STDIN), the
|
|
127
|
+
# redundant strings will be removed.
|
|
128
|
+
#
|
|
129
|
+
# The order of the arguments will be preserved into the return
|
|
130
|
+
# value, even if the redundant strings were removed.
|
|
108
131
|
#
|
|
109
132
|
# :call-seq:
|
|
110
|
-
#
|
|
133
|
+
# read_multiple_timestamps(args) -> [String]
|
|
134
|
+
|
|
135
|
+
def read_multiple_timestamps(args)
|
|
136
|
+
strings = args.size < 1 ? read_multiple_args($stdin) : args
|
|
137
|
+
raise NoArgumentError if (strings.nil? || strings.empty?)
|
|
138
|
+
strings.uniq.map { |str| Textrepo::Timestamp.parse_s(str) }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# Reads timestamp patterns in an array of arguments. It supports
|
|
143
|
+
# keywords expansion and enumeration of week. The function is
|
|
144
|
+
# intended to be used from Commands::List#execute and
|
|
145
|
+
# Commands::Pick#execute.
|
|
146
|
+
#
|
|
147
|
+
def read_timestamp_patterns(args, enum_week: false)
|
|
148
|
+
patterns = nil
|
|
149
|
+
if enum_week
|
|
150
|
+
arg = args.shift
|
|
151
|
+
begin
|
|
152
|
+
patterns = timestamp_patterns_in_week(arg.dup)
|
|
153
|
+
rescue InvalidTimestampPatternAsDateError => _e
|
|
154
|
+
raise InvalidTimestampPatternAsDateError, args.unshift(arg)
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
patterns = expand_keyword_in_args(args)
|
|
158
|
+
end
|
|
159
|
+
patterns
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
##
|
|
163
|
+
# Enumerates all timestamp patterns in a week which contains a
|
|
164
|
+
# given timestamp as a day of the week.
|
|
165
|
+
#
|
|
166
|
+
# The argument must be one of the followings:
|
|
167
|
+
# - "yyyymodd" (eg. "20201220")
|
|
168
|
+
# - "yymoddhhmiss" (eg. "20201220120048")
|
|
169
|
+
# - "yymoddhhmiss_sfx" (eg. "20201220120048_012")
|
|
170
|
+
# - "modd" (eg. "1220") (assums in the current year)
|
|
171
|
+
# - nil (assumes today)
|
|
172
|
+
#
|
|
173
|
+
# :call-seq:
|
|
174
|
+
# timestamp_patterns_in_week(String) -> [Array of Strings]
|
|
175
|
+
#
|
|
176
|
+
def timestamp_patterns_in_week(arg)
|
|
177
|
+
date_str = arg || Textrepo::Timestamp.now[0, 8]
|
|
178
|
+
|
|
179
|
+
case date_str.size
|
|
180
|
+
when "yyyymodd".size
|
|
181
|
+
# nothing to do
|
|
182
|
+
when "yyyymoddhhmiss".size, "yyyymoddhhmiss_sfx".size
|
|
183
|
+
date_str = date_str[0, 8]
|
|
184
|
+
when "modd".size
|
|
185
|
+
this_year = Time.now.year.to_s
|
|
186
|
+
date_str = "#{this_year}#{date_str}"
|
|
187
|
+
else
|
|
188
|
+
raise InvalidTimestampPatternAsDateError, arg
|
|
189
|
+
end
|
|
111
190
|
|
|
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
191
|
begin
|
|
119
|
-
|
|
120
|
-
rescue
|
|
121
|
-
|
|
192
|
+
date = Date.parse(date_str)
|
|
193
|
+
rescue Date::Error => _e
|
|
194
|
+
raise InvalidTimestampPatternAsDateError, arg
|
|
122
195
|
end
|
|
196
|
+
|
|
197
|
+
dates_in_week(date).map { |date| timestamp_pattern(date) }
|
|
123
198
|
end
|
|
124
199
|
|
|
125
200
|
##
|
|
@@ -143,19 +218,20 @@ module Rbnotes
|
|
|
143
218
|
# - "yeasterday" (or "ye")
|
|
144
219
|
# - "this_week" (or "tw")
|
|
145
220
|
# - "last_week" (or "lw")
|
|
221
|
+
# - "this_month" (or "tm")
|
|
222
|
+
# - "last_month" (or "lm")
|
|
146
223
|
#
|
|
147
224
|
# :call-seq:
|
|
148
225
|
# expand_keyword_in_args(Array of Strings) -> Array of Strings
|
|
149
|
-
|
|
226
|
+
#
|
|
150
227
|
def expand_keyword_in_args(args)
|
|
151
228
|
return [nil] if args.empty?
|
|
152
229
|
|
|
153
230
|
patterns = []
|
|
154
231
|
while args.size > 0
|
|
155
232
|
arg = args.shift
|
|
156
|
-
if
|
|
157
|
-
|
|
158
|
-
patterns.concat(Rbnotes.utils.expand_keyword(arg))
|
|
233
|
+
if KEYWORDS.include?(arg)
|
|
234
|
+
patterns.concat(expand_keyword(arg))
|
|
159
235
|
else
|
|
160
236
|
patterns << arg
|
|
161
237
|
end
|
|
@@ -163,52 +239,32 @@ module Rbnotes
|
|
|
163
239
|
patterns.sort.uniq
|
|
164
240
|
end
|
|
165
241
|
|
|
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
242
|
##
|
|
190
243
|
# Makes a headline with the timestamp and subject of the notes, it
|
|
191
244
|
# looks like as follows:
|
|
192
245
|
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# |
|
|
196
|
-
# 20101010001000_123: I love Macintosh.
|
|
197
|
-
# 20100909090909_999: This is very very long
|
|
198
|
-
#
|
|
199
|
-
#
|
|
246
|
+
# |<--------------- console column size -------------------->|
|
|
247
|
+
# | |+-- timestamp ---+ +-subject (the 1st line of note) -+
|
|
248
|
+
# | | | |
|
|
249
|
+
# | |20101010001000_123: I love Macintosh. [EOL]
|
|
250
|
+
# | |20100909090909_999: This is very very long looong subj[EOL]
|
|
251
|
+
# |<->| | |
|
|
252
|
+
# ^--- pad ++
|
|
253
|
+
# ^--- delimiter (2 characters)
|
|
200
254
|
#
|
|
201
255
|
# The subject part will truncate when it is long.
|
|
202
256
|
|
|
203
|
-
def make_headline(timestamp, text)
|
|
257
|
+
def make_headline(timestamp, text, pad = nil)
|
|
204
258
|
_, column = IO.console_size
|
|
205
259
|
delimiter = ": "
|
|
206
260
|
timestamp_width = timestamp.to_s.size
|
|
207
261
|
subject_width = column - timestamp_width - delimiter.size - 1
|
|
262
|
+
subject_width -= pad.size unless pad.nil?
|
|
208
263
|
|
|
209
264
|
subject = remove_heading_markup(text[0])
|
|
210
265
|
|
|
211
266
|
ts_part = "#{timestamp.to_s} "[0..(timestamp_width - 1)]
|
|
267
|
+
ts_part.prepend(pad) unless pad.nil?
|
|
212
268
|
sj_part = truncate_str(subject, subject_width)
|
|
213
269
|
|
|
214
270
|
ts_part + delimiter + sj_part
|
|
@@ -217,6 +273,7 @@ module Rbnotes
|
|
|
217
273
|
##
|
|
218
274
|
# Finds all notes those timestamps match to given patterns in the
|
|
219
275
|
# given repository. Returns an Array contains Timestamp objects.
|
|
276
|
+
# The returned Array is sorted by Timestamp.
|
|
220
277
|
#
|
|
221
278
|
# :call-seq:
|
|
222
279
|
# find_notes(Array of timestamp patterns, Textrepo::Repository)
|
|
@@ -230,6 +287,74 @@ module Rbnotes
|
|
|
230
287
|
# :stopdoc:
|
|
231
288
|
|
|
232
289
|
private
|
|
290
|
+
|
|
291
|
+
##
|
|
292
|
+
# Reads an argument from the IO object. Typically, it is intended
|
|
293
|
+
# to be used with STDIN.
|
|
294
|
+
#
|
|
295
|
+
# :call-seq:
|
|
296
|
+
# read_arg(IO) -> String
|
|
297
|
+
|
|
298
|
+
def read_arg(io)
|
|
299
|
+
read_multiple_args(io)[0]
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
##
|
|
303
|
+
# Reads arguments from the IO object. Typically, it is intended
|
|
304
|
+
# to be used with STDIN.
|
|
305
|
+
#
|
|
306
|
+
# :call-seq:
|
|
307
|
+
# read_multiple_arg(IO) -> [String]
|
|
308
|
+
|
|
309
|
+
def read_multiple_args(io)
|
|
310
|
+
strings = io.readlines
|
|
311
|
+
strings.map { |str|
|
|
312
|
+
# assumes the reading line looks like:
|
|
313
|
+
#
|
|
314
|
+
# foo bar baz ...
|
|
315
|
+
#
|
|
316
|
+
# then, only the first string is interested
|
|
317
|
+
begin
|
|
318
|
+
str.split(":")[0].rstrip
|
|
319
|
+
rescue NoMethodError => _
|
|
320
|
+
nil
|
|
321
|
+
end
|
|
322
|
+
}.compact
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
##
|
|
326
|
+
# Expands a keyword to timestamp strings.
|
|
327
|
+
#
|
|
328
|
+
# :call-seq:
|
|
329
|
+
# expand_keyword(keyword as String) -> Array of timestamp Strings
|
|
330
|
+
#
|
|
331
|
+
def expand_keyword(keyword)
|
|
332
|
+
patterns = []
|
|
333
|
+
case keyword
|
|
334
|
+
when "today", "to"
|
|
335
|
+
patterns << timestamp_pattern(Date.today)
|
|
336
|
+
when "yesterday", "ye"
|
|
337
|
+
patterns << timestamp_pattern(Date.today.prev_day)
|
|
338
|
+
when "this_week", "tw"
|
|
339
|
+
patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
|
|
340
|
+
when "last_week", "lw"
|
|
341
|
+
patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
|
|
342
|
+
when "this_month", "tm"
|
|
343
|
+
patterns.concat(dates_in_this_month.map { |d| timestamp_pattern(d) })
|
|
344
|
+
when "last_month", "lm"
|
|
345
|
+
patterns.concat(dates_in_last_month.map { |d| timestamp_pattern(d) })
|
|
346
|
+
else
|
|
347
|
+
raise UnknownKeywordError, keyword
|
|
348
|
+
end
|
|
349
|
+
patterns
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
KEYWORDS = %w(
|
|
353
|
+
today to yesterday ye
|
|
354
|
+
this_week tw last_week lw
|
|
355
|
+
this_month tm last_month lm
|
|
356
|
+
)
|
|
357
|
+
|
|
233
358
|
def search_in_path(name)
|
|
234
359
|
search_paths = ENV["PATH"].split(":")
|
|
235
360
|
found = search_paths.map { |path|
|
|
@@ -247,45 +372,61 @@ module Rbnotes
|
|
|
247
372
|
date.strftime("%Y%m%d")
|
|
248
373
|
end
|
|
249
374
|
|
|
250
|
-
def date_of_today
|
|
251
|
-
date(Time.now)
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def date_of_yesterday
|
|
255
|
-
date(Time.now).prev_day
|
|
256
|
-
end
|
|
257
|
-
|
|
258
375
|
def date(time)
|
|
259
376
|
Date.new(time.year, time.mon, time.day)
|
|
260
377
|
end
|
|
261
378
|
|
|
262
379
|
def dates_in_this_week
|
|
263
|
-
dates_in_week(
|
|
380
|
+
dates_in_week(Date.today)
|
|
264
381
|
end
|
|
265
382
|
|
|
266
383
|
def dates_in_last_week
|
|
267
|
-
dates_in_week(
|
|
384
|
+
dates_in_week(Date.today.prev_day(7))
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def dates_in_week(date)
|
|
388
|
+
start_date = start_date_of_week(date)
|
|
389
|
+
dates = [start_date]
|
|
390
|
+
1.upto(6) { |i| dates << start_date.next_day(i) }
|
|
391
|
+
dates
|
|
268
392
|
end
|
|
269
393
|
|
|
270
|
-
def
|
|
394
|
+
def start_date_of_week(date)
|
|
395
|
+
# week day in monday start calendar
|
|
396
|
+
date.prev_day((date.wday - 1) % 7)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def first_date_of_this_month
|
|
271
400
|
today = Time.now
|
|
272
|
-
|
|
401
|
+
date(Time.new(today.year, today.mon, 1))
|
|
273
402
|
end
|
|
274
403
|
|
|
275
|
-
def
|
|
276
|
-
|
|
404
|
+
def dates_in_this_month
|
|
405
|
+
dates_in_month(first_date_of_this_month)
|
|
277
406
|
end
|
|
278
407
|
|
|
279
|
-
def
|
|
280
|
-
(
|
|
408
|
+
def dates_in_last_month
|
|
409
|
+
dates_in_month(first_date_of_this_month.prev_month)
|
|
281
410
|
end
|
|
282
411
|
|
|
283
|
-
def
|
|
284
|
-
|
|
285
|
-
|
|
412
|
+
def dates_in_month(first_date)
|
|
413
|
+
days = days_in_month(first_date.mon, leap: first_date.leap?)
|
|
414
|
+
dates = [first_date]
|
|
415
|
+
1.upto(days - 1) { |i| dates << first_date.next_day(i) }
|
|
286
416
|
dates
|
|
287
417
|
end
|
|
288
418
|
|
|
419
|
+
DAYS = {
|
|
420
|
+
# 1 2 3 4 5 6 7 8 9 10 11 12
|
|
421
|
+
# Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
|
|
422
|
+
false => [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
|
423
|
+
true => [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
def days_in_month(mon, leap: false)
|
|
427
|
+
DAYS[leap][mon]
|
|
428
|
+
end
|
|
429
|
+
|
|
289
430
|
def truncate_str(str, size)
|
|
290
431
|
count = 0
|
|
291
432
|
result = ""
|