rbnotes 0.4.10 → 0.4.15
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 +45 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +7 -7
- data/README.md +2 -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 +10 -1
- data/lib/rbnotes.rb +1 -5
- data/lib/rbnotes/commands.rb +3 -2
- data/lib/rbnotes/commands/add.rb +45 -14
- data/lib/rbnotes/commands/commands.rb +17 -12
- data/lib/rbnotes/commands/import.rb +37 -5
- data/lib/rbnotes/commands/list.rb +63 -33
- data/lib/rbnotes/commands/pick.rb +33 -4
- data/lib/rbnotes/commands/show.rb +102 -15
- data/lib/rbnotes/commands/statistics.rb +74 -0
- data/lib/rbnotes/commands/update.rb +21 -10
- data/lib/rbnotes/conf.rb +25 -11
- data/lib/rbnotes/error.rb +47 -6
- data/lib/rbnotes/statistics.rb +101 -0
- data/lib/rbnotes/utils.rb +239 -77
- data/lib/rbnotes/version.rb +2 -2
- data/rbnotes.gemspec +1 -1
- metadata +12 -6
- data/.travis.yml +0 -6
@@ -32,16 +32,7 @@ module Rbnotes::Commands
|
|
32
32
|
|
33
33
|
def execute(args, conf)
|
34
34
|
@opts = {}
|
35
|
-
|
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,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
@@ -7,12 +7,15 @@ module Rbnotes
|
|
7
7
|
# :stopdoc:
|
8
8
|
|
9
9
|
module ErrMsg
|
10
|
-
MISSING_ARGUMENT = "
|
11
|
-
MISSING_TIMESTAMP = "
|
12
|
-
NO_EDITOR = "
|
13
|
-
PROGRAM_ABORT = "
|
14
|
-
UNKNOWN_KEYWORD = "
|
15
|
-
INVALID_TIMESTAMP_PATTERN = "
|
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"
|
16
19
|
end
|
17
20
|
|
18
21
|
# :startdoc:
|
@@ -75,4 +78,42 @@ module Rbnotes
|
|
75
78
|
end
|
76
79
|
end
|
77
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
|
+
|
78
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.
|
@@ -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,27 +126,102 @@ 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
|
|
105
135
|
##
|
106
|
-
#
|
107
|
-
#
|
136
|
+
# Generates multiple Textrepo::Timestamp objects from the command
|
137
|
+
# line arguments. When no argument is given, try to read from
|
138
|
+
# STDIN.
|
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.
|
108
146
|
#
|
109
147
|
# :call-seq:
|
110
|
-
#
|
148
|
+
# read_multiple_timestamps(args) -> [String]
|
149
|
+
|
150
|
+
def read_multiple_timestamps(args)
|
151
|
+
strings = args.size < 1 ? read_multiple_args($stdin) : args
|
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
|
111
217
|
|
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
218
|
begin
|
119
|
-
|
120
|
-
rescue
|
121
|
-
|
219
|
+
date = Date.parse(date_str)
|
220
|
+
rescue Date::Error => _e
|
221
|
+
raise InvalidTimestampPatternAsDateError, arg
|
122
222
|
end
|
223
|
+
|
224
|
+
dates_in_week(date).map { |date| timestamp_pattern(date) }
|
123
225
|
end
|
124
226
|
|
125
227
|
##
|
@@ -143,72 +245,53 @@ module Rbnotes
|
|
143
245
|
# - "yeasterday" (or "ye")
|
144
246
|
# - "this_week" (or "tw")
|
145
247
|
# - "last_week" (or "lw")
|
248
|
+
# - "this_month" (or "tm")
|
249
|
+
# - "last_month" (or "lm")
|
146
250
|
#
|
147
251
|
# :call-seq:
|
148
252
|
# expand_keyword_in_args(Array of Strings) -> Array of Strings
|
149
|
-
|
253
|
+
#
|
150
254
|
def expand_keyword_in_args(args)
|
151
255
|
return [nil] if args.empty?
|
152
256
|
|
153
257
|
patterns = []
|
154
258
|
while args.size > 0
|
155
259
|
arg = args.shift
|
156
|
-
if
|
157
|
-
|
158
|
-
patterns.concat(Rbnotes.utils.expand_keyword(arg))
|
260
|
+
if KEYWORDS.include?(arg)
|
261
|
+
patterns.concat(expand_keyword(arg))
|
159
262
|
else
|
160
263
|
patterns << arg
|
161
264
|
end
|
162
265
|
end
|
163
|
-
patterns.sort
|
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
|
266
|
+
patterns.uniq.sort
|
187
267
|
end
|
188
268
|
|
189
269
|
##
|
190
270
|
# Makes a headline with the timestamp and subject of the notes, it
|
191
271
|
# looks like as follows:
|
192
272
|
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
# |
|
196
|
-
# 20101010001000_123: I love Macintosh.
|
197
|
-
# 20100909090909_999: This is very very long
|
198
|
-
#
|
199
|
-
#
|
273
|
+
# |<--------------- console column size -------------------->|
|
274
|
+
# | |+-- timestamp ---+ +-subject (the 1st line of note) -+
|
275
|
+
# | | | |
|
276
|
+
# | |20101010001000_123: I love Macintosh. [EOL]
|
277
|
+
# | |20100909090909_999: This is very very long looong subj[EOL]
|
278
|
+
# |<->| | |
|
279
|
+
# ^--- pad ++
|
280
|
+
# ^--- delimiter (2 characters)
|
200
281
|
#
|
201
282
|
# The subject part will truncate when it is long.
|
202
283
|
|
203
|
-
def make_headline(timestamp, text)
|
284
|
+
def make_headline(timestamp, text, pad = nil)
|
204
285
|
_, column = IO.console_size
|
205
286
|
delimiter = ": "
|
206
287
|
timestamp_width = timestamp.to_s.size
|
207
288
|
subject_width = column - timestamp_width - delimiter.size - 1
|
289
|
+
subject_width -= pad.size unless pad.nil?
|
208
290
|
|
209
291
|
subject = remove_heading_markup(text[0])
|
210
292
|
|
211
293
|
ts_part = "#{timestamp.to_s} "[0..(timestamp_width - 1)]
|
294
|
+
ts_part.prepend(pad) unless pad.nil?
|
212
295
|
sj_part = truncate_str(subject, subject_width)
|
213
296
|
|
214
297
|
ts_part + delimiter + sj_part
|
@@ -217,6 +300,7 @@ module Rbnotes
|
|
217
300
|
##
|
218
301
|
# Finds all notes those timestamps match to given patterns in the
|
219
302
|
# given repository. Returns an Array contains Timestamp objects.
|
303
|
+
# The returned Array is sorted by Timestamp.
|
220
304
|
#
|
221
305
|
# :call-seq:
|
222
306
|
# find_notes(Array of timestamp patterns, Textrepo::Repository)
|
@@ -227,20 +311,86 @@ module Rbnotes
|
|
227
311
|
}.flatten.sort{ |a, b| b <=> a }.uniq
|
228
312
|
end
|
229
313
|
|
314
|
+
# :stopdoc:
|
315
|
+
|
316
|
+
private
|
317
|
+
|
230
318
|
##
|
231
|
-
#
|
232
|
-
#
|
319
|
+
# Reads an argument from the IO object. Typically, it is intended
|
320
|
+
# to be used with STDIN.
|
233
321
|
#
|
234
322
|
# :call-seq:
|
235
|
-
#
|
323
|
+
# read_arg(IO) -> String
|
236
324
|
|
237
|
-
def
|
238
|
-
|
325
|
+
def read_arg(io)
|
326
|
+
read_multiple_args(io)[0]
|
239
327
|
end
|
240
328
|
|
241
|
-
|
329
|
+
##
|
330
|
+
# Reads arguments from the IO object. Typically, it is intended
|
331
|
+
# to be used with STDIN.
|
332
|
+
#
|
333
|
+
# :call-seq:
|
334
|
+
# read_multiple_arg(IO) -> [String]
|
335
|
+
|
336
|
+
def read_multiple_args(io)
|
337
|
+
strings = io.readlines
|
338
|
+
strings.map { |str|
|
339
|
+
# assumes the reading line looks like:
|
340
|
+
#
|
341
|
+
# foo bar baz ...
|
342
|
+
#
|
343
|
+
# then, only the first string is interested
|
344
|
+
begin
|
345
|
+
str.split(":")[0].rstrip
|
346
|
+
rescue NoMethodError => _
|
347
|
+
nil
|
348
|
+
end
|
349
|
+
}.compact
|
350
|
+
end
|
351
|
+
|
352
|
+
def remove_delimiters_from_timestamp_string(stamp_str) # :nodoc:
|
353
|
+
str = stamp_str.gsub(TIMESTAMP_DELIMITERS, "")
|
354
|
+
base_size = "yyyymiddhhmoss".size
|
355
|
+
if str.size > base_size # when suffix is specified
|
356
|
+
str = str[0...base_size] + "_" + str[base_size..-1]
|
357
|
+
end
|
358
|
+
str
|
359
|
+
end
|
360
|
+
|
361
|
+
##
|
362
|
+
# Expands a keyword to timestamp strings.
|
363
|
+
#
|
364
|
+
# :call-seq:
|
365
|
+
# expand_keyword(keyword as String) -> Array of timestamp Strings
|
366
|
+
#
|
367
|
+
def expand_keyword(keyword)
|
368
|
+
patterns = []
|
369
|
+
case keyword
|
370
|
+
when "today", "to"
|
371
|
+
patterns << timestamp_pattern(Date.today)
|
372
|
+
when "yesterday", "ye"
|
373
|
+
patterns << timestamp_pattern(Date.today.prev_day)
|
374
|
+
when "this_week", "tw"
|
375
|
+
patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
|
376
|
+
when "last_week", "lw"
|
377
|
+
patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
|
378
|
+
when "this_month", "tm"
|
379
|
+
patterns.concat(dates_in_this_month.map { |d| timestamp_pattern(d) })
|
380
|
+
when "last_month", "lm"
|
381
|
+
patterns.concat(dates_in_last_month.map { |d| timestamp_pattern(d) })
|
382
|
+
else
|
383
|
+
raise UnknownKeywordError, keyword
|
384
|
+
end
|
385
|
+
patterns
|
386
|
+
end
|
387
|
+
|
388
|
+
KEYWORDS = %w(
|
389
|
+
today to yesterday ye
|
390
|
+
this_week tw last_week lw
|
391
|
+
this_month tm last_month lm
|
392
|
+
)
|
242
393
|
|
243
|
-
private
|
244
394
|
def search_in_path(name)
|
245
395
|
search_paths = ENV["PATH"].split(":")
|
246
396
|
found = search_paths.map { |path|
|
@@ -258,49 +408,61 @@ module Rbnotes
|
|
258
408
|
date.strftime("%Y%m%d")
|
259
409
|
end
|
260
410
|
|
261
|
-
def date_of_today
|
262
|
-
date(Time.now)
|
263
|
-
end
|
264
|
-
|
265
|
-
def date_of_yesterday
|
266
|
-
date(Time.now).prev_day
|
267
|
-
end
|
268
|
-
|
269
411
|
def date(time)
|
270
412
|
Date.new(time.year, time.mon, time.day)
|
271
413
|
end
|
272
414
|
|
273
415
|
def dates_in_this_week
|
274
|
-
dates_in_week(
|
416
|
+
dates_in_week(Date.today)
|
275
417
|
end
|
276
418
|
|
277
419
|
def dates_in_last_week
|
278
|
-
dates_in_week(
|
420
|
+
dates_in_week(Date.today.prev_day(7))
|
279
421
|
end
|
280
422
|
|
281
|
-
def
|
282
|
-
|
423
|
+
def dates_in_week(date)
|
424
|
+
start_date = start_date_of_week(date)
|
425
|
+
dates = [start_date]
|
426
|
+
1.upto(6) { |i| dates << start_date.next_day(i) }
|
427
|
+
dates
|
283
428
|
end
|
284
429
|
|
285
|
-
def
|
286
|
-
|
430
|
+
def start_date_of_week(date)
|
431
|
+
# week day in monday start calendar
|
432
|
+
date.prev_day((date.wday - 1) % 7)
|
287
433
|
end
|
288
434
|
|
289
|
-
def
|
290
|
-
|
291
|
-
|
435
|
+
def first_date_of_this_month
|
436
|
+
today = Time.now
|
437
|
+
date(Time.new(today.year, today.mon, 1))
|
292
438
|
end
|
293
439
|
|
294
|
-
def
|
295
|
-
(
|
440
|
+
def dates_in_this_month
|
441
|
+
dates_in_month(first_date_of_this_month)
|
296
442
|
end
|
297
443
|
|
298
|
-
def
|
299
|
-
|
300
|
-
|
444
|
+
def dates_in_last_month
|
445
|
+
dates_in_month(first_date_of_this_month.prev_month)
|
446
|
+
end
|
447
|
+
|
448
|
+
def dates_in_month(first_date)
|
449
|
+
days = days_in_month(first_date.mon, leap: first_date.leap?)
|
450
|
+
dates = [first_date]
|
451
|
+
1.upto(days - 1) { |i| dates << first_date.next_day(i) }
|
301
452
|
dates
|
302
453
|
end
|
303
454
|
|
455
|
+
DAYS = {
|
456
|
+
# 1 2 3 4 5 6 7 8 9 10 11 12
|
457
|
+
# Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
|
458
|
+
false => [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
459
|
+
true => [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
460
|
+
}
|
461
|
+
|
462
|
+
def days_in_month(mon, leap: false)
|
463
|
+
DAYS[leap][mon]
|
464
|
+
end
|
465
|
+
|
304
466
|
def truncate_str(str, size)
|
305
467
|
count = 0
|
306
468
|
result = ""
|