rbnotes 0.2.2 → 0.4.2

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: 74c8725b48ee7e4048c6689227e519e27490e604463f84822211b0bad0bf8849
4
- data.tar.gz: 71ef7e862a20d309c2c8fb29dc54ddacd42f6c3ab1f2eefd2e1dfb17e6ca8b2b
3
+ metadata.gz: 7a43f87f2f4b03c9f8e27d06eb4fc426abb6dafbd420d5bbbcdb4ec2ea38d497
4
+ data.tar.gz: 0f9c060bfefde2e6c3f611d7d7e683fdd5ced20327b3ae6a3cbfcac39cdaf5a3
5
5
  SHA512:
6
- metadata.gz: b3310eae55fd1bc5e5b8a9b83a0ba5ae7f76e3d953a8a94f62fb7aabd9952ff03f868328e4b1e978b9c80a9c9b3563b676fc10eb776dbb47051bb1026fcad765
7
- data.tar.gz: 36e328b4a3a6fe64f52c52f31050c46b372f91e2f768420b235c1ed811908634e80197aa7cf358cc87e434f57cba61fcbefa76a0aa61633e119d70f3502a9997
6
+ metadata.gz: e28ed07cde6f385945a9ed3b92ff6402b8ec2dcdd2a75f6d1d0a8e391a2bf5e000245d708903fa815ec5faecfb2cfc39034240ef83a44818bd9a9ccb39c326ed
7
+ data.tar.gz: 0e2a87a92c5b5a674d143c3d4461577976e5f62508d67ebbdb5202bfbc771c04aacaab520f8860e29429411f50677351f059e01a919e0836fc889b6285507fbd
@@ -7,8 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
  ## [Unreleased]
8
8
  Nothing to record here.
9
9
 
10
+ ## [0.4.2] - 2020-11-05
11
+ ### Added
12
+ - Add a feature to keep the timestamp in `update` command. (#44)
13
+
14
+ ### Changed
15
+ - Fix issue #45: hanging up of `add` command.
16
+
17
+ ## [0.4.1] - 2020-11-04
18
+ ### Added
19
+ - Add a feature to accept a timestamp in `add` command. (#34)
20
+
21
+ ## [0.4.0] - 2020-11-03
22
+ ### Added
23
+ - Add a new command `search` to perform full text search. (#33)
24
+
25
+ ## [0.3.1] - 2020-10-30
26
+ ### Added
27
+ - Add feature to specify configuration file in the command line. (#21)
28
+
29
+ ## [0.3.0] - 2020-10-29
30
+ ### Added
31
+ - Add feature to read argument from the standard input. (#27)
32
+
10
33
  ## [0.2.2] - 2020-10-27
11
- ###
34
+ ### Added
12
35
  - Add feature to accept a timestamp pattern in `list` command. (#22)
13
36
 
14
37
  ## [0.2.1] - 2020-10-25
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.2.2)
5
- textrepo (~> 0.4)
4
+ rbnotes (0.4.2)
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.3)
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:
@@ -28,12 +31,19 @@ General syntax is:
28
31
  rbnotes [global_opts] [command] [command_opts] [args]
29
32
  ```
30
33
 
34
+ ### Global options
35
+
36
+ - "-c CONF_FILE", "--conf"
37
+ - specifies a configuration file
38
+
31
39
  ### Commands
32
40
 
33
41
  - import
34
42
  - imports existing files
35
43
  - list
36
44
  - lists notes in the repository with their timestamps and subject
45
+ - search
46
+ - search a word (or words) in the repository
37
47
  - show
38
48
  - shows the content of a note
39
49
  - add
@@ -57,6 +67,8 @@ Searches the configuration file in the following order:
57
67
 
58
68
  None of them is found, then the default configuration is used.
59
69
 
70
+ The global option "-c CONF_FILE" will override the location.
71
+
60
72
  #### Content
61
73
 
62
74
  The format of the configuration file must be written in YAML.
@@ -75,14 +87,16 @@ A real example:
75
87
 
76
88
  ``` yaml
77
89
  ---
78
- :run_mode: :development
90
+ :run_mode: :production
79
91
  :repository_type: :file_system
80
92
  :repository_name: "notes"
81
93
  :repository_base: "~"
82
- :pager: "bat"
94
+ :pager: "bat -l md"
83
95
  :editor: "/usr/local/bin/emacsclient"
84
96
  ```
85
97
 
98
+ The content is identical to `conf/config.yml`.
99
+
86
100
  #### Variables
87
101
 
88
102
  ##### :run-mode (mandatory)
@@ -124,13 +138,50 @@ the `:repository_name` value. It would be:
124
138
 
125
139
  > :repository_base/:repository_name
126
140
 
127
- The value must be an absolute path. The short-hand notation of the
128
- home directory ("~") is usable.
141
+ The value is recommended to be an absolute path in the production
142
+ time, since relative path will be expanded with the current working
143
+ directory. However, in the development and testing time, the relative
144
+ path may be useful. See `conf/config_deve.yml` or
145
+ `conf/config_test.yml`.
146
+
147
+ The short-hand notation of the home directory ("~") is usable.
129
148
 
130
149
  ##### Miscellaneous variables (optional)
131
150
 
132
151
  - :pager : specify a pager program
133
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"]`.
134
185
 
135
186
  ##### Default values for mandatory variables
136
187
 
@@ -0,0 +1,7 @@
1
+ ---
2
+ :run_mode: :production
3
+ :repository_type: :file_system
4
+ :repository_name: "notes"
5
+ :repository_base: "~"
6
+ :pager: "bat -l md"
7
+ :editor: "/usr/local/bin/emacsclient"
@@ -0,0 +1,7 @@
1
+ ---
2
+ :run_mode: :development
3
+ :repository_type: :file_system
4
+ :repository_name: "notes"
5
+ :repository_base: "tmp"
6
+ :pager: "bat -l md"
7
+ :editor: "/usr/local/bin/emacsclient"
@@ -0,0 +1,5 @@
1
+ ---
2
+ :run_mode: :test
3
+ :repository_type: :file_system
4
+ :repository_name: "notes"
5
+ :repository_base: "test/sandbox"
@@ -4,20 +4,46 @@ require "rbnotes"
4
4
 
5
5
  include Rbnotes
6
6
 
7
- DEBUG = true
8
-
9
7
  class App
8
+ def initialize
9
+ @gopts = {}
10
+ end
11
+
12
+ def options
13
+ @gopts
14
+ end
15
+
16
+ def parse_global_options(args)
17
+ while args.size > 0
18
+ arg = args.shift
19
+ case arg
20
+ when "-c", "--conf"
21
+ file = args.shift
22
+ raise ArgumentError, args.unshift(arg) if file.nil?
23
+ file = File.expand_path(file)
24
+ raise ArgumentError, "no such file: %s" % file unless FileTest.exist?(file)
25
+ @gopts[:conf_file] = file
26
+ else
27
+ args.unshift(arg)
28
+ break
29
+ end
30
+ end
31
+ end
32
+
10
33
  def run(args)
11
34
  cmd = args.shift
12
- Commands.load(cmd).execute(args, Rbnotes.conf)
35
+ Commands.load(cmd).execute(args, Rbnotes.conf(@gopts[:conf_file]))
13
36
  end
14
37
  end
15
38
 
16
39
  app = App.new
17
40
  begin
41
+ app.parse_global_options(ARGV)
18
42
  app.run(ARGV)
19
43
  rescue MissingArgumentError, MissingTimestampError,
20
- NoEditorError, ProgramAbortError => e
44
+ NoEditorError, ProgramAbortError,
45
+ Textrepo::InvalidTimestampStringError,
46
+ ArgumentError => e
21
47
  puts e.message
22
48
  exit 1
23
49
  end
@@ -2,16 +2,20 @@ module Rbnotes
2
2
  ##
3
3
  # This module defines all command classes of rbnotes. Each command
4
4
  # class must be derived from Rbnotes::Commands::Command class.
5
+
5
6
  module Commands
6
7
  ##
7
8
  # The base class for a command class.
9
+
8
10
  class Command
11
+
9
12
  ##
10
13
  # :call-seq:
11
- # Array, Hash -> nil
14
+ # execute(Array, Hash) -> nil
15
+ #
12
16
  # - Array: arguments for each command
13
17
  # - Hash : rbnotes configuration
14
- #
18
+
15
19
  def execute(args, conf)
16
20
  Builtins::DEFAULT_CMD.new.execute(args, conf)
17
21
  end
@@ -28,22 +32,55 @@ module Rbnotes
28
32
  class Help < Command
29
33
  def execute(_, _)
30
34
  puts <<USAGE
31
- usage: rbnotes [command] [args]
35
+ usage:
36
+ rbnotes [-c|--conf CONF_FILE] [command] [args]
37
+
38
+ option:
39
+ -c, --conf [CONF_FILE] : specifiy the configuration file
40
+
41
+ CONF_FILE must be written in YAML. To know about details of the
42
+ configuration file, see README.md or Wiki page.
32
43
 
33
44
  command:
34
- add : create a new note
35
- update STAMP : edit the note with external editor
36
- import FILE : import a FILE into the repository
37
- list NUM : list NUM notes
38
- show STAMP : show the note specified with STAMP
39
- delete STAMP : delete the note specified with STAMP
40
-
41
- conf : print the current configuraitons
42
- repo : print the repository path
43
- stamp TIME_STR : convert TIME_STR into a timestamp
44
- time STAMP : convert STAMP into a time string
45
- version : print version
46
- help : show help
45
+ add : create a new note
46
+ import FILE : import a FILE into the repository
47
+
48
+ list [STAMP_PATTERN] : list notes those timestamp matches PATTERN
49
+ search PATTERN [STAMP_PATTERN] : search PATTERN
50
+
51
+ STAMP_PATTERN must be:
52
+
53
+ (a) full qualified timestamp (with suffix): "20201030160200"
54
+ (b) year and date part: "20201030"
55
+ (c) year and month part: "202010"
56
+ (d) year part only: "2020"
57
+ (e) date part only: "1030"
58
+
59
+ PATTERN is a word (or words) to search, it may also be a regular
60
+ expression.
61
+
62
+ show [STAMP] : show the note specified with STAMP
63
+ update [STAMP] : edit the note with external editor
64
+ delete [STAMP] : delete the note specified with STAMP
65
+
66
+ STAMP must be a sequence of digits to represent year, date and
67
+ time (and suffix), such "20201030160200" or "20201030160200_012".
68
+
69
+ show/update/delete reads its argument from the standard input when
70
+ no argument was passed in the command line.
71
+
72
+ version : print version
73
+ help : show help
74
+
75
+ command for development:
76
+ conf : print the current configuraitons
77
+ repo : print the repository path
78
+ stamp TIME_STR : convert TIME_STR into a timestamp
79
+ time STAMP : convert STAMP into a time string
80
+
81
+ For more information, see Wiki page.
82
+ - https://github.com/mnbi/rbnotes/wiki
83
+
47
84
  USAGE
48
85
  end
49
86
  end
@@ -64,9 +101,9 @@ USAGE
64
101
 
65
102
  puts case type
66
103
  when :file_system
67
- "#{base}/#{name}"
104
+ File.expand_path(name, base)
68
105
  else
69
- "#{base}/#{name}"
106
+ File.join(base, name)
70
107
  end
71
108
  end
72
109
  end
@@ -113,15 +150,16 @@ USAGE
113
150
  # :startdoc:
114
151
 
115
152
  class << self
153
+
116
154
  ##
117
155
  # Loads a class to perfom the command, then returns an instance
118
156
  # of the class.
119
157
  #
120
158
  # :call-seq:
121
- # "import" -> an object of Rbnotes::Commnads::Import
122
- # "list" -> an object of Rbnotes::Commands::List
123
- # "show" -> an object of Rbnotes::Commands::Show
124
- #
159
+ # load("import") -> Rbnotes::Commnads::Import
160
+ # load("list") -> Rbnotes::Commands::List
161
+ # load("show") -> Rbnotes::Commands::Show
162
+
125
163
  def load(cmd_name)
126
164
  cmd_name ||= DEFAULT_CMD_NAME
127
165
  klass_name = cmd_name.capitalize
@@ -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,38 @@ 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
 
21
32
  def execute(args, conf)
22
- newstamp = Textrepo::Timestamp.new(Time.now)
33
+ @opts = {}
34
+ while args.size > 0
35
+ arg = args.shift
36
+ case arg
37
+ when "-t", "--timestamp"
38
+ stamp_str = args.shift
39
+ raise ArgumentError, "missing timestamp: %s" % args.unshift(arg) if stamp_str.nil?
40
+ stamp_str = complement_timestamp_pattern(stamp_str)
41
+ @opts[:timestamp] = Textrepo::Timestamp.parse_s(stamp_str)
42
+ else
43
+ args.unshift(arg)
44
+ break
45
+ end
46
+ end
47
+
48
+ stamp = @opts[:timestamp] || Textrepo::Timestamp.new(Time.now)
23
49
 
24
50
  candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
25
51
  editor = find_program(candidates)
26
52
  raise Rbnotes::NoEditorError, candidates if editor.nil?
27
53
 
28
- tmpfile = run_with_tmpfile(editor, newstamp.to_s)
54
+ tmpfile = run_with_tmpfile(editor, stamp.to_s)
29
55
  text = File.readlines(tmpfile)
30
56
 
31
57
  repo = Textrepo.init(conf)
32
58
  begin
33
- repo.create(newstamp, text)
59
+ repo.create(stamp, text)
34
60
  rescue Textrepo::DuplicateTimestampError => e
35
61
  puts e.message
36
62
  puts "Just wait a second, then retry."
@@ -39,11 +65,28 @@ module Rbnotes::Commands
39
65
  rescue StandardError => e
40
66
  puts e.message
41
67
  else
42
- puts "Add a note [%s]" % newstamp.to_s
68
+ puts "Add a note [%s]" % stamp.to_s
43
69
  ensure
44
70
  # Don't forget to remove the temporary file.
45
71
  File.delete(tmpfile)
46
72
  end
47
73
  end
74
+
75
+ # :stopdoc:
76
+ private
77
+ def complement_timestamp_pattern(pattern)
78
+ stamp_str = nil
79
+ case pattern.to_s.size
80
+ when "yyyymoddhhmiss_lll".size, "yyyymoddhhmiss".size
81
+ stamp_str = pattern.dup
82
+ when "yyyymoddhhmi".size # omit sec part
83
+ stamp_str = "#{pattern}00"
84
+ when "moddhhmi".size # omit year and sec part
85
+ stamp_str = "#{Time.now.year}#{pattern}00"
86
+ else
87
+ raise Textrepo::InvalidTimestampStringError, pattern
88
+ end
89
+ stamp_str
90
+ end
48
91
  end
49
92
  end
@@ -14,21 +14,17 @@
14
14
  module Rbnotes
15
15
  class Commands::Delete < Commands::Command
16
16
  def execute(args, conf)
17
- stamp_str = args.shift
18
- unless stamp_str.nil?
19
- repo = Textrepo.init(conf)
20
- begin
21
- stamp = Textrepo::Timestamp.parse_s(stamp_str)
22
- repo.delete(stamp)
23
- rescue Textrepo::MissingTimestampError => e
24
- puts e.message
25
- rescue StandardError => e
26
- puts e.message
27
- else
28
- puts "Delete [%s]" % stamp.to_s
29
- end
17
+ stamp = Rbnotes::Utils.read_timestamp(args)
18
+
19
+ repo = Textrepo.init(conf)
20
+ begin
21
+ repo.delete(stamp)
22
+ rescue Textrepo::MissingTimestampError => e
23
+ puts e.message
24
+ rescue StandardError => e
25
+ puts e.message
30
26
  else
31
- super
27
+ puts "Delete [%s]" % stamp.to_s
32
28
  end
33
29
  end
34
30
  end
@@ -29,7 +29,7 @@ module Rbnotes
29
29
  puts "The note [%s] in the repository exactly matches" \
30
30
  " the specified file." % stamp
31
31
  puts "It seems there is no need to import the file [%s]." % file
32
- exit # normal end
32
+ break
33
33
  else
34
34
  puts "The text in the repository does not match the" \
35
35
  " specified file."
@@ -40,8 +40,8 @@ module Rbnotes
40
40
  end
41
41
  rescue Textrepo::EmptyTextError => _
42
42
  puts "... aborted."
43
- puts "The specified file is empyt."
44
- exit 1 # error
43
+ puts "The specified file is empty."
44
+ break
45
45
  end
46
46
  end
47
47
  if count > 999
@@ -24,6 +24,9 @@ module Rbnotes
24
24
  # "20201027": specifies year and date
25
25
  # - all Timestamps those have the same year and date would match
26
26
  #
27
+ # "202011": specifies year and month
28
+ # - all Timestamps those have the same year and month would match
29
+ #
27
30
  # "2020": specifies year only
28
31
  # - all Timestamps those have the same year would match
29
32
  #
@@ -54,11 +57,11 @@ module Rbnotes
54
57
  # Makes a headline with the timestamp and subject of the notes, it
55
58
  # looks like as follows:
56
59
  #
57
- # |<------------------ console column size --------------------->|
58
- # +-- timestamp ---+ +--- subject (the 1st line of each note) --+
59
- # | | | |
60
- # 20101010001000_123: # I love Macintosh. [EOL]
61
- # 20100909090909_999: # This is very very long long loooong subje[EOL]
60
+ # |<------------------ console column size ------------------->|
61
+ # +-- timestamp ---+ +- subject (the 1st line of each note) -+
62
+ # | | | |
63
+ # 20101010001000_123: I love Macintosh. [EOL]
64
+ # 20100909090909_999: This is very very long long loooong subje[EOL]
62
65
  # ++
63
66
  # ^--- delimiter (2 characters)
64
67
  #
@@ -69,9 +72,7 @@ module Rbnotes
69
72
  delimiter = ": "
70
73
  subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
71
74
 
72
- subject = @repo.read(timestamp)[0]
73
- prefix = '# '
74
- subject = prefix + subject.lstrip if subject[0, 2] != prefix
75
+ subject = remove_heading_markup(@repo.read(timestamp)[0])
75
76
 
76
77
  ts_part = "#{timestamp.to_s} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
77
78
  sj_part = truncate_str(subject, subject_width)
@@ -90,6 +91,10 @@ module Rbnotes
90
91
  result
91
92
  end
92
93
 
94
+ def remove_heading_markup(str)
95
+ str.sub(/^#+ +/, '')
96
+ end
97
+
93
98
  # :startdoc:
94
99
  end
95
100
  end
@@ -0,0 +1,44 @@
1
+ module Rbnotes
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 Commands::Search < Commands::Command
26
+ def execute(args, conf)
27
+ pattern = args.shift
28
+ raise MissingArgumentError, args if pattern.nil?
29
+
30
+ timestamp_pattern = args.shift # `nil` is acceptable
31
+
32
+ repo = Textrepo.init(conf)
33
+ begin
34
+ result = repo.search(pattern, timestamp_pattern)
35
+ rescue Textrepo::InvalidSearchResultError => e
36
+ puts e.message
37
+ else
38
+ result.each { |stamp, num, match|
39
+ puts "#{stamp}:#{num}:#{match}"
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,24 +1,20 @@
1
1
  module Rbnotes
2
2
  class Commands::Show < Commands::Command
3
3
  def execute(args, conf)
4
- stamp_str = args.shift
5
- unless stamp_str.nil?
6
- repo = Textrepo.init(conf)
7
- stamp = Textrepo::Timestamp.parse_s(stamp_str)
8
- content = repo.read(stamp)
4
+ stamp = Rbnotes::Utils.read_timestamp(args)
9
5
 
10
- pager = conf[:pager]
11
- unless pager.nil?
12
- require 'open3'
13
- Open3.pipeline_w(pager) { |stdin|
14
- stdin.puts content
15
- stdin.close
16
- }
17
- else
18
- puts content
19
- end
6
+ repo = Textrepo.init(conf)
7
+ content = repo.read(stamp)
8
+
9
+ pager = conf[:pager]
10
+ unless pager.nil?
11
+ require 'open3'
12
+ Open3.pipeline_w(pager) { |stdin|
13
+ stdin.puts content
14
+ stdin.close
15
+ }
20
16
  else
21
- super
17
+ puts content
22
18
  end
23
19
  end
24
20
  end
@@ -5,6 +5,9 @@ module Rbnotes::Commands
5
5
  # The timestamp associated with the note will be updated to new one,
6
6
  # which is generated while the command exection.
7
7
  #
8
+ # When "-k" (or "--keep") option is specified, the timestamp will
9
+ # remain unchanged.
10
+ #
8
11
  # A timestamp string must be specified as the only argument. It
9
12
  # must exactly match to the one of the target note in the
10
13
  # repository. When the given timestamp was not found, the command
@@ -19,7 +22,7 @@ module Rbnotes::Commands
19
22
  # as add command.
20
23
  #
21
24
  # If none of editors is available, the command fails.
22
- #
25
+
23
26
  class Update < Command
24
27
  include ::Rbnotes::Utils
25
28
 
@@ -30,19 +33,22 @@ module Rbnotes::Commands
30
33
  #
31
34
  # :call-seq:
32
35
  # "20201020112233" -> "20201021123400"
33
- #
34
- def execute(args, conf)
35
- raise Rbnotes::MissingArgumentError, args if args.size < 1
36
36
 
37
- target_stamp = nil
38
- begin
39
- target_stamp = Textrepo::Timestamp.parse_s(args.shift)
40
- rescue ArgumentError => e
41
- raise Rbnotes::MissingArgumentError, args
37
+ def execute(args, conf)
38
+ @opts = {}
39
+ while args.size > 0
40
+ arg = args.shift
41
+ case arg
42
+ when "-k", "--keep"
43
+ @opts[:keep_timestamp] = true
44
+ else
45
+ args.unshift(arg)
46
+ break
47
+ end
42
48
  end
43
49
 
50
+ target_stamp = Rbnotes::Utils.read_timestamp(args)
44
51
  editor = find_editor(conf[:editor])
45
-
46
52
  repo = Textrepo.init(conf)
47
53
 
48
54
  text = nil
@@ -53,16 +59,21 @@ module Rbnotes::Commands
53
59
  end
54
60
 
55
61
  tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
56
- text = File.readlines(tmpfile)
62
+ text = File.readlines(tmpfile, :chomp => true)
57
63
 
58
64
  unless text.empty?
65
+ keep = @opts[:keep_timestamp] || false
59
66
  newstamp = nil
60
67
  begin
61
- newstamp = repo.update(target_stamp, text)
68
+ newstamp = repo.update(target_stamp, text, keep)
62
69
  rescue StandardError => e
63
70
  puts e.message
64
71
  else
65
- puts "Update the note [%s -> %s]" % [target_stamp, newstamp]
72
+ if keep
73
+ puts "Update the note content, the timestamp unchanged [%s]" % newstamp
74
+ else
75
+ puts "Update the note [%s -> %s]" % [target_stamp, newstamp] unless target_stamp == newstamp
76
+ end
66
77
  ensure
67
78
  # Don't forget to remove the temporary file.
68
79
  File.delete(tmpfile)
@@ -1,10 +1,11 @@
1
1
  module Rbnotes
2
2
  ##
3
3
  # A base class for each error class of rbnotes.
4
- #
4
+
5
5
  class Error < StandardError; end
6
6
 
7
7
  # :stopdoc:
8
+
8
9
  module ErrMsg
9
10
  MISSING_ARGUMENT = "missing argument: %s"
10
11
  MISSING_TIMESTAMP = "missing timestamp: %s"
@@ -16,7 +17,7 @@ module Rbnotes
16
17
 
17
18
  ##
18
19
  # An error raised if an essential argument was missing.
19
- #
20
+
20
21
  class MissingArgumentError < Error
21
22
  def initialize(args)
22
23
  super(ErrMsg::MISSING_ARGUMENT % args.to_s)
@@ -26,7 +27,7 @@ module Rbnotes
26
27
  ##
27
28
  # An error raised if a given timestamp was not found in the
28
29
  # repository.
29
- #
30
+
30
31
  class MissingTimestampError < Error
31
32
  def initialize(timestamp)
32
33
  super(ErrMsg::MISSING_TIMESTAMP % timestamp)
@@ -36,7 +37,7 @@ module Rbnotes
36
37
  ##
37
38
  # An error raised if no external editor is available to edit a note,
38
39
  # even "nano" or "vi".
39
- #
40
+
40
41
  class NoEditorError < Error
41
42
  def initialize(names)
42
43
  super(ErrMsg::NO_EDITOR % names.to_s)
@@ -46,6 +47,7 @@ module Rbnotes
46
47
  ##
47
48
  # An error raised when a external program such an editor was aborted
48
49
  # during its execution.
50
+
49
51
  class ProgramAbortError < Error
50
52
  def initialize(cmdline)
51
53
  super(ErrMsg::PROGRAM_ABORT % cmdline.join(" "))
@@ -86,6 +86,41 @@ module Rbnotes
86
86
  end
87
87
  module_function :run_with_tmpfile
88
88
 
89
+ ##
90
+ # Generates a Textrepo::Timestamp object from a String which comes
91
+ # from the command line arguments. When no argument is given,
92
+ # then reads from STDIN.
93
+ #
94
+ # :call-seq:
95
+ # read_timestamp(args) -> String
96
+
97
+ def read_timestamp(args)
98
+ str = args.shift || read_arg($stdin)
99
+ Textrepo::Timestamp.parse_s(str)
100
+ end
101
+ module_function :read_timestamp
102
+
103
+ ##
104
+ # Reads an argument from the IO object. Typically, it is intended
105
+ # to be used with STDIN.
106
+ #
107
+ # :call-seq:
108
+ # read_arg(IO) -> String
109
+
110
+ def read_arg(io)
111
+ # assumes the reading line looks like:
112
+ #
113
+ # foo bar baz ...
114
+ #
115
+ # then, only the first string is interested
116
+ begin
117
+ io.gets.split(":")[0].rstrip
118
+ rescue NoMethodError => _
119
+ nil
120
+ end
121
+ end
122
+ module_function :read_arg
123
+
89
124
  # :stopdoc:
90
125
 
91
126
  private
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.2.2"
3
- RELEASE = '2020-10-27'
2
+ VERSION = "0.4.2"
3
+ RELEASE = "2020-11-05"
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.2.2
4
+ version: 0.4.2
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-27 00:00:00.000000000 Z
11
+ date: 2020-11-05 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
@@ -56,6 +56,9 @@ files:
56
56
  - Rakefile
57
57
  - bin/console
58
58
  - bin/setup
59
+ - conf/config.yml
60
+ - conf/config_deve.yml
61
+ - conf/config_test.yml
59
62
  - exe/rbnotes
60
63
  - lib/rbnotes.rb
61
64
  - lib/rbnotes/commands.rb
@@ -63,6 +66,7 @@ files:
63
66
  - lib/rbnotes/commands/delete.rb
64
67
  - lib/rbnotes/commands/import.rb
65
68
  - lib/rbnotes/commands/list.rb
69
+ - lib/rbnotes/commands/search.rb
66
70
  - lib/rbnotes/commands/show.rb
67
71
  - lib/rbnotes/commands/update.rb
68
72
  - lib/rbnotes/conf.rb