rbnotes 0.3.1 → 0.4.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cb54d2116edee3136979232f749434f0b4c068fa5162321bd02dfc96fcd8ece
4
- data.tar.gz: 1d3d61b2cafa2d7a2825deb764ee232729e0a7bc8afa51868dbd98db2b7c016f
3
+ metadata.gz: a04bd4d45d7c2f4227f355672baa860602ae3b9e481e7a384daf4283f4ad6754
4
+ data.tar.gz: bc707b20ea91ab655f6e355026164c01844c82ea3869c18fd5ca1040c968934d
5
5
  SHA512:
6
- metadata.gz: 07a40884e1f0a73bc4b3d70104d98d56814084ce643b86715cf739ece89124da45236cccc4ba881f78e176b8ee318eceaa5974811e4f990d81dce17a018b8a74
7
- data.tar.gz: fd70d8ed8d461610cf0f60fe506588414abbd2f9a6fe9deabe8de49ca7a3146d49efcb799b25bb4f66158afc4298643993f1d2a3a7e21d095efe2e7462d8792f
6
+ metadata.gz: d93df0df9e002a8bae26b7d9e1e89acf19d84551635d58f3ec747c78f3764ac004a784977a3d9940783a8b4661b2caafd2b55502644bca4bb69947d91593bd79
7
+ data.tar.gz: 913c240b04215cb5502146f4d1edcd5c1ddc273de31baace3f0d8dff600dcf759e5983aee5cce72fb5079dc26e1a22b43c0aad46e1cc766c4127697090fb0037
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
  ## [Unreleased]
8
8
  Nothing to record here.
9
9
 
10
+ ## [0.4.4] - 2020-11-09
11
+ ###
12
+ - Add a feature to use a keyword as an argument for `list`. (#47)
13
+
14
+ ## [0.4.3] - 2020-11-08
15
+ ### Added
16
+ - Add a new command `export` to write out a note into a file. (#51)
17
+ - Add individual help for each command. (#42)
18
+
19
+ ### Fixed
20
+ - Fix `add` fails without modification (#48)
21
+
22
+ ## [0.4.2] - 2020-11-05
23
+ ### Added
24
+ - Add a feature to keep the timestamp in `update` command. (#44)
25
+
26
+ ### Fixed
27
+ - Fix issue #45: hanging up of `add` command.
28
+
29
+ ## [0.4.1] - 2020-11-04
30
+ ### Added
31
+ - Add a feature to accept a timestamp in `add` command. (#34)
32
+
33
+ ## [0.4.0] - 2020-11-03
34
+ ### Added
35
+ - Add a new command `search` to perform full text search. (#33)
36
+
10
37
  ## [0.3.1] - 2020-10-30
11
38
  ### Added
12
39
  - Add feature to specify configuration file in the command line. (#21)
data/Gemfile CHANGED
@@ -6,4 +6,4 @@ gemspec
6
6
  gem "rake", "~> 13.0"
7
7
  gem "minitest", "~> 5.0"
8
8
 
9
- gem 'textrepo', "~> 0.4"
9
+ gem 'textrepo', "~> 0.5.4"
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbnotes (0.3.1)
5
- textrepo (~> 0.4)
4
+ rbnotes (0.4.4)
5
+ textrepo (~> 0.5.4)
6
6
  unicode-display_width (~> 1.7)
7
7
 
8
8
  GEM
@@ -10,7 +10,7 @@ GEM
10
10
  specs:
11
11
  minitest (5.14.2)
12
12
  rake (13.0.1)
13
- textrepo (0.4.5)
13
+ textrepo (0.5.4)
14
14
  unicode-display_width (1.7.0)
15
15
 
16
16
  PLATFORMS
@@ -20,7 +20,7 @@ DEPENDENCIES
20
20
  minitest (~> 5.0)
21
21
  rake (~> 13.0)
22
22
  rbnotes!
23
- textrepo (~> 0.4)
23
+ textrepo (~> 0.5.4)
24
24
 
25
25
  BUNDLED WITH
26
26
  2.1.4
data/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  Rbnotes is a simple utility to write a note in the single repository.
6
6
 
7
+ This document provides the basic information to use rbnotes.
8
+ You may find more useful information in [Wiki pages](https://github.com/mnbi/rbnotes/wiki).
9
+
7
10
  ## Installation
8
11
 
9
12
  Add this line to your application's Gemfile:
@@ -39,6 +42,8 @@ rbnotes [global_opts] [command] [command_opts] [args]
39
42
  - imports existing files
40
43
  - list
41
44
  - lists notes in the repository with their timestamps and subject
45
+ - search
46
+ - search a word (or words) in the repository
42
47
  - show
43
48
  - shows the content of a note
44
49
  - add
@@ -145,6 +150,38 @@ The short-hand notation of the home directory ("~") is usable.
145
150
 
146
151
  - :pager : specify a pager program
147
152
  - :editor : specify a editor program
153
+ - :searcher: specify a program to perform search
154
+ - :searcher_options: specify options to pass to the searcher program
155
+
156
+ Be careful to set `:searcher` and `:searcher_options`. The searcher
157
+ program must be expected to behave equivalent to `grep` with `-inRE`.
158
+ At least, its output must be the same format and it must runs
159
+ recursively to a directory.
160
+
161
+ If your favorite searcher is one of the followings, see the default
162
+ options which `textrepo` sets for those searchers. In most cases, you
163
+ don't have to set `:searcher_options` for them.
164
+
165
+ | searcher | default options in `textrepo` |
166
+ |:---------|:---------------------------------------------------|
167
+ | `grep` | `["-i", "-n", "-R", "-E"]` |
168
+ | `egrep` | `["-i", "-n", "-R"]` |
169
+ | `ggrep` | `["-i", "-n", "-R", "-E"]` |
170
+ | `gegrep` | `["-i", "-n", "-R"]` |
171
+ | `rg` | `["-S", "-n", "--no-heading", "--color", "never"]` |
172
+
173
+ Those searcher names are used in macOS (with Homebrew). Any other OS
174
+ might use different names.
175
+
176
+ - `grep` and `egrep` -> BSD grep (macOS default)
177
+ - `ggrep` and `gegrep` -> GNU grep
178
+ - `rg` -> ripgrep
179
+
180
+ If the name is different, `:searcher_options` should be set with the
181
+ same value. For example, if you system use the name `gnugrep` as GNU
182
+ grep, and you want to use it as the searcher (that is, set `gnugrep`
183
+ to `:searcher`), you should set `:searcher_options` value with `["-i",
184
+ "-n", "-R", "-E"]`.
148
185
 
149
186
  ##### Default values for mandatory variables
150
187
 
@@ -4,8 +4,6 @@ require "rbnotes"
4
4
 
5
5
  include Rbnotes
6
6
 
7
- DEBUG = true
8
-
9
7
  class App
10
8
  def initialize
11
9
  @gopts = {}
@@ -25,6 +23,14 @@ class App
25
23
  file = File.expand_path(file)
26
24
  raise ArgumentError, "no such file: %s" % file unless FileTest.exist?(file)
27
25
  @gopts[:conf_file] = file
26
+ when "-v", "--version"
27
+ args.clear
28
+ args.unshift("version")
29
+ break
30
+ when "-h", "--help"
31
+ args.clear
32
+ args.unshift("help")
33
+ break
28
34
  else
29
35
  args.unshift(arg)
30
36
  break
@@ -45,7 +51,8 @@ begin
45
51
  rescue MissingArgumentError, MissingTimestampError,
46
52
  NoEditorError, ProgramAbortError,
47
53
  Textrepo::InvalidTimestampStringError,
48
- ArgumentError => e
54
+ ArgumentError,
55
+ Errno::EACCES => e
49
56
  puts e.message
50
57
  exit 1
51
58
  end
@@ -1,6 +1,8 @@
1
1
  require "textrepo"
2
2
 
3
3
  module Rbnotes
4
+ NAME = File.basename($PROGRAM_NAME) # :nodoc:
5
+
4
6
  require_relative "rbnotes/version"
5
7
  require_relative "rbnotes/error"
6
8
  require_relative "rbnotes/conf"
@@ -1,14 +1,21 @@
1
1
  module Rbnotes
2
+
2
3
  ##
3
4
  # This module defines all command classes of rbnotes. Each command
4
5
  # class must be derived from Rbnotes::Commands::Command class.
5
6
 
6
7
  module Commands
8
+
7
9
  ##
8
10
  # The base class for a command class.
9
11
 
10
12
  class Command
11
13
 
14
+ ##
15
+ # Short description of each command.
16
+
17
+ def description; nil; end
18
+
12
19
  ##
13
20
  # :call-seq:
14
21
  # execute(Array, Hash) -> nil
@@ -17,8 +24,17 @@ module Rbnotes
17
24
  # - Hash : rbnotes configuration
18
25
 
19
26
  def execute(args, conf)
20
- Builtins::DEFAULT_CMD.new.execute(args, conf)
27
+ Builtins.default_cmd.new.execute(args, conf)
28
+ end
29
+
30
+ ##
31
+ # Shows the help message for the command.
32
+ #
33
+
34
+ def help
35
+ Builtins::Usage.new.execute(nil, nil)
21
36
  end
37
+
22
38
  end
23
39
 
24
40
  # :stopdoc:
@@ -29,54 +45,73 @@ module Rbnotes
29
45
  # - stamp: converts given TIME_STR into a timestamp.
30
46
  # - time: converts given STAMP into a time string.
31
47
  module Builtins
32
- class Help < Command
33
- def execute(_, _)
34
- puts <<USAGE
35
- usage: rbnotes [-c|--conf CONF_FILE] [command] [args]
48
+ class Usage < Command
36
49
 
37
- command:
38
- add : create a new note
39
- import FILE : import a FILE into the repository
50
+ def description
51
+ "Print usage"
52
+ end
40
53
 
41
- list PATTERN : list notes those timestamp matches PATTERN
54
+ def execute(_, _)
55
+ puts <<USAGE
56
+ Syntax:
57
+ #{Rbnotes::NAME} [-c| --conf CONF_FILE] [command] [args]
42
58
 
43
- PATTERN must be:
44
- (a) full qualified timestamp (with suffix): "20201030160200"
45
- (b) year and date part: "20201030"
46
- (c) year part only: "2020"
47
- (d) date part only: "1030"
59
+ Example usage:
60
+ #{Rbnotes::NAME} add [-t STAMP_PATTERN]
61
+ #{Rbnotes::NAME} delete [TIMESTAMP]
62
+ #{Rbnotes::NAME} export [TIMESTAMP [FILENAME]]
63
+ #{Rbnotes::NAME} import FILE
64
+ #{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
65
+ #{Rbnotes::NAME} search PATTERN [STAMP_PATTERN]
66
+ #{Rbnotes::NAME} show [TIMESTAMP]
67
+ #{Rbnotes::NAME} update [TIMESTAMP]
48
68
 
49
- show STAMP : show the note specified with STAMP
50
- update STAMP : edit the note with external editor
51
- delete STAMP : delete the note specified with STAMP
69
+ Further help for each command:
70
+ #{Rbnotes::NAME} help commands
71
+ #{Rbnotes::NAME} help [COMMAND]
52
72
 
53
- STAMP must be a sequence of digits to represent year, date and
54
- time (and suffix), such "20201030160200" or "20201030160200_012".
73
+ Further information:
74
+ https://github.com/mnbi/rbnotes/wiki
55
75
 
56
- show/update/delete reads its argument from the standard input when
57
- no argument was passed in the command line.
76
+ USAGE
77
+ end
58
78
 
59
- version : print version
60
- help : show help
79
+ def help
80
+ puts <<HELP_USAGE
81
+ usage:
82
+ #{Rbnotes::NAME} usage
61
83
 
62
- commands for development purpose:
63
- conf : print the current configuraitons
64
- repo : print the repository path
65
- stamp TIME_STR : convert TIME_STR into a timestamp
66
- time STAMP : convert STAMP into a time string
67
- USAGE
84
+ Print a short example of usage.
85
+ HELP_USAGE
68
86
  end
69
87
  end
70
88
 
71
89
  class Version < Command
90
+ def description
91
+ "Print version"
92
+ end
93
+
72
94
  def execute(_, _)
73
- rbnotes_version = "rbnotes #{Rbnotes::VERSION} (#{Rbnotes::RELEASE})"
95
+ rbnotes_version = "#{Rbnotes::NAME} #{Rbnotes::VERSION} (#{Rbnotes::RELEASE})"
74
96
  textrepo_version = "textrepo #{Textrepo::VERSION}"
75
97
  puts "#{rbnotes_version} [#{textrepo_version}]"
76
98
  end
99
+
100
+ def help
101
+ puts <<VERSION
102
+ usage:
103
+ #{Rbnotes::NAME} version
104
+
105
+ Print version of #{Rbnotes::NAME} and release date.
106
+ VERSION
107
+ end
77
108
  end
78
109
 
79
110
  class Repo < Command
111
+ def description
112
+ "Print repository path"
113
+ end
114
+
80
115
  def execute(_, conf)
81
116
  name = conf[:repository_name]
82
117
  base = conf[:repository_base]
@@ -89,19 +124,49 @@ USAGE
89
124
  File.join(base, name)
90
125
  end
91
126
  end
127
+
128
+ def help
129
+ puts <<REPO
130
+ usage:
131
+ #{Rbnotes::NAME} repo
132
+
133
+ Print the path of the repository. The type of the path entity depends
134
+ on what type is specified to the repository type in the configuration.
135
+ When ":file_system" is set to "repository_type", the path is a
136
+ directory which contains all note files. The structure of the
137
+ directory depends on the implementation of `textrepo`.
138
+ REPO
139
+ end
92
140
  end
93
141
 
94
142
  class Conf < Command
143
+ def description
144
+ "Print the current configuration"
145
+ end
146
+
95
147
  def execute(_, conf)
96
148
  conf.keys.sort.each { |k|
97
149
  puts "#{k}=#{conf[k]}"
98
150
  }
99
151
  end
152
+
153
+ def help
154
+ puts <<CONF
155
+ usage:
156
+ #{Rbnotes::NAME} conf
157
+
158
+ Print the current configuration values.
159
+ CONF
160
+ end
100
161
  end
101
162
 
102
163
  require "time"
103
164
 
104
165
  class Stamp < Command
166
+ def description
167
+ "Convert a time string into a timestamp string"
168
+ end
169
+
105
170
  def execute(args, _)
106
171
  time_str = args.shift
107
172
  unless time_str.nil?
@@ -111,9 +176,30 @@ USAGE
111
176
  super
112
177
  end
113
178
  end
179
+
180
+ def help
181
+ puts <<STAMP
182
+ usage:
183
+ #{Rbnotes::NAME} stamp
184
+
185
+ Convert a given time string into a timestamp string. The timestamp
186
+ string could be used as an argument of some rbnotes commands, such
187
+ "show". Here is short example of conversion:
188
+
189
+ "2020-11-06 16:51:15" -> "20201106165115"
190
+ "2020-11-06" -> "20201106000000"
191
+ "20201106" -> "20201106000000"
192
+ "2020-11-06 16" -> "20201106160000"
193
+ "2020-11-06 16:51" -> "20201106165100"
194
+ STAMP
195
+ end
114
196
  end
115
197
 
116
198
  class Time < Command
199
+ def description
200
+ "Convert a timestamp into a time string"
201
+ end
202
+
117
203
  def execute(args, _)
118
204
  stamp = args.shift
119
205
  unless stamp.nil?
@@ -123,12 +209,41 @@ USAGE
123
209
  super
124
210
  end
125
211
  end
212
+
213
+ def help
214
+ puts <<TIME
215
+ usage:
216
+ #{Rbnotes::NAME} time
217
+
218
+ Convert a given timestamp string into a time string. Here is short
219
+ example of conversion:
220
+
221
+ "20201106165115" -> "2020-11-06 16:51:15 +0900"
222
+ "202011061651" -> "2020-11-06 16:51:00 +0900"
223
+ "2020110616" -> "2020-11-06 16:00:00 +0900"
224
+ "20201106" -> "2020-11-06 00:00:00 +0900"
225
+ TIME
226
+ end
126
227
  end
127
228
 
128
- DEFAULT_CMD = Help
129
- end
229
+ class << self
230
+ def default_cmd_name
231
+ "usage"
232
+ end
130
233
 
131
- DEFAULT_CMD_NAME = "help"
234
+ def default_cmd
235
+ Usage
236
+ end
237
+
238
+ def command(name)
239
+ begin
240
+ const_defined?(name, false) ? const_get(name, false) : nil
241
+ rescue NameError => _
242
+ nil
243
+ end
244
+ end
245
+ end
246
+ end
132
247
 
133
248
  # :startdoc:
134
249
 
@@ -139,24 +254,28 @@ USAGE
139
254
  # of the class.
140
255
  #
141
256
  # :call-seq:
257
+ # load("add") -> Rbnotes::Commands::Add
258
+ # load("delete") -> Rbnotes::Commands::Delete
259
+ # load("export") -> Rbnotes::Commands::Export
260
+ # load("help") -> Rbnotes::Commands::Help
142
261
  # load("import") -> Rbnotes::Commnads::Import
143
262
  # load("list") -> Rbnotes::Commands::List
263
+ # load("search") -> Rbnotes::Commands::Search
144
264
  # load("show") -> Rbnotes::Commands::Show
265
+ # load("update") -> Rbnotes::Commands::Update
145
266
 
146
267
  def load(cmd_name)
147
- cmd_name ||= DEFAULT_CMD_NAME
268
+ cmd_name ||= Builtins.default_cmd_name
148
269
  klass_name = cmd_name.capitalize
149
270
 
150
- klass = nil
151
- if Builtins.const_defined?(klass_name, false)
152
- klass = Builtins::const_get(klass_name, false)
153
- else
271
+ klass = Builtins.command(klass_name)
272
+ if klass.nil?
154
273
  begin
155
274
  require_relative "commands/#{cmd_name}"
156
275
  klass = const_get(klass_name, false)
157
276
  rescue LoadError => _
158
277
  STDERR.puts "unknown command: #{cmd_name}"
159
- klass = Builtins::DEFAULT_CMD
278
+ klass = Builtins.default_cmd
160
279
  end
161
280
  end
162
281
  klass.new
@@ -1,9 +1,20 @@
1
1
  module Rbnotes::Commands
2
+
2
3
  ##
3
- # Adds a new note to the repository. A new timestamp is generated
4
- # at the execution time, then it is attached to the note. If the
5
- # timestamp has already existed in the repository, the command
6
- # fails.
4
+ # Adds a new note to the repository. If no options, a new timestamp
5
+ # is generated at the execution time, then it is attached to the
6
+ # note. If the timestamp has already existed in the repository, the
7
+ # command fails.
8
+ #
9
+ # Accepts an option with `-t STAMP_PATTERN` (or `--timestamp`), a
10
+ # timestamp is generated according to `STAMP_PATTERN`.
11
+ #
12
+ # STAMP_PATTERN could be one of followings:
13
+ #
14
+ # "20201104172230_078" : full qualified timestamp string
15
+ # "20201104172230" : full qualified timestamp string (no suffix)
16
+ # "202011041722" : year, date and time (omit second part)
17
+ # "11041722" : date and time (omit year and second part)
7
18
  #
8
19
  # This command starts the external editor program to prepare text to
9
20
  # store. The editor program will be searched in the following order:
@@ -14,23 +25,48 @@ module Rbnotes::Commands
14
25
  # 4. "vi"
15
26
  #
16
27
  # If none of the above editor is available, the command fails.
17
- #
28
+
18
29
  class Add < Command
19
30
  include ::Rbnotes::Utils
20
31
 
32
+ def description # :nodoc:
33
+ "Add a new note"
34
+ end
35
+
21
36
  def execute(args, conf)
22
- newstamp = Textrepo::Timestamp.new(Time.now)
37
+ @opts = {}
38
+ while args.size > 0
39
+ arg = args.shift
40
+ case arg
41
+ when "-t", "--timestamp"
42
+ stamp_str = args.shift
43
+ raise ArgumentError, "missing timestamp: %s" % args.unshift(arg) if stamp_str.nil?
44
+ stamp_str = complement_timestamp_pattern(stamp_str)
45
+ @opts[:timestamp] = Textrepo::Timestamp.parse_s(stamp_str)
46
+ else
47
+ args.unshift(arg)
48
+ break
49
+ end
50
+ end
51
+
52
+ stamp = @opts[:timestamp] || Textrepo::Timestamp.new(Time.now)
23
53
 
24
54
  candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
25
55
  editor = find_program(candidates)
26
56
  raise Rbnotes::NoEditorError, candidates if editor.nil?
27
57
 
28
- tmpfile = run_with_tmpfile(editor, newstamp.to_s)
58
+ tmpfile = run_with_tmpfile(editor, stamp.to_s)
59
+
60
+ unless FileTest.exist?(tmpfile)
61
+ puts "Cancel adding, since nothing to store"
62
+ return
63
+ end
64
+
29
65
  text = File.readlines(tmpfile)
30
66
 
31
67
  repo = Textrepo.init(conf)
32
68
  begin
33
- repo.create(newstamp, text)
69
+ repo.create(stamp, text)
34
70
  rescue Textrepo::DuplicateTimestampError => e
35
71
  puts e.message
36
72
  puts "Just wait a second, then retry."
@@ -39,11 +75,58 @@ module Rbnotes::Commands
39
75
  rescue StandardError => e
40
76
  puts e.message
41
77
  else
42
- puts "Add a note [%s]" % newstamp.to_s
78
+ puts "Add a note [%s]" % stamp.to_s
43
79
  ensure
44
80
  # Don't forget to remove the temporary file.
45
81
  File.delete(tmpfile)
46
82
  end
47
83
  end
84
+
85
+ def help # :nodoc:
86
+ puts <<HELP
87
+ usage:
88
+ #{Rbnotes::NAME} add [(-t|--timestamp) STAMP_PATTERN]
89
+
90
+ Add a new note to the repository. If no options, a new timestamp is
91
+ generated at the execution time, then it is attached to the note.
92
+
93
+ Accept an option with `-t STAMP_PATTERN` (or `--timestamp`), a
94
+ timestamp is generated according to `STAMP_PATTERN`.
95
+
96
+ STAMP_PATTERN could be one of followings:
97
+
98
+ "20201104172230_078" : full qualified timestamp string
99
+ "20201104172230" : full qualified timestamp string (no suffix)
100
+ "202011041722" : year, date and time (omit second part)
101
+ "11041722" : date and time (omit year and second part)
102
+
103
+ This command starts the external editor program to prepare text to
104
+ store. The editor program will be searched in the following order:
105
+
106
+ 1. configuration setting of ":editor"
107
+ 2. ENV["EDITOR"]
108
+ 3. "nano"
109
+ 4. "vi"
110
+
111
+ If none of the above editor is available, the execution fails.
112
+ HELP
113
+ end
114
+
115
+ # :stopdoc:
116
+ private
117
+ def complement_timestamp_pattern(pattern)
118
+ stamp_str = nil
119
+ case pattern.to_s.size
120
+ when "yyyymoddhhmiss_lll".size, "yyyymoddhhmiss".size
121
+ stamp_str = pattern.dup
122
+ when "yyyymoddhhmi".size # omit sec part
123
+ stamp_str = "#{pattern}00"
124
+ when "moddhhmi".size # omit year and sec part
125
+ stamp_str = "#{Time.now.year}#{pattern}00"
126
+ else
127
+ raise Textrepo::InvalidTimestampStringError, pattern
128
+ end
129
+ stamp_str
130
+ end
48
131
  end
49
132
  end
@@ -1,18 +1,17 @@
1
- # :markup: markdown
1
+ module Rbnotes::Commands
2
2
 
3
- # Delete command deletes one note in the repository, which specified
4
- # with a given timestamp string. The timestamp string must be a fully
5
- # qualified one, like "20201016165130". The argument to specify a
6
- # note is mandatory. If no argument was passed, it would print help
7
- # message and exit.
8
- #
9
- # It does nothing when the specified note does not exist except to
10
- # print error message.
3
+ # Deletes a given note in the repository. The timestamp string must
4
+ # be a fully qualified one, like "20201016165130". If no argument
5
+ # was passed, it would try to read from the standard input.
6
+ #
7
+ # It does nothing to change the repository when the specified note
8
+ # does not exist.
11
9
 
12
- # :stopdoc:
10
+ class Delete < Command
11
+ def description # :nodoc:
12
+ "Delete a note"
13
+ end
13
14
 
14
- module Rbnotes
15
- class Commands::Delete < Commands::Command
16
15
  def execute(args, conf)
17
16
  stamp = Rbnotes::Utils.read_timestamp(args)
18
17
 
@@ -27,5 +26,18 @@ module Rbnotes
27
26
  puts "Delete [%s]" % stamp.to_s
28
27
  end
29
28
  end
29
+
30
+ def help # :nodoc:
31
+ puts <<HELP
32
+ usage:
33
+ #{Rbnotes::NAME} delete [TIMESTAMP]
34
+
35
+ Delete a given note. TIMESTAMP must be a fully qualified one, such
36
+ "20201016165130" or "20201016165130_012" if it has a suffix.
37
+
38
+ Delete reads its argument from the standard input when no argument was
39
+ passed in the command line.
40
+ HELP
41
+ end
30
42
  end
31
43
  end
@@ -0,0 +1,58 @@
1
+ require "pathname"
2
+
3
+ module Rbnotes::Commands
4
+
5
+ ##
6
+ # Writes out a given note into a specified file. The file will be
7
+ # created in the current working directory unless an absolute path
8
+ # is specified as a filename.
9
+ #
10
+ # When no argument was passed, would try to read a timestamp string
11
+ # from the standard input.
12
+
13
+ class Export < Command
14
+
15
+ def description # :nodoc:
16
+ "Write out a note into a file"
17
+ end
18
+
19
+ #
20
+ # :call-seq:
21
+ # execute([a String as timestring], Rbnotes::Conf or Hash) -> nil
22
+
23
+ def execute(args, conf)
24
+ stamp = Rbnotes::Utils.read_timestamp(args)
25
+
26
+ repo = Textrepo.init(conf)
27
+ begin
28
+ content = repo.read(stamp)
29
+ rescue Textrepo::MissingTimestampError => _
30
+ raise MissingTimestampError, stamp
31
+ end
32
+
33
+ pathname = Pathname.new(args.shift || "#{stamp}.md")
34
+ pathname.parent.mkpath
35
+ pathname.open("w"){ |f| f.puts content }
36
+ puts "Export a note [%s] into a file [%s]" % [stamp, pathname]
37
+ end
38
+
39
+ def help # :nodoc:
40
+ puts <<HELP
41
+ usage:
42
+ #{Rbnotes::NAME} export [TIMESTAMP [FILENAME]]
43
+
44
+ Write out a given note into a specified file. TIMESTAMP must be a
45
+ fully qualified one, such "20201108141600", or "20201108141600_012" if
46
+ it has a suffix. FILENAME is optional. When it omitted, the filename
47
+ would be a timestamp string with ".md" as its extension, such
48
+ "20201108141600.md"
49
+
50
+ The file will be created into the current working directory unless an
51
+ absolute path is specified as FILENAME.
52
+
53
+ When no argument was passed, it would try to read a timestamp string
54
+ from the standard input. Then, FILENAME would be regarded as omitted.
55
+ HELP
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,98 @@
1
+ module Rbnotes::Commands
2
+
3
+ ##
4
+ # Shows help message for the command which specifies with the
5
+ # argument.
6
+
7
+ class Help < Command
8
+
9
+ def description # :nodoc:
10
+ "Provide help on each command"
11
+ end
12
+
13
+ ##
14
+ # :call-seq:
15
+ # execute(["add"], Rbnotes::Conf or Hash) -> nil
16
+ # execute(["delete"], Rbnotes::Conf or Hash) -> nil
17
+
18
+ def execute(args, conf)
19
+ cmd_name = args.shift
20
+ case cmd_name
21
+ when nil
22
+ self.help
23
+ when "commands"
24
+ print_commands
25
+ else
26
+ Commands.load(cmd_name).help
27
+ end
28
+ end
29
+
30
+ def help # :nodoc:
31
+ puts <<HELP
32
+ #{Rbnotes::NAME.capitalize} is a simple tool to write a note into a single repository.
33
+
34
+ When creates a new note, a timestamp is attached to the note. #{Rbnotes::NAME.capitalize}
35
+ manages notes with those timestamps, such update, delete, ...etc.
36
+
37
+ Timestamp is a series of digits which represents year, date, and time.
38
+ It looks like "20201106121100", means "2020-11-06 12:11:00". It is
39
+ generated in the local time.
40
+
41
+ usage:
42
+ #{Rbnotes::NAME} [option] [command] [args]
43
+
44
+ option:
45
+ -c, --conf [CONF_FILE] : specifiy the configuration file
46
+ -v, --version : print version
47
+ -h, --help : show this message
48
+
49
+ CONF_FILE must be written in YAML. To know about details of the
50
+ configuration file, see README.md or Wiki page.
51
+
52
+ Further help:
53
+ #{Rbnotes::NAME} help commands
54
+ #{Rbnotes::NAME} help COMMAND
55
+ #{Rbnotes::NAME} usage
56
+
57
+ Further information:
58
+ https://github.com/mnbi/rbnotes/wiki
59
+ HELP
60
+ end
61
+
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
+ end
98
+ end
@@ -1,5 +1,29 @@
1
- module Rbnotes
2
- class Commands::Import < Commands::Command
1
+ module Rbnotes::Commands
2
+
3
+ ##
4
+ # Imports a existing file which specified by the argument as a note.
5
+ #
6
+ # A timestamp is generated referring to the birthtime of the given
7
+ # file. If birthtime is not available on the system, use mtime
8
+ # (modification time).
9
+ #
10
+ # Occasionally, there is another note which has the same timestmap
11
+ # in the repository. Then, tries to create a new timestamp with a
12
+ # suffix. Unluckily, when such timestamp with a suffix already
13
+ # exists, tries to create a new one with increasing suffix. Suffix
14
+ # will be "001", "002", ..., or "999". In worst case, all suffix
15
+ # might have been already used. Then, abandons to import.
16
+
17
+ class Import < Command
18
+
19
+ def description # :nodoc:
20
+ "Import a file as a note"
21
+ end
22
+
23
+ ##
24
+ # :call-seq:
25
+ # execute([PATHNAME], Rbnotes::Conf or Hash) -> nil
26
+
3
27
  def execute(args, conf)
4
28
  file = args.shift
5
29
  unless file.nil?
@@ -58,5 +82,18 @@ module Rbnotes
58
82
  super
59
83
  end
60
84
  end
85
+
86
+ def help # :nodoc:
87
+ puts <<HELP
88
+ usage:
89
+ #{Rbnotes::NAME} import FILE
90
+
91
+ Imports a existing file which specified by the argument as a note.
92
+
93
+ A timestamp is generated referring to the birthtime of the given file.
94
+ If birthtime is not available on the system, use mtime (modification
95
+ time).
96
+ HELP
97
+ end
61
98
  end
62
99
  end
@@ -1,22 +1,27 @@
1
+ require "date"
1
2
  require "unicode/display_width"
2
3
  require "io/console/size"
3
4
 
4
- module Rbnotes
5
+ module Rbnotes::Commands
6
+
5
7
  ##
6
8
  # Defines `list` command for `rbnotes`. See the document of execute
7
9
  # method to know about the behavior of this command.
8
10
 
9
- class Commands::List < Commands::Command
11
+ class List < Command
12
+
13
+ def description # :nodoc:
14
+ "List notes"
15
+ end
10
16
 
11
17
  ##
12
- # Shows the list of notes in the repository. The only argument is
18
+ # Shows a list of notes in the repository. The only argument is
13
19
  # optional. If it passed, it must be an timestamp pattern. A
14
20
  # timestamp is an instance of Textrepo::Timestamp class. A
15
21
  # timestamp pattern is a string which would match several
16
22
  # Timestamp objects.
17
23
  #
18
- # Here is
19
- # several examples of timestamp patterns.
24
+ # Here is several examples of timestamp patterns.
20
25
  #
21
26
  # "20201027093600_012": a complete string to represent a timestamp
22
27
  # - this pattern would match exactly one Timestamp object
@@ -24,6 +29,9 @@ module Rbnotes
24
29
  # "20201027": specifies year and date
25
30
  # - all Timestamps those have the same year and date would match
26
31
  #
32
+ # "202011": specifies year and month
33
+ # - all Timestamps those have the same year and month would match
34
+ #
27
35
  # "2020": specifies year only
28
36
  # - all Timestamps those have the same year would match
29
37
  #
@@ -35,16 +43,61 @@ module Rbnotes
35
43
  # execute(Array, Rbnotes::Conf or Hash) -> nil
36
44
 
37
45
  def execute(args, conf)
38
- pattern = args.shift # `nil` is acceptable
46
+ arg = args.shift
47
+ patterns = []
48
+
49
+ case arg.to_s
50
+ when "today", "to"
51
+ patterns << Textrepo::Timestamp.new(Time.now).to_s[0..7]
52
+ when "yesterday", "ye"
53
+ t = Time.now
54
+ patterns << Date.new(t.year, t.mon, t.day).prev_day.strftime("%Y%m%d")
55
+ when "this_week", "tw"
56
+ patterns.concat(dates_in_this_week)
57
+ when "last_week", "lw"
58
+ patterns.concat(dates_in_last_week)
59
+ else
60
+ patterns << arg
61
+ end
39
62
 
40
63
  @repo = Textrepo.init(conf)
64
+ stamps = patterns.map { |pat|
65
+ @repo.entries(pat).sort{|a, b| b <=> a}
66
+ }.flatten
41
67
  # newer stamp shoud be above
42
- stamps = @repo.entries(pattern).sort{|a, b| b <=> a}
43
68
  stamps.each { |timestamp|
44
69
  puts make_headline(timestamp)
45
70
  }
46
71
  end
47
72
 
73
+ def help # :nodoc:
74
+ puts <<HELP
75
+ usage:
76
+ #{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
77
+
78
+ Show a list of notes. When no arguments, make a list with all notes
79
+ in the repository. When specified STAMP_PATTERN, only those match the
80
+ pattern are listed. Instead of STAMP_PATTERN, some KEYWORDs could be
81
+ used.
82
+
83
+ STAMP_PATTERN must be:
84
+
85
+ (a) full qualified timestamp (with suffix): "20201030160200"
86
+ (b) year and date part: "20201030"
87
+ (c) year and month part: "202010"
88
+ (d) year part only: "2020"
89
+ (e) date part only: "1030"
90
+
91
+ KEYWORD:
92
+
93
+ - "today" (or "to")
94
+ - "yeasterday" (or "ye")
95
+ - "this_week" (or "tw")
96
+ - "last_week" (or "lw")
97
+
98
+ HELP
99
+ end
100
+
48
101
  # :stopdoc:
49
102
 
50
103
  private
@@ -92,6 +145,29 @@ module Rbnotes
92
145
  str.sub(/^#+ +/, '')
93
146
  end
94
147
 
148
+ # week day for Monday start calendar
149
+ def wday(time)
150
+ (time.wday - 1) % 7
151
+ end
152
+
153
+ def dates_in_this_week
154
+ to = Time.now
155
+ start = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
156
+ dates_in_week(start)
157
+ end
158
+
159
+ def dates_in_last_week
160
+ to = Time.now
161
+ start_of_this_week = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
162
+ dates_in_week(start_of_this_week.prev_day(7))
163
+ end
164
+
165
+ def dates_in_week(start_date)
166
+ dates = [start_date]
167
+ 1.upto(6) { |i| dates << start_date.next_day(i) }
168
+ dates.map { |d| d.strftime("%Y%m%d") }
169
+ end
170
+
95
171
  # :startdoc:
96
172
  end
97
173
  end
@@ -0,0 +1,67 @@
1
+ module Rbnotes::Commands
2
+
3
+ ##
4
+ # Searches a given pattern in notes those have timestamps match a
5
+ # given timestamp pattern. The first argument is a pattern to search.
6
+ # It is a String object represents a portion of text or it may a
7
+ # String represents a regular expression. The second argument is
8
+ # optional and it is a timestamp pattern to specify the search target.
9
+ #
10
+ # A pattern for search is mandatory. If no pattern, raises
11
+ # Rbnotes::MissingArgumentError.
12
+ #
13
+ # Example of PATTERN for search:
14
+ #
15
+ # "rbnotes" (a word)
16
+ # "macOS Big Sur" (a few words)
17
+ # "2[0-9]{3}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])" (a regular expression)
18
+ #
19
+ # A timestamp pattern is optional. If no timestamp pattern, all notes
20
+ # in the repository would be target of search.
21
+ #
22
+ # See the document of `Rbnotes::Commands::List#execute` to know about
23
+ # a timestamp pattern.
24
+
25
+ class Search < Command
26
+
27
+ def description # :nodoc:
28
+ "Search a given pattern in notes"
29
+ end
30
+
31
+ def execute(args, conf)
32
+ pattern = args.shift
33
+ raise MissingArgumentError, args if pattern.nil?
34
+
35
+ timestamp_pattern = args.shift # `nil` is acceptable
36
+
37
+ repo = Textrepo.init(conf)
38
+ begin
39
+ result = repo.search(pattern, timestamp_pattern)
40
+ rescue Textrepo::InvalidSearchResultError => e
41
+ puts e.message
42
+ else
43
+ result.each { |stamp, num, match|
44
+ puts "#{stamp}:#{num}:#{match}"
45
+ }
46
+ end
47
+ end
48
+
49
+ def help # :nodoc:
50
+ puts <<HELP
51
+ usage:
52
+ #{Rbnotes::NAME} search PATTERN [STAMP_PATTERN]
53
+
54
+ PATTERN is a word (or words) to search, it may also be a regular
55
+ expression.
56
+
57
+ STAMP_PATTERN must be:
58
+
59
+ (a) full qualified timestamp (with suffix): "20201030160200"
60
+ (b) year and date part: "20201030"
61
+ (c) year and month part: "202010"
62
+ (d) year part only: "2020"
63
+ (e) date part only: "1030"
64
+ HELP
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,23 @@
1
- module Rbnotes
2
- class Commands::Show < Commands::Command
1
+ module Rbnotes::Commands
2
+
3
+ ##
4
+ # Shows the content of the note specified by the argument. The
5
+ # argument must be a string which can be converted into
6
+ # Textrepo::Timestamp object.
7
+ #
8
+ # A string for Timestamp must be:
9
+ #
10
+ # "20201106112600" : year, date, time and sec
11
+ # "20201106112600_012" : with suffix
12
+ #
13
+ # If no argument is passed, reads the standard input for an argument.
14
+
15
+ class Show < Command
16
+
17
+ def description # :nodoc:
18
+ "Show the content of a note"
19
+ end
20
+
3
21
  def execute(args, conf)
4
22
  stamp = Rbnotes::Utils.read_timestamp(args)
5
23
 
@@ -17,5 +35,18 @@ module Rbnotes
17
35
  puts content
18
36
  end
19
37
  end
38
+
39
+ def help # :nodoc:
40
+ puts <<HELP
41
+ usage:
42
+ #{Rbnotes::NAME} show [TIMESTAMP]
43
+
44
+ Show the content of given note. TIMESTAMP must be a fully qualified
45
+ one, such "20201016165130" or "20201016165130_012" if it has a suffix.
46
+
47
+ The command try to read its argument from the standard input when no
48
+ argument was passed in the command line.
49
+ HELP
50
+ end
20
51
  end
21
52
  end
@@ -1,37 +1,49 @@
1
1
  module Rbnotes::Commands
2
+
2
3
  ##
3
4
  # Updates the content of the note associated with given timestamp.
4
- # Actual modification is done interactively by the external editor.
5
+ #
6
+ # Reads its argument from the standard input when no argument was
7
+ # passed in the command line.
8
+ #
5
9
  # The timestamp associated with the note will be updated to new one,
6
10
  # which is generated while the command exection.
7
11
  #
8
- # A timestamp string must be specified as the only argument. It
9
- # must exactly match to the one of the target note in the
10
- # repository. When the given timestamp was not found, the command
11
- # fails.
12
- #
13
- # Timestamp which is associated to the target note will be newly
14
- # generated with the command execution time. That is, the timestamp
15
- # before the command exection will be obsolete.
12
+ # When "-k" (or "--keep") option is specified, the timestamp will
13
+ # remain unchanged.
16
14
  #
17
- # This command starts the external editor program to edit the
18
- # content of the note. The editor program will be searched as same
19
- # as add command.
15
+ # Actual modification is done interactively by the external editor.
20
16
  #
21
- # If none of editors is available, the command fails.
17
+ # The editor program will be searched as same as add command. If
18
+ # none of editors is available, the execution fails.
22
19
 
23
20
  class Update < Command
24
21
  include ::Rbnotes::Utils
25
22
 
23
+ def description # :nodoc:
24
+ "Update the content of a note"
25
+ end
26
+
26
27
  ##
27
28
  # The 1st and only one argument is the timestamp to speficy the
28
- # note to update. Returns the new timestamp which is associated
29
- # to the note updated.
29
+ # note to update.
30
30
  #
31
31
  # :call-seq:
32
32
  # "20201020112233" -> "20201021123400"
33
33
 
34
34
  def execute(args, conf)
35
+ @opts = {}
36
+ while args.size > 0
37
+ arg = args.shift
38
+ case arg
39
+ when "-k", "--keep"
40
+ @opts[:keep_timestamp] = true
41
+ else
42
+ args.unshift(arg)
43
+ break
44
+ end
45
+ end
46
+
35
47
  target_stamp = Rbnotes::Utils.read_timestamp(args)
36
48
  editor = find_editor(conf[:editor])
37
49
  repo = Textrepo.init(conf)
@@ -44,16 +56,21 @@ module Rbnotes::Commands
44
56
  end
45
57
 
46
58
  tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
47
- text = File.readlines(tmpfile)
59
+ text = File.readlines(tmpfile, :chomp => true)
48
60
 
49
61
  unless text.empty?
62
+ keep = @opts[:keep_timestamp] || false
50
63
  newstamp = nil
51
64
  begin
52
- newstamp = repo.update(target_stamp, text)
65
+ newstamp = repo.update(target_stamp, text, keep)
53
66
  rescue StandardError => e
54
67
  puts e.message
55
68
  else
56
- puts "Update the note [%s -> %s]" % [target_stamp, newstamp]
69
+ if keep
70
+ puts "Update the note content, the timestamp unchanged [%s]" % newstamp
71
+ else
72
+ puts "Update the note [%s -> %s]" % [target_stamp, newstamp] unless target_stamp == newstamp
73
+ end
57
74
  ensure
58
75
  # Don't forget to remove the temporary file.
59
76
  File.delete(tmpfile)
@@ -62,5 +79,27 @@ module Rbnotes::Commands
62
79
  puts "Nothing is updated, since the specified content is empty."
63
80
  end
64
81
  end
82
+
83
+ def help # :nodoc:
84
+ puts <<HELP
85
+ usage:
86
+ #{Rbnotes::NAME} update [TIMESTAMP]
87
+
88
+ Updates the content of the note associated with given timestamp.
89
+
90
+ Reads its argument from the standard input when no argument was passed
91
+ in the command line.
92
+
93
+ The timestamp associated with the note will be updated to new one,
94
+ which is generated while the command exection.
95
+
96
+ When "-k" (or "--keep") option is specified, the timestamp will remain
97
+ unchanged.
98
+
99
+ Actual modification is done interactively by the external editor. The
100
+ editor program will be searched as same as add command. If none of
101
+ editors is available, the execution fails.
102
+ HELP
103
+ end
65
104
  end
66
105
  end
@@ -113,7 +113,11 @@ module Rbnotes
113
113
  # foo bar baz ...
114
114
  #
115
115
  # then, only the first string is interested
116
- io.gets.split(" ")[0]
116
+ begin
117
+ io.gets.split(":")[0].rstrip
118
+ rescue NoMethodError => _
119
+ nil
120
+ end
117
121
  end
118
122
  module_function :read_arg
119
123
 
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.3.1"
3
- RELEASE = '2020-10-30'
2
+ VERSION = "0.4.4"
3
+ RELEASE = "2020-11-09"
4
4
  end
@@ -25,6 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ["lib"]
27
27
 
28
- spec.add_dependency "textrepo", "~> 0.4"
28
+ spec.add_dependency "textrepo", "~> 0.5.4"
29
29
  spec.add_dependency "unicode-display_width", "~> 1.7"
30
30
  end
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.3.1
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - mnbi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-30 00:00:00.000000000 Z
11
+ date: 2020-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: textrepo
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.4'
19
+ version: 0.5.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.4'
26
+ version: 0.5.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: unicode-display_width
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -64,8 +64,11 @@ files:
64
64
  - lib/rbnotes/commands.rb
65
65
  - lib/rbnotes/commands/add.rb
66
66
  - lib/rbnotes/commands/delete.rb
67
+ - lib/rbnotes/commands/export.rb
68
+ - lib/rbnotes/commands/help.rb
67
69
  - lib/rbnotes/commands/import.rb
68
70
  - lib/rbnotes/commands/list.rb
71
+ - lib/rbnotes/commands/search.rb
69
72
  - lib/rbnotes/commands/show.rb
70
73
  - lib/rbnotes/commands/update.rb
71
74
  - lib/rbnotes/conf.rb