rbnotes 0.2.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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