rbnotes 0.4.11 → 0.4.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +0 -1
- data/CHANGELOG.md +36 -0
- 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/exe/rbnotes +9 -1
- data/lib/rbnotes.rb +0 -5
- data/lib/rbnotes/commands/add.rb +45 -14
- data/lib/rbnotes/commands/commands.rb +17 -12
- data/lib/rbnotes/commands/import.rb +21 -10
- data/lib/rbnotes/commands/list.rb +69 -32
- data/lib/rbnotes/commands/pick.rb +40 -4
- data/lib/rbnotes/commands/show.rb +45 -3
- data/lib/rbnotes/commands/statistics.rb +32 -13
- data/lib/rbnotes/commands/update.rb +21 -10
- data/lib/rbnotes/conf.rb +20 -17
- data/lib/rbnotes/error.rb +37 -7
- data/lib/rbnotes/utils.rb +207 -72
- data/lib/rbnotes/version.rb +2 -2
- data/rbnotes.gemspec +1 -1
- metadata +9 -6
- data/.travis.yml +0 -6
@@ -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
|
-
|
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
|
-
|
18
|
-
list <<
|
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(
|
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|
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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(
|
35
|
-
@conf_path = conf_path
|
34
|
+
def initialize(path = nil) # :nodoc:
|
36
35
|
@conf = {}
|
37
36
|
|
38
|
-
|
39
|
-
|
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
|
-
@
|
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
|
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
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
def default_conf_file
|
106
|
-
File.join(base_path, FILENAME_CONF)
|
109
|
+
path
|
107
110
|
end
|
108
111
|
|
109
|
-
def
|
110
|
-
|
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 = "
|
11
|
-
MISSING_TIMESTAMP = "
|
12
|
-
NO_EDITOR = "
|
13
|
-
PROGRAM_ABORT = "
|
14
|
-
UNKNOWN_KEYWORD = "
|
15
|
-
INVALID_TIMESTAMP_PATTERN = "
|
16
|
-
NO_CONF_FILE = "
|
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.
|
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
|
150
|
-
|
151
|
-
|
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
|
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
|
-
#
|
187
|
-
#
|
188
|
-
# |
|
189
|
-
# 20101010001000_123: I love Macintosh.
|
190
|
-
# 20100909090909_999: This is very very long
|
191
|
-
#
|
192
|
-
#
|
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(
|
417
|
+
dates_in_week(Date.today)
|
303
418
|
end
|
304
419
|
|
305
420
|
def dates_in_last_week
|
306
|
-
dates_in_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
|
310
|
-
|
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
|
314
|
-
|
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
|
318
|
-
|
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
|
323
|
-
(
|
445
|
+
def dates_in_last_month
|
446
|
+
dates_in_month(first_date_of_this_month.prev_month)
|
324
447
|
end
|
325
448
|
|
326
|
-
def
|
327
|
-
|
328
|
-
|
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 = ""
|