rbnotes 0.4.5 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -1
- data/Gemfile.lock +2 -2
- data/conf/config.yml +1 -0
- data/conf/config_deve.yml +1 -0
- data/exe/rbnotes +6 -0
- data/lib/rbnotes.rb +6 -0
- data/lib/rbnotes/commands.rb +2 -1
- data/lib/rbnotes/commands/add.rb +2 -3
- data/lib/rbnotes/commands/commands.rb +121 -0
- data/lib/rbnotes/commands/delete.rb +1 -1
- data/lib/rbnotes/commands/export.rb +1 -1
- data/lib/rbnotes/commands/help.rb +1 -38
- data/lib/rbnotes/commands/list.rb +39 -102
- data/lib/rbnotes/commands/pick.rb +47 -0
- data/lib/rbnotes/commands/search.rb +47 -4
- data/lib/rbnotes/commands/show.rb +1 -1
- data/lib/rbnotes/commands/update.rb +4 -5
- data/lib/rbnotes/error.rb +24 -2
- data/lib/rbnotes/utils.rb +188 -8
- data/lib/rbnotes/version.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e6de4675ffb2b409f72f240e4509a6d254793d977a8dd68cc54cf6d0c839e3d
|
4
|
+
data.tar.gz: 364d4deb47d049af7145024cfb3151639ea9cf0504963c1b72e5a15e465a84a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf90b255e23257a921f7a9254d4df5233f97394e73368f27f6a4a8e96989fa3aa76ab55fca69b201ecd9a15264d5d07cf974ee9b745854ca80d3c962accbe37a
|
7
|
+
data.tar.gz: c9fca12a0a6a71562a968775b09e3ebf7bda202ce7251666ab7b727236c47f8b5981d7ee703b94bfc22fae038cc161f76b10ed413ca28bcac3a9d9e0417f7f83
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
## [Unreleased]
|
8
8
|
Nothing to record here.
|
9
9
|
|
10
|
+
## [0.4.10] - 2020-11-20
|
11
|
+
### Added
|
12
|
+
- Add a new command `commands` to show all command names. (#71)
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
- Fix issue #69: crashes with invalid timestamp pattern.
|
16
|
+
|
17
|
+
## [0.4.9] - 2020-11-17
|
18
|
+
### Added
|
19
|
+
- Add a new option `--week` to the `list` command. (#67)
|
20
|
+
|
21
|
+
## [0.4.8] - 2020-11-16
|
22
|
+
### Fixed
|
23
|
+
- Fix issue #65: messy output of the `search` command.
|
24
|
+
|
25
|
+
## [0.4.7] - 2020-11-15
|
26
|
+
### Changed
|
27
|
+
- Beautify output of the `search` command. (#63)
|
28
|
+
|
29
|
+
### Fixed
|
30
|
+
- Fix issue #61: `list` command fails in pipeline.
|
31
|
+
|
32
|
+
## [0.4.6] - 2020-11-13
|
33
|
+
### Added
|
34
|
+
- Add a new command `pick` to select a note with picker program. (#59)
|
35
|
+
|
10
36
|
## [0.4.5] - 2020-11-12
|
11
37
|
### Changed
|
12
38
|
- Add a feature to accept multiple args for `list`. (#57)
|
@@ -24,7 +50,7 @@ Nothing to record here.
|
|
24
50
|
- Add individual help for each command. (#42)
|
25
51
|
|
26
52
|
### Fixed
|
27
|
-
- Fix `add` fails without modification
|
53
|
+
- Fix issue #48: `add` fails without modification.
|
28
54
|
|
29
55
|
## [0.4.2] - 2020-11-05
|
30
56
|
### Changed
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rbnotes (0.4.
|
4
|
+
rbnotes (0.4.10)
|
5
5
|
textrepo (~> 0.5.4)
|
6
6
|
unicode-display_width (~> 1.7)
|
7
7
|
|
@@ -10,7 +10,7 @@ GEM
|
|
10
10
|
specs:
|
11
11
|
minitest (5.14.2)
|
12
12
|
rake (13.0.1)
|
13
|
-
textrepo (0.5.
|
13
|
+
textrepo (0.5.7)
|
14
14
|
unicode-display_width (1.7.0)
|
15
15
|
|
16
16
|
PLATFORMS
|
data/conf/config.yml
CHANGED
data/conf/config_deve.yml
CHANGED
data/exe/rbnotes
CHANGED
@@ -48,9 +48,15 @@ app = App.new
|
|
48
48
|
begin
|
49
49
|
app.parse_global_options(ARGV)
|
50
50
|
app.run(ARGV)
|
51
|
+
rescue Errno::EPIPE => e
|
52
|
+
# Fix issue #61: When the pipeline which rbnotes connects is
|
53
|
+
# discarded by the other program, the execption was raised. It does
|
54
|
+
# not end abnormally for rbnotes. So, just ignores the exception.
|
55
|
+
exit 0
|
51
56
|
rescue MissingArgumentError, MissingTimestampError,
|
52
57
|
NoEditorError, ProgramAbortError,
|
53
58
|
Textrepo::InvalidTimestampStringError,
|
59
|
+
InvalidTimestampPatternError,
|
54
60
|
ArgumentError,
|
55
61
|
Errno::EACCES => e
|
56
62
|
puts e.message
|
data/lib/rbnotes.rb
CHANGED
data/lib/rbnotes/commands.rb
CHANGED
@@ -58,13 +58,14 @@ Syntax:
|
|
58
58
|
|
59
59
|
Example usage:
|
60
60
|
#{Rbnotes::NAME} add [-t STAMP_PATTERN]
|
61
|
+
#{Rbnotes::NAME} commands [-d]
|
61
62
|
#{Rbnotes::NAME} delete [TIMESTAMP]
|
62
63
|
#{Rbnotes::NAME} export [TIMESTAMP [FILENAME]]
|
63
64
|
#{Rbnotes::NAME} import FILE
|
64
65
|
#{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
|
65
66
|
#{Rbnotes::NAME} search PATTERN [STAMP_PATTERN]
|
66
67
|
#{Rbnotes::NAME} show [TIMESTAMP]
|
67
|
-
#{Rbnotes::NAME} update [TIMESTAMP]
|
68
|
+
#{Rbnotes::NAME} update [-k] [TIMESTAMP]
|
68
69
|
|
69
70
|
Further help for each command:
|
70
71
|
#{Rbnotes::NAME} help commands
|
data/lib/rbnotes/commands/add.rb
CHANGED
@@ -27,7 +27,6 @@ module Rbnotes::Commands
|
|
27
27
|
# If none of the above editor is available, the command fails.
|
28
28
|
|
29
29
|
class Add < Command
|
30
|
-
include ::Rbnotes::Utils
|
31
30
|
|
32
31
|
def description # :nodoc:
|
33
32
|
"Add a new note"
|
@@ -52,10 +51,10 @@ module Rbnotes::Commands
|
|
52
51
|
stamp = @opts[:timestamp] || Textrepo::Timestamp.new(Time.now)
|
53
52
|
|
54
53
|
candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
|
55
|
-
editor = find_program(candidates)
|
54
|
+
editor = Rbnotes.utils.find_program(candidates)
|
56
55
|
raise Rbnotes::NoEditorError, candidates if editor.nil?
|
57
56
|
|
58
|
-
tmpfile = run_with_tmpfile(editor, stamp.to_s)
|
57
|
+
tmpfile = Rbnotes.utils.run_with_tmpfile(editor, stamp.to_s)
|
59
58
|
|
60
59
|
unless FileTest.exist?(tmpfile)
|
61
60
|
puts "Cancel adding, since nothing to store"
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Rbnotes::Commands
|
2
|
+
##
|
3
|
+
# Prints all command names into a single line. When `-d` (or
|
4
|
+
# `--deve-commands`) was specified, development commands (such
|
5
|
+
# `conf`) would be also printed in addition to general commands.
|
6
|
+
|
7
|
+
class Commands < Command
|
8
|
+
|
9
|
+
def description # :nodoc:
|
10
|
+
"Print all command names into a single line"
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(args, conf)
|
14
|
+
@opts = {}
|
15
|
+
while args.size > 0
|
16
|
+
arg = args.shift
|
17
|
+
case arg.to_s
|
18
|
+
when "" # no options
|
19
|
+
break
|
20
|
+
when "-d", "--deve-commands"
|
21
|
+
@opts[:print_deve_commands] = true
|
22
|
+
else # invalid options or args
|
23
|
+
args.unshift(arg)
|
24
|
+
raise ArgumentError, "invalid option or argument: %s" % args.join(" ")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
puts commands(@opts[:print_deve_commands]).join(" ")
|
29
|
+
end
|
30
|
+
|
31
|
+
def help
|
32
|
+
puts <<HELP
|
33
|
+
usage:
|
34
|
+
#{Rbnotes::NAME} [-d|--deve-commands]
|
35
|
+
|
36
|
+
Print all command names into a single line. If "-d" option (or
|
37
|
+
"--deve-commands") is specified, commands for development purpose are
|
38
|
+
also printed.
|
39
|
+
|
40
|
+
HELP
|
41
|
+
print_commands
|
42
|
+
end
|
43
|
+
|
44
|
+
# :stopdoc:
|
45
|
+
private
|
46
|
+
|
47
|
+
##
|
48
|
+
# Enumerates all command names.
|
49
|
+
#
|
50
|
+
# :call-seq:
|
51
|
+
# commands(builtins = false) -> [Array of Strings]
|
52
|
+
|
53
|
+
def commands(include_builtins = false)
|
54
|
+
names = external_commands.map { |cmd| cmd.to_s.downcase }
|
55
|
+
names += builtin_commands.map { |cmd| cmd.to_s.downcase } if include_builtins
|
56
|
+
names
|
57
|
+
end
|
58
|
+
|
59
|
+
def external_commands
|
60
|
+
Dir.glob("*.rb", :base => __dir__) { |rb|
|
61
|
+
require_relative rb
|
62
|
+
}
|
63
|
+
Rbnotes::Commands.constants.difference([:Builtins, :Command]).sort
|
64
|
+
end
|
65
|
+
|
66
|
+
def builtin_commands
|
67
|
+
Rbnotes::Commands::Builtins.constants.sort
|
68
|
+
end
|
69
|
+
|
70
|
+
def print_commands
|
71
|
+
Dir.glob("*.rb", :base => __dir__) { |rb|
|
72
|
+
require_relative rb
|
73
|
+
}
|
74
|
+
puts "#{Rbnotes::NAME.capitalize} Commands:"
|
75
|
+
print_commands_desc(external_commands)
|
76
|
+
puts
|
77
|
+
puts "for development purpose"
|
78
|
+
print_builtins_desc(builtin_commands)
|
79
|
+
end
|
80
|
+
|
81
|
+
def print_commands_desc(commands)
|
82
|
+
print_desc(Rbnotes::Commands, commands)
|
83
|
+
end
|
84
|
+
|
85
|
+
def print_builtins_desc(builtins)
|
86
|
+
print_desc(Rbnotes::Commands::Builtins, builtins)
|
87
|
+
end
|
88
|
+
|
89
|
+
class CmdNames
|
90
|
+
attr_reader :symbol, :name, :size
|
91
|
+
def initialize(cmd)
|
92
|
+
@symbol = cmd
|
93
|
+
@name = cmd.to_s.downcase
|
94
|
+
@size = name.size
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def print_desc(mod, commands)
|
99
|
+
cmds = commands.map { |cmd| CmdNames.new(cmd) }
|
100
|
+
name_part_size = cmds.map(&:size).max + 2
|
101
|
+
cmds.map { |cmd|
|
102
|
+
puts "#{spaces(4)}#{name_part(cmd.name, name_part_size)} #{desc_part(cmd.symbol, mod)}"
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def name_part(name, size)
|
107
|
+
"#{name}#{spaces(size)}"[0, size]
|
108
|
+
end
|
109
|
+
|
110
|
+
def desc_part(symbol, mod)
|
111
|
+
mod.const_get(symbol, false).new.description
|
112
|
+
end
|
113
|
+
|
114
|
+
def spaces(size)
|
115
|
+
Array.new(size, " ").join
|
116
|
+
end
|
117
|
+
|
118
|
+
# :startdoc:
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -7,7 +7,7 @@ module Rbnotes::Commands
|
|
7
7
|
class Help < Command
|
8
8
|
|
9
9
|
def description # :nodoc:
|
10
|
-
"
|
10
|
+
"Print help of each command"
|
11
11
|
end
|
12
12
|
|
13
13
|
##
|
@@ -20,8 +20,6 @@ module Rbnotes::Commands
|
|
20
20
|
case cmd_name
|
21
21
|
when nil
|
22
22
|
self.help
|
23
|
-
when "commands"
|
24
|
-
print_commands
|
25
23
|
else
|
26
24
|
Commands.load(cmd_name).help
|
27
25
|
end
|
@@ -59,40 +57,5 @@ Further information:
|
|
59
57
|
HELP
|
60
58
|
end
|
61
59
|
|
62
|
-
# :stopdoc:
|
63
|
-
private
|
64
|
-
|
65
|
-
def print_commands
|
66
|
-
Dir.glob("*.rb", :base => __dir__) { |rb|
|
67
|
-
next if rb == "help.rb"
|
68
|
-
require_relative rb
|
69
|
-
}
|
70
|
-
commands = Commands.constants.difference([:Builtins, :Command])
|
71
|
-
builtins = Commands::Builtins.constants
|
72
|
-
|
73
|
-
puts "#{Rbnotes::NAME.capitalize} Commands:"
|
74
|
-
print_commands_desc(commands.sort)
|
75
|
-
puts
|
76
|
-
puts "for development purpose"
|
77
|
-
print_builtins_desc(builtins.sort)
|
78
|
-
end
|
79
|
-
|
80
|
-
def print_commands_desc(commands)
|
81
|
-
print_desc(Commands, commands)
|
82
|
-
end
|
83
|
-
|
84
|
-
def print_builtins_desc(builtins)
|
85
|
-
print_desc(Commands::Builtins, builtins)
|
86
|
-
end
|
87
|
-
|
88
|
-
def print_desc(mod, commands)
|
89
|
-
commands.map { |cmd|
|
90
|
-
name = "#{cmd.to_s.downcase} "[0, 8]
|
91
|
-
desc = mod.const_get(cmd, false).new.description
|
92
|
-
puts " #{name} #{desc}"
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
# :startdoc:
|
97
60
|
end
|
98
61
|
end
|
@@ -1,7 +1,3 @@
|
|
1
|
-
require "date"
|
2
|
-
require "unicode/display_width"
|
3
|
-
require "io/console/size"
|
4
|
-
|
5
1
|
module Rbnotes::Commands
|
6
2
|
|
7
3
|
##
|
@@ -55,22 +51,45 @@ module Rbnotes::Commands
|
|
55
51
|
# execute(Array, Rbnotes::Conf or Hash) -> nil
|
56
52
|
|
57
53
|
def execute(args, conf)
|
58
|
-
|
54
|
+
@opts = {}
|
55
|
+
while args.size > 0
|
56
|
+
arg = args.shift
|
57
|
+
case arg
|
58
|
+
when "-w", "--week"
|
59
|
+
@opts[:enum_week] = true
|
60
|
+
else
|
61
|
+
args.unshift(arg)
|
62
|
+
break
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
patterns = nil
|
67
|
+
if @opts[:enum_week]
|
68
|
+
arg = args.shift || Textrepo::Timestamp.now[0, 8]
|
69
|
+
case arg.size
|
70
|
+
when "yyyymodd".size, "yyyymoddhhmiss".size, "yyyymoddhhmiss_sfx".size
|
71
|
+
stamp_str = "#{arg}000000"[0, 14]
|
72
|
+
timestamp = Textrepo::Timestamp.parse_s(stamp_str)
|
73
|
+
patterns = Rbnotes.utils.timestamp_patterns_in_week(timestamp)
|
74
|
+
else
|
75
|
+
raise InvalidTimestampPatternError,
|
76
|
+
"cannot convert to a date [%s]" % args.unshift(arg)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
patterns = Rbnotes.utils.expand_keyword_in_args(args)
|
80
|
+
end
|
59
81
|
|
60
82
|
@repo = Textrepo.init(conf)
|
61
83
|
# newer stamp shoud be above
|
62
|
-
|
63
|
-
@repo.
|
64
|
-
}.flatten.sort{|a, b| b <=> a}.uniq
|
65
|
-
stamps.each { |timestamp|
|
66
|
-
puts make_headline(timestamp)
|
84
|
+
Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
|
85
|
+
puts Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
|
67
86
|
}
|
68
87
|
end
|
69
88
|
|
70
89
|
def help # :nodoc:
|
71
90
|
puts <<HELP
|
72
91
|
usage:
|
73
|
-
#{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
|
92
|
+
#{Rbnotes::NAME} list [-w|--week][STAMP_PATTERN|KEYWORD]
|
74
93
|
|
75
94
|
Show a list of notes. When no arguments, make a list with all notes
|
76
95
|
in the repository. When specified STAMP_PATTERN, only those match the
|
@@ -92,100 +111,18 @@ KEYWORD:
|
|
92
111
|
- "this_week" (or "tw")
|
93
112
|
- "last_week" (or "lw")
|
94
113
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
private
|
101
|
-
TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
|
102
|
-
|
103
|
-
##
|
104
|
-
# Makes a headline with the timestamp and subject of the notes, it
|
105
|
-
# looks like as follows:
|
106
|
-
#
|
107
|
-
# |<------------------ console column size ------------------->|
|
108
|
-
# +-- timestamp ---+ +- subject (the 1st line of each note) -+
|
109
|
-
# | | | |
|
110
|
-
# 20101010001000_123: I love Macintosh. [EOL]
|
111
|
-
# 20100909090909_999: This is very very long long loooong subje[EOL]
|
112
|
-
# ++
|
113
|
-
# ^--- delimiter (2 characters)
|
114
|
-
#
|
115
|
-
# The subject part will truncate when it is long.
|
116
|
-
|
117
|
-
def make_headline(timestamp)
|
118
|
-
_, column = IO.console_size
|
119
|
-
delimiter = ": "
|
120
|
-
subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
|
114
|
+
An option "--week" is also acceptable. It specifies to enumerate all
|
115
|
+
days of a week. Typically, the option is used with a STAMP_PATTERN
|
116
|
+
which specifies a date, such "20201117", then it enumerates all days
|
117
|
+
of the week which contains "17th November 2020".
|
121
118
|
|
122
|
-
|
119
|
+
A STAMP_PATTERN other than (a) and (b) causes an error if it was used
|
120
|
+
with "--week" option.
|
123
121
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
ts_part + delimiter + sj_part
|
128
|
-
end
|
129
|
-
|
130
|
-
def truncate_str(str, size)
|
131
|
-
count = 0
|
132
|
-
result = ""
|
133
|
-
str.each_char { |c|
|
134
|
-
count += Unicode::DisplayWidth.of(c)
|
135
|
-
break if count > size
|
136
|
-
result << c
|
137
|
-
}
|
138
|
-
result
|
139
|
-
end
|
140
|
-
|
141
|
-
def remove_heading_markup(str)
|
142
|
-
str.sub(/^#+ +/, '')
|
143
|
-
end
|
144
|
-
|
145
|
-
def convert_keyword(args)
|
146
|
-
patterns = []
|
147
|
-
while args.size > 0
|
148
|
-
arg = args.shift
|
149
|
-
case arg.to_s
|
150
|
-
when "today", "to"
|
151
|
-
patterns << Textrepo::Timestamp.new(Time.now).to_s[0..7]
|
152
|
-
when "yesterday", "ye"
|
153
|
-
t = Time.now
|
154
|
-
patterns << Date.new(t.year, t.mon, t.day).prev_day.strftime("%Y%m%d")
|
155
|
-
when "this_week", "tw"
|
156
|
-
patterns.concat(dates_in_this_week)
|
157
|
-
when "last_week", "lw"
|
158
|
-
patterns.concat(dates_in_last_week)
|
159
|
-
else
|
160
|
-
patterns << arg
|
161
|
-
end
|
162
|
-
end
|
163
|
-
patterns.sort!.uniq
|
164
|
-
end
|
165
|
-
|
166
|
-
# week day for Monday start calendar
|
167
|
-
def wday(time)
|
168
|
-
(time.wday - 1) % 7
|
169
|
-
end
|
170
|
-
|
171
|
-
def dates_in_this_week
|
172
|
-
to = Time.now
|
173
|
-
start = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
|
174
|
-
dates_in_week(start)
|
175
|
-
end
|
176
|
-
|
177
|
-
def dates_in_last_week
|
178
|
-
to = Time.now
|
179
|
-
start_of_this_week = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
|
180
|
-
dates_in_week(start_of_this_week.prev_day(7))
|
181
|
-
end
|
182
|
-
|
183
|
-
def dates_in_week(start_date)
|
184
|
-
dates = [start_date]
|
185
|
-
1.upto(6) { |i| dates << start_date.next_day(i) }
|
186
|
-
dates.map { |d| d.strftime("%Y%m%d") }
|
122
|
+
When no STAMP_PATTERN was specified with "--week" option, the output
|
123
|
+
would be as same as the KEYWORD, "this_week" was specified.
|
124
|
+
HELP
|
187
125
|
end
|
188
126
|
|
189
|
-
# :startdoc:
|
190
127
|
end
|
191
128
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rbnotes::Commands
|
2
|
+
|
3
|
+
##
|
4
|
+
# Picks a timestamp with a picker program, like `fzf`.
|
5
|
+
|
6
|
+
class Pick < Command
|
7
|
+
|
8
|
+
def description # :nodoc:
|
9
|
+
"Pick a timestamp with a picker program"
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(args, conf)
|
13
|
+
patterns = Rbnotes.utils.expand_keyword_in_args(args)
|
14
|
+
@repo = Textrepo.init(conf)
|
15
|
+
|
16
|
+
list = []
|
17
|
+
Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
|
18
|
+
list << Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
|
19
|
+
}
|
20
|
+
|
21
|
+
picker = conf[:picker]
|
22
|
+
unless picker.nil?
|
23
|
+
require 'open3'
|
24
|
+
result = Open3.pipeline_rw(picker) { |stdin, stdout, _|
|
25
|
+
stdin.puts list
|
26
|
+
stdin.close
|
27
|
+
stdout.read
|
28
|
+
}
|
29
|
+
puts result
|
30
|
+
else
|
31
|
+
puts list
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def help # :nodoc:
|
36
|
+
puts <<HELP
|
37
|
+
usage:
|
38
|
+
#{Rbnotes::NAME} pick
|
39
|
+
|
40
|
+
Pick a timestamp with a picker program, like `fzf`. This command
|
41
|
+
refers the configuration setting of ":picker". If no picker program
|
42
|
+
is specified, it will behave as same as "list" command.
|
43
|
+
|
44
|
+
HELP
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -39,11 +39,8 @@ module Rbnotes::Commands
|
|
39
39
|
result = repo.search(pattern, timestamp_pattern)
|
40
40
|
rescue Textrepo::InvalidSearchResultError => e
|
41
41
|
puts e.message
|
42
|
-
else
|
43
|
-
result.each { |stamp, num, match|
|
44
|
-
puts "#{stamp}:#{num}:#{match}"
|
45
|
-
}
|
46
42
|
end
|
43
|
+
print_search_result(result.map{ |e| SearchEntry.new(*e) })
|
47
44
|
end
|
48
45
|
|
49
46
|
def help # :nodoc:
|
@@ -63,5 +60,51 @@ STAMP_PATTERN must be:
|
|
63
60
|
(e) date part only: "1030"
|
64
61
|
HELP
|
65
62
|
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Each entry of search result is:
|
67
|
+
#
|
68
|
+
# [<timestamp>, <line_number>, <matched_text>]
|
69
|
+
#
|
70
|
+
# The sort must be done in;
|
71
|
+
#
|
72
|
+
# - descending order for <timestamp>,
|
73
|
+
# - ascending ordier for <line_number>.
|
74
|
+
#
|
75
|
+
|
76
|
+
SearchEntry = Struct.new(:timestamp, :line_number, :matched_text) {
|
77
|
+
def timestamp_size
|
78
|
+
timestamp.to_s.size
|
79
|
+
end
|
80
|
+
|
81
|
+
def line_number_digits_size
|
82
|
+
line_number.to_s.size
|
83
|
+
end
|
84
|
+
}
|
85
|
+
|
86
|
+
def print_search_result(entries)
|
87
|
+
maxcol_stamp = entries.map(&:timestamp_size).max
|
88
|
+
maxcol_num = entries.map(&:line_number_digits_size).max
|
89
|
+
|
90
|
+
sort(entries).each { |e|
|
91
|
+
stamp_display = "%- *s" % [maxcol_stamp, e.timestamp]
|
92
|
+
num_display = "%*d" % [maxcol_num, e.line_number]
|
93
|
+
|
94
|
+
puts "#{stamp_display}: #{num_display}: #{e.matched_text}"
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def sort(search_result)
|
99
|
+
search_result.sort { |a, b|
|
100
|
+
stamp_comparison = (b.timestamp <=> a.timestamp)
|
101
|
+
if stamp_comparison == 0
|
102
|
+
a.line_number <=> b.line_number
|
103
|
+
else
|
104
|
+
stamp_comparison
|
105
|
+
end
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
66
109
|
end
|
67
110
|
end
|
@@ -18,7 +18,6 @@ module Rbnotes::Commands
|
|
18
18
|
# none of editors is available, the execution fails.
|
19
19
|
|
20
20
|
class Update < Command
|
21
|
-
include ::Rbnotes::Utils
|
22
21
|
|
23
22
|
def description # :nodoc:
|
24
23
|
"Update the content of a note"
|
@@ -44,8 +43,8 @@ module Rbnotes::Commands
|
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
47
|
-
target_stamp = Rbnotes
|
48
|
-
editor = find_editor(conf[:editor])
|
46
|
+
target_stamp = Rbnotes.utils.read_timestamp(args)
|
47
|
+
editor = Rbnotes.utils.find_editor(conf[:editor])
|
49
48
|
repo = Textrepo.init(conf)
|
50
49
|
|
51
50
|
text = nil
|
@@ -55,7 +54,7 @@ module Rbnotes::Commands
|
|
55
54
|
raise Rbnotes::MissingTimestampError, target_stamp
|
56
55
|
end
|
57
56
|
|
58
|
-
tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
|
57
|
+
tmpfile = Rbnotes.utils.run_with_tmpfile(editor, target_stamp.to_s, text)
|
59
58
|
text = File.readlines(tmpfile, :chomp => true)
|
60
59
|
|
61
60
|
unless text.empty?
|
@@ -83,7 +82,7 @@ module Rbnotes::Commands
|
|
83
82
|
def help # :nodoc:
|
84
83
|
puts <<HELP
|
85
84
|
usage:
|
86
|
-
#{Rbnotes::NAME} update [TIMESTAMP]
|
85
|
+
#{Rbnotes::NAME} update [-k|--keep] [TIMESTAMP]
|
87
86
|
|
88
87
|
Updates the content of the note associated with given timestamp.
|
89
88
|
|
data/lib/rbnotes/error.rb
CHANGED
@@ -7,10 +7,12 @@ module Rbnotes
|
|
7
7
|
# :stopdoc:
|
8
8
|
|
9
9
|
module ErrMsg
|
10
|
-
MISSING_ARGUMENT = "
|
11
|
-
MISSING_TIMESTAMP = "
|
10
|
+
MISSING_ARGUMENT = "Missing argument: %s"
|
11
|
+
MISSING_TIMESTAMP = "Missing timestamp: %s"
|
12
12
|
NO_EDITOR = "No editor is available: %s"
|
13
13
|
PROGRAM_ABORT = "External program was aborted: %s"
|
14
|
+
UNKNOWN_KEYWORD = "Unknown keyword: %s"
|
15
|
+
INVALID_TIMESTAMP_PATTERN = "Invalid timestamp pattern: %s"
|
14
16
|
end
|
15
17
|
|
16
18
|
# :startdoc:
|
@@ -53,4 +55,24 @@ module Rbnotes
|
|
53
55
|
super(ErrMsg::PROGRAM_ABORT % cmdline.join(" "))
|
54
56
|
end
|
55
57
|
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# An eeror raised when an unknown keyword was specified as a
|
61
|
+
# timestamp string pattern.
|
62
|
+
|
63
|
+
class UnknownKeywordError < Error
|
64
|
+
def initialize(keyword)
|
65
|
+
super(ErrMsg::UNKNOWN_KEYWORD % keyword)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# An error raised when an invalid timestamp pattern was specified.
|
71
|
+
|
72
|
+
class InvalidTimestampPatternError < Error
|
73
|
+
def initialize(pattern)
|
74
|
+
super(ErrMsg::INVALID_TIMESTAMP_PATTERN % pattern)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
56
78
|
end
|
data/lib/rbnotes/utils.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
+
require "singleton"
|
1
2
|
require "pathname"
|
2
3
|
require "tmpdir"
|
4
|
+
require "date"
|
5
|
+
require "io/console/size"
|
6
|
+
|
7
|
+
require "unicode/display_width"
|
3
8
|
|
4
9
|
module Rbnotes
|
5
10
|
##
|
6
11
|
# Defines several utility methods those are intended to be used in
|
7
12
|
# Rbnotes classes.
|
8
13
|
#
|
9
|
-
|
14
|
+
class Utils
|
15
|
+
include Singleton
|
10
16
|
|
11
17
|
##
|
12
18
|
# Finds a external editor program which is specified with the
|
@@ -26,7 +32,6 @@ module Rbnotes
|
|
26
32
|
def find_editor(preferred_editor)
|
27
33
|
find_program([preferred_editor, ENV["EDITOR"], "nano", "vi"].compact)
|
28
34
|
end
|
29
|
-
module_function :find_editor
|
30
35
|
|
31
36
|
##
|
32
37
|
# Finds a executable program in given names. When the executable
|
@@ -60,7 +65,6 @@ module Rbnotes
|
|
60
65
|
}
|
61
66
|
nil
|
62
67
|
end
|
63
|
-
module_function :find_program
|
64
68
|
|
65
69
|
##
|
66
70
|
# Executes the program with passing the given filename as argument.
|
@@ -84,7 +88,6 @@ module Rbnotes
|
|
84
88
|
raise ProgramAbortError, [prog, tmpfile] unless rc
|
85
89
|
tmpfile
|
86
90
|
end
|
87
|
-
module_function :run_with_tmpfile
|
88
91
|
|
89
92
|
##
|
90
93
|
# Generates a Textrepo::Timestamp object from a String which comes
|
@@ -98,7 +101,6 @@ module Rbnotes
|
|
98
101
|
str = args.shift || read_arg($stdin)
|
99
102
|
Textrepo::Timestamp.parse_s(str)
|
100
103
|
end
|
101
|
-
module_function :read_timestamp
|
102
104
|
|
103
105
|
##
|
104
106
|
# Reads an argument from the IO object. Typically, it is intended
|
@@ -119,7 +121,122 @@ module Rbnotes
|
|
119
121
|
nil
|
120
122
|
end
|
121
123
|
end
|
122
|
-
|
124
|
+
|
125
|
+
##
|
126
|
+
# Parses the given arguments and expand keywords if found. Each
|
127
|
+
# of the arguments is assumed to represent a timestamp pattern (or
|
128
|
+
# a keyword to be expand into several timestamp pattern). Returns
|
129
|
+
# an Array of timestamp partterns (each pattern is a String
|
130
|
+
# object).
|
131
|
+
#
|
132
|
+
# A timestamp pattern looks like:
|
133
|
+
#
|
134
|
+
# (a) full qualified timestamp (with suffix): "20201030160200"
|
135
|
+
# (b) year and date part: "20201030"
|
136
|
+
# (c) year and month part: "202010"
|
137
|
+
# (d) year part only: "2020"
|
138
|
+
# (e) date part only: "1030"
|
139
|
+
#
|
140
|
+
# KEYWORD:
|
141
|
+
#
|
142
|
+
# - "today" (or "to")
|
143
|
+
# - "yeasterday" (or "ye")
|
144
|
+
# - "this_week" (or "tw")
|
145
|
+
# - "last_week" (or "lw")
|
146
|
+
#
|
147
|
+
# :call-seq:
|
148
|
+
# expand_keyword_in_args(Array of Strings) -> Array of Strings
|
149
|
+
|
150
|
+
def expand_keyword_in_args(args)
|
151
|
+
return [nil] if args.empty?
|
152
|
+
|
153
|
+
patterns = []
|
154
|
+
while args.size > 0
|
155
|
+
arg = args.shift
|
156
|
+
if ["today", "to", "yesterday", "ye",
|
157
|
+
"this_week", "tw", "last_week", "lw"].include?(arg)
|
158
|
+
patterns.concat(Rbnotes.utils.expand_keyword(arg))
|
159
|
+
else
|
160
|
+
patterns << arg
|
161
|
+
end
|
162
|
+
end
|
163
|
+
patterns.sort.uniq
|
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
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# Makes a headline with the timestamp and subject of the notes, it
|
191
|
+
# looks like as follows:
|
192
|
+
#
|
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)
|
200
|
+
#
|
201
|
+
# The subject part will truncate when it is long.
|
202
|
+
|
203
|
+
def make_headline(timestamp, text)
|
204
|
+
_, column = IO.console_size
|
205
|
+
delimiter = ": "
|
206
|
+
timestamp_width = timestamp.to_s.size
|
207
|
+
subject_width = column - timestamp_width - delimiter.size - 1
|
208
|
+
|
209
|
+
subject = remove_heading_markup(text[0])
|
210
|
+
|
211
|
+
ts_part = "#{timestamp.to_s} "[0..(timestamp_width - 1)]
|
212
|
+
sj_part = truncate_str(subject, subject_width)
|
213
|
+
|
214
|
+
ts_part + delimiter + sj_part
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Finds all notes those timestamps match to given patterns in the
|
219
|
+
# given repository. Returns an Array contains Timestamp objects.
|
220
|
+
#
|
221
|
+
# :call-seq:
|
222
|
+
# find_notes(Array of timestamp patterns, Textrepo::Repository)
|
223
|
+
|
224
|
+
def find_notes(timestamp_patterns, repo)
|
225
|
+
timestamp_patterns.map { |pat|
|
226
|
+
repo.entries(pat)
|
227
|
+
}.flatten.sort{ |a, b| b <=> a }.uniq
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Enumerates all timestamp patterns in a week which contains a
|
232
|
+
# given timestamp as a day of the week.
|
233
|
+
#
|
234
|
+
# :call-seq:
|
235
|
+
# timestamp_patterns_in_week(timestamp) -> [Array of Strings]
|
236
|
+
|
237
|
+
def timestamp_patterns_in_week(timestamp)
|
238
|
+
dates_in_week(start_date_in_the_week(timestamp.time)).map { |date| timestamp_pattern(date) }
|
239
|
+
end
|
123
240
|
|
124
241
|
# :stopdoc:
|
125
242
|
|
@@ -132,11 +249,74 @@ module Rbnotes
|
|
132
249
|
}
|
133
250
|
found.compact[0]
|
134
251
|
end
|
135
|
-
module_function :search_in_path
|
136
252
|
|
137
253
|
def add_extension(basename)
|
138
254
|
"#{basename}.md"
|
139
255
|
end
|
140
|
-
|
256
|
+
|
257
|
+
def timestamp_pattern(date)
|
258
|
+
date.strftime("%Y%m%d")
|
259
|
+
end
|
260
|
+
|
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
|
+
def date(time)
|
270
|
+
Date.new(time.year, time.mon, time.day)
|
271
|
+
end
|
272
|
+
|
273
|
+
def dates_in_this_week
|
274
|
+
dates_in_week(start_date_in_this_week)
|
275
|
+
end
|
276
|
+
|
277
|
+
def dates_in_last_week
|
278
|
+
dates_in_week(start_date_in_last_week)
|
279
|
+
end
|
280
|
+
|
281
|
+
def start_date_in_this_week
|
282
|
+
start_date_in_the_week(Time.now)
|
283
|
+
end
|
284
|
+
|
285
|
+
def start_date_in_last_week
|
286
|
+
start_date_in_this_week.prev_day(7)
|
287
|
+
end
|
288
|
+
|
289
|
+
def start_date_in_the_week(time)
|
290
|
+
parts = [:year, :mon, :day].map { |sym| time.send(sym) }
|
291
|
+
Date.new(*parts).prev_day(wday(time))
|
292
|
+
end
|
293
|
+
|
294
|
+
def wday(time)
|
295
|
+
(time.wday - 1) % 7
|
296
|
+
end
|
297
|
+
|
298
|
+
def dates_in_week(start_date)
|
299
|
+
dates = [start_date]
|
300
|
+
1.upto(6) { |i| dates << start_date.next_day(i) }
|
301
|
+
dates
|
302
|
+
end
|
303
|
+
|
304
|
+
def truncate_str(str, size)
|
305
|
+
count = 0
|
306
|
+
result = ""
|
307
|
+
str.each_char { |c|
|
308
|
+
count += Unicode::DisplayWidth.of(c)
|
309
|
+
break if count > size
|
310
|
+
result << c
|
311
|
+
}
|
312
|
+
result
|
313
|
+
end
|
314
|
+
|
315
|
+
def remove_heading_markup(str)
|
316
|
+
str.sub(/^#+ +/, '')
|
317
|
+
end
|
318
|
+
|
319
|
+
# :startdoc:
|
320
|
+
|
141
321
|
end
|
142
322
|
end
|
data/lib/rbnotes/version.rb
CHANGED
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.
|
4
|
+
version: 0.4.10
|
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-
|
11
|
+
date: 2020-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: textrepo
|
@@ -63,11 +63,13 @@ files:
|
|
63
63
|
- lib/rbnotes.rb
|
64
64
|
- lib/rbnotes/commands.rb
|
65
65
|
- lib/rbnotes/commands/add.rb
|
66
|
+
- lib/rbnotes/commands/commands.rb
|
66
67
|
- lib/rbnotes/commands/delete.rb
|
67
68
|
- lib/rbnotes/commands/export.rb
|
68
69
|
- lib/rbnotes/commands/help.rb
|
69
70
|
- lib/rbnotes/commands/import.rb
|
70
71
|
- lib/rbnotes/commands/list.rb
|
72
|
+
- lib/rbnotes/commands/pick.rb
|
71
73
|
- lib/rbnotes/commands/search.rb
|
72
74
|
- lib/rbnotes/commands/show.rb
|
73
75
|
- lib/rbnotes/commands/update.rb
|