rbnotes 0.4.5 → 0.4.10
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/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
|