rbnotes 0.2.1 → 0.4.1

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: e3b44e20631a0216391b389aee7faca4ecebe31502bbff92606c938023a98240
4
- data.tar.gz: 4a39041ad77631da13be01ebde0745edd38f22f52b5c9ad2ae1a002053739c1b
3
+ metadata.gz: '09f402dd55c244533d8f8ba3518df3d13cde1c5952efad94598f210a43a88c11'
4
+ data.tar.gz: a824e8e2e33c1c1e67d2ff27ab22618d83aa7138a889db20d43f40491f6e1c82
5
5
  SHA512:
6
- metadata.gz: e98fabf28f56131caf2ec40c1111aa74a0819d923937cd033eeed6921c5ce43102657e8c3d787a8b9718b3057e7505921d2f7d316a327a041e80dcc2bce4b2ae
7
- data.tar.gz: 672cd941dd68c545056c752a714bc29df9a9de5b14ce00b8c6568562df619e796c7cea77b376380bb3c32c06d680f834552c3c6367033bf2ab02b7460c4e1b0f
6
+ metadata.gz: e52be521f1d1502ca2d36fd6853203436e15c3f830e17ff509529b0b7f08a2fad26a264714d81732603e6f6dabe69c2d40bebdb400b306f4fba1218c86c2ac52
7
+ data.tar.gz: c555b3b74717d45d18a6d63959356ff411457b2f3b47819ca53ecca38fc6de03a9e5d5839e622e5b65d3513c40dfe487b2649206697d075400c1121a61ea99d4
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
  ## [Unreleased]
8
8
  Nothing to record here.
9
9
 
10
+ ## [0.4.1] - 2020-11-04
11
+ ### Added
12
+ - Add a feature to accept a timestamp in `add` command. (#34)
13
+
14
+ ## [0.4.0] - 2020-11-03
15
+ ### Added
16
+ - Add a new command `search` to perform full text search. (#33)
17
+
18
+ ## [0.3.1] - 2020-10-30
19
+ ### Added
20
+ - Add feature to specify configuration file in the command line. (#21)
21
+
22
+ ## [0.3.0] - 2020-10-29
23
+ ### Added
24
+ - Add feature to read argument from the standard input. (#27)
25
+
26
+ ## [0.2.2] - 2020-10-27
27
+ ### Added
28
+ - Add feature to accept a timestamp pattern in `list` command. (#22)
29
+
10
30
  ## [0.2.1] - 2020-10-25
11
31
  ### Added
12
32
  - Add feature to load the configuration from an external file.
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"
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbnotes (0.2.1)
5
- textrepo (~> 0.4)
4
+ rbnotes (0.4.1)
5
+ textrepo (~> 0.5)
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.2)
13
+ textrepo (0.5.3)
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)
24
24
 
25
25
  BUNDLED WITH
26
26
  2.1.4
data/README.md CHANGED
@@ -4,18 +4,25 @@
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:
10
13
 
11
14
  ```ruby
12
- gem 'rbnotes, github: 'mnbi/rbnotes, branch: 'main'
15
+ gem 'rbnotes'
13
16
  ```
14
17
 
15
18
  And then execute:
16
19
 
17
20
  $ bundle install
18
21
 
22
+ Or install it yourself as:
23
+
24
+ $ gem install rbnotes
25
+
19
26
  ## Usage
20
27
 
21
28
  General syntax is:
@@ -24,12 +31,19 @@ General syntax is:
24
31
  rbnotes [global_opts] [command] [command_opts] [args]
25
32
  ```
26
33
 
34
+ ### Global options
35
+
36
+ - "-c CONF_FILE", "--conf"
37
+ - specifies a configuration file
38
+
27
39
  ### Commands
28
40
 
29
41
  - import
30
42
  - imports existing files
31
43
  - list
32
44
  - lists notes in the repository with their timestamps and subject
45
+ - search
46
+ - search a word (or words) in the repository
33
47
  - show
34
48
  - shows the content of a note
35
49
  - add
@@ -53,6 +67,8 @@ Searches the configuration file in the following order:
53
67
 
54
68
  None of them is found, then the default configuration is used.
55
69
 
70
+ The global option "-c CONF_FILE" will override the location.
71
+
56
72
  #### Content
57
73
 
58
74
  The format of the configuration file must be written in YAML.
@@ -71,14 +87,16 @@ A real example:
71
87
 
72
88
  ``` yaml
73
89
  ---
74
- :run_mode: :development
90
+ :run_mode: :production
75
91
  :repository_type: :file_system
76
92
  :repository_name: "notes"
77
93
  :repository_base: "~"
78
- :pager: "bat"
94
+ :pager: "bat -l md"
79
95
  :editor: "/usr/local/bin/emacsclient"
80
96
  ```
81
97
 
98
+ The content is identical to `conf/config.yml`.
99
+
82
100
  #### Variables
83
101
 
84
102
  ##### :run-mode (mandatory)
@@ -120,13 +138,50 @@ the `:repository_name` value. It would be:
120
138
 
121
139
  > :repository_base/:repository_name
122
140
 
123
- The value must be an absolute path. The short-hand notation of the
124
- 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.
125
148
 
126
149
  ##### Miscellaneous variables (optional)
127
150
 
128
151
  - :pager : specify a pager program
129
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"]`.
130
185
 
131
186
  ##### Default values for mandatory variables
132
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,20 +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
- import FILE : import a FILE into the repository
35
- list NUM : list NUM notes
36
- show STAMP : show the note specified with STAMP
37
- delete STAMP : delete the note specified with STAMP
38
-
39
- conf : print the current configuraitons
40
- repo : print the repository path
41
- stamp TIME_STR : convert TIME_STR into a timestamp
42
- time STAMP : convert STAMP into a time string
43
- version : print version
44
- 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
+
45
84
  USAGE
46
85
  end
47
86
  end
@@ -62,9 +101,9 @@ USAGE
62
101
 
63
102
  puts case type
64
103
  when :file_system
65
- "#{base}/#{name}"
104
+ File.expand_path(name, base)
66
105
  else
67
- "#{base}/#{name}"
106
+ File.join(base, name)
68
107
  end
69
108
  end
70
109
  end
@@ -111,15 +150,16 @@ USAGE
111
150
  # :startdoc:
112
151
 
113
152
  class << self
153
+
114
154
  ##
115
155
  # Loads a class to perfom the command, then returns an instance
116
156
  # of the class.
117
157
  #
118
158
  # :call-seq:
119
- # "import" -> an object of Rbnotes::Commnads::Import
120
- # "list" -> an object of Rbnotes::Commands::List
121
- # "show" -> an object of Rbnotes::Commands::Show
122
- #
159
+ # load("import") -> Rbnotes::Commnads::Import
160
+ # load("list") -> Rbnotes::Commands::List
161
+ # load("show") -> Rbnotes::Commands::Show
162
+
123
163
  def load(cmd_name)
124
164
  cmd_name ||= DEFAULT_CMD_NAME
125
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,37 @@ 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
+ end
45
+ end
46
+
47
+ stamp = @opts[:timestamp] || Textrepo::Timestamp.new(Time.now)
23
48
 
24
49
  candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
25
50
  editor = find_program(candidates)
26
51
  raise Rbnotes::NoEditorError, candidates if editor.nil?
27
52
 
28
- tmpfile = run_with_tmpfile(editor, newstamp.to_s)
53
+ tmpfile = run_with_tmpfile(editor, stamp.to_s)
29
54
  text = File.readlines(tmpfile)
30
55
 
31
56
  repo = Textrepo.init(conf)
32
57
  begin
33
- repo.create(newstamp, text)
58
+ repo.create(stamp, text)
34
59
  rescue Textrepo::DuplicateTimestampError => e
35
60
  puts e.message
36
61
  puts "Just wait a second, then retry."
@@ -39,11 +64,28 @@ module Rbnotes::Commands
39
64
  rescue StandardError => e
40
65
  puts e.message
41
66
  else
42
- puts "Add a note [%s]" % newstamp.to_s
67
+ puts "Add a note [%s]" % stamp.to_s
43
68
  ensure
44
69
  # Don't forget to remove the temporary file.
45
70
  File.delete(tmpfile)
46
71
  end
47
72
  end
73
+
74
+ # :stopdoc:
75
+ private
76
+ def complement_timestamp_pattern(pattern)
77
+ stamp_str = nil
78
+ case pattern.to_s.size
79
+ when "yyyymoddhhmiss_lll".size, "yyyymoddhhmiss".size
80
+ stamp_str = pattern.dup
81
+ when "yyyymoddhhmi".size # omit sec part
82
+ stamp_str = "#{pattern}00"
83
+ when "moddhhmi".size # omit year and sec part
84
+ stamp_str = "#{Time.now.year}#{pattern}00"
85
+ else
86
+ raise Textrepo::InvalidTimestampStringError, pattern
87
+ end
88
+ stamp_str
89
+ end
48
90
  end
49
91
  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
@@ -2,42 +2,79 @@ require "unicode/display_width"
2
2
  require "io/console/size"
3
3
 
4
4
  module Rbnotes
5
+ ##
6
+ # Defines `list` command for `rbnotes`. See the document of execute
7
+ # method to know about the behavior of this command.
8
+
5
9
  class Commands::List < Commands::Command
10
+
11
+ ##
12
+ # Shows the list of notes in the repository. The only argument is
13
+ # optional. If it passed, it must be an timestamp pattern. A
14
+ # timestamp is an instance of Textrepo::Timestamp class. A
15
+ # timestamp pattern is a string which would match several
16
+ # Timestamp objects.
17
+ #
18
+ # Here is
19
+ # several examples of timestamp patterns.
20
+ #
21
+ # "20201027093600_012": a complete string to represent a timestamp
22
+ # - this pattern would match exactly one Timestamp object
23
+ #
24
+ # "20201027": specifies year and date
25
+ # - all Timestamps those have the same year and date would match
26
+ #
27
+ # "202011": specifies year and month
28
+ # - all Timestamps those have the same year and month would match
29
+ #
30
+ # "2020": specifies year only
31
+ # - all Timestamps those have the same year would match
32
+ #
33
+ # "1027": specifies date only
34
+ # - all Timestamps those have the same date would match, even if
35
+ # they have the different year.
36
+ #
37
+ # :call-seq:
38
+ # execute(Array, Rbnotes::Conf or Hash) -> nil
39
+
6
40
  def execute(args, conf)
7
- @row, @column = IO.console_size
8
- max = (args.shift || @row - 3).to_i
41
+ pattern = args.shift # `nil` is acceptable
9
42
 
10
43
  @repo = Textrepo.init(conf)
11
- notes = @repo.entries.sort{|a, b| b <=> a}
12
- notes[0, max].each { |timestamp_str|
13
- puts make_headline(timestamp_str)
44
+ # newer stamp shoud be above
45
+ stamps = @repo.entries(pattern).sort{|a, b| b <=> a}
46
+ stamps.each { |timestamp|
47
+ puts make_headline(timestamp)
14
48
  }
15
49
  end
16
50
 
51
+ # :stopdoc:
52
+
17
53
  private
18
54
  TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
55
+
56
+ ##
19
57
  # Makes a headline with the timestamp and subject of the notes, it
20
58
  # looks like as follows:
21
59
  #
22
- # |<------------------ console column size --------------------->|
23
- # +-- timestamp ---+ +--- subject (the 1st line of each note) --+
24
- # | | | |
25
- # 20101010001000_123: # I love Macintosh. [EOL]
26
- # 20100909090909_999: # This is very very long long loooong subje[EOL]
27
- # ++
28
- # ^--- delimiter (2 characters)
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]
65
+ # ++
66
+ # ^--- delimiter (2 characters)
29
67
  #
30
68
  # The subject part will truncate when it is long.
31
- def make_headline(timestamp_str)
32
69
 
70
+ def make_headline(timestamp)
71
+ _, column = IO.console_size
33
72
  delimiter = ": "
34
- subject_width = @column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
73
+ subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
35
74
 
36
- subject = @repo.read(Textrepo::Timestamp.parse_s(timestamp_str))[0]
37
- prefix = '# '
38
- subject = prefix + subject.lstrip if subject[0, 2] != prefix
75
+ subject = remove_heading_markup(@repo.read(timestamp)[0])
39
76
 
40
- ts_part = "#{timestamp_str} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
77
+ ts_part = "#{timestamp.to_s} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
41
78
  sj_part = truncate_str(subject, subject_width)
42
79
 
43
80
  ts_part + delimiter + sj_part
@@ -53,5 +90,11 @@ module Rbnotes
53
90
  }
54
91
  result
55
92
  end
93
+
94
+ def remove_heading_markup(str)
95
+ str.sub(/^#+ +/, '')
96
+ end
97
+
98
+ # :startdoc:
56
99
  end
57
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
@@ -19,7 +19,7 @@ module Rbnotes::Commands
19
19
  # as add command.
20
20
  #
21
21
  # If none of editors is available, the command fails.
22
- #
22
+
23
23
  class Update < Command
24
24
  include ::Rbnotes::Utils
25
25
 
@@ -30,19 +30,10 @@ module Rbnotes::Commands
30
30
  #
31
31
  # :call-seq:
32
32
  # "20201020112233" -> "20201021123400"
33
- #
34
- def execute(args, conf)
35
- raise Rbnotes::MissingArgumentError, args if args.size < 1
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
42
- end
43
33
 
34
+ def execute(args, conf)
35
+ target_stamp = Rbnotes::Utils.read_timestamp(args)
44
36
  editor = find_editor(conf[:editor])
45
-
46
37
  repo = Textrepo.init(conf)
47
38
 
48
39
  text = nil
@@ -53,7 +44,7 @@ module Rbnotes::Commands
53
44
  end
54
45
 
55
46
  tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
56
- text = File.readlines(tmpfile)
47
+ text = File.readlines(tmpfile, :chomp => true)
57
48
 
58
49
  unless text.empty?
59
50
  newstamp = nil
@@ -62,7 +53,7 @@ module Rbnotes::Commands
62
53
  rescue StandardError => e
63
54
  puts e.message
64
55
  else
65
- puts "Update the note [%s -> %s]" % [target_stamp, newstamp]
56
+ puts "Update the note [%s -> %s]" % [target_stamp, newstamp] unless target_stamp == newstamp
66
57
  ensure
67
58
  # Don't forget to remove the temporary file.
68
59
  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.1"
3
- RELEASE = '2020-10-25'
2
+ VERSION = "0.4.1"
3
+ RELEASE = "2020-11-04"
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"
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.1
4
+ version: 0.4.1
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-25 00:00:00.000000000 Z
11
+ date: 2020-11-04 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'
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'
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