rbnotes 0.4.3 → 0.4.8

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: f48ac5666d917964e9c103c11be86b12736b81ced6e072be4707e1e943b6ff76
4
- data.tar.gz: d29b9b68d6ebfcfdc7289962405a0390daed2705cd80b3beb50e036d2f8d9e85
3
+ metadata.gz: de852db7e0679690dc371c256eb8f65a5f8022772b04ed6024c4f4b55c5cebae
4
+ data.tar.gz: 27764e6f940f29be341745d50db658ef419af0e576b674533398910694f294f7
5
5
  SHA512:
6
- metadata.gz: f5be0687fc189a5dad6178104a05af0f64e35fc44dd1a73bf677a232038b81b4f88e5562ca9f9e8f098152136c005ebdc334158d34652c0fe7a2bea8e13c4056
7
- data.tar.gz: a1e5d671d93ff9efdbe33e7c6751984f851c39756304df7c874fe82df558b18b7f4f716d82253e99b2d8e21cc1d795d6b81c0f1ba665f8f3e99e38c30e4d19d8
6
+ metadata.gz: de49f9046ad826a346cabd37bab304e1d0ab08dfda4e6f5ca6ce7b216cc717ed39645baf092aa993e8287bd65761a605d1f2d1c158e465bc75efbac68ceeb785
7
+ data.tar.gz: 9c28c82c7b486695a5e5fe5875fda39e69bc1673e85434368fba4a2a1a71c0cff1918e60c88ecfd43bf22546bbb79d121319044b1fb068da5445dbc78b239639
@@ -7,23 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
  ## [Unreleased]
8
8
  Nothing to record here.
9
9
 
10
+ ## [0.4.8] - 2020-11-16
11
+ ### Fixed
12
+ - Fix issue #65: messy output of the `search` command.
13
+
14
+ ## [0.4.7] - 2020-11-15
15
+ ### Changed
16
+ - Beautify output of the `search` command. (#63)
17
+
18
+ ### Fixed
19
+ - Fix issue #61: `list` command fails in pipeline.
20
+
21
+ ## [0.4.6] - 2020-11-13
22
+ ### Added
23
+ - Add a new command `pick` to select a note with picker program. (#59)
24
+
25
+ ## [0.4.5] - 2020-11-12
26
+ ### Changed
27
+ - Add a feature to accept multiple args for `list`. (#57)
28
+
29
+ ### Fixed
30
+ - Fix issue #54: Notes list does not sort correctly.
31
+
32
+ ## [0.4.4] - 2020-11-09
33
+ ### Changed
34
+ - Add a feature to use a keyword as an argument for `list`. (#47)
35
+
10
36
  ## [0.4.3] - 2020-11-08
11
37
  ### Added
12
- - Add a new command `export` to write out a note into a file (#51)
38
+ - Add a new command `export` to write out a note into a file. (#51)
13
39
  - Add individual help for each command. (#42)
14
40
 
15
41
  ### Fixed
16
- - Fix `add` fails without modification (#48)
42
+ - Fix issue #48: `add` fails without modification.
17
43
 
18
44
  ## [0.4.2] - 2020-11-05
19
- ### Added
45
+ ### Changed
20
46
  - Add a feature to keep the timestamp in `update` command. (#44)
21
47
 
22
48
  ### Fixed
23
49
  - Fix issue #45: hanging up of `add` command.
24
50
 
25
51
  ## [0.4.1] - 2020-11-04
26
- ### Added
52
+ ### Changed
27
53
  - Add a feature to accept a timestamp in `add` command. (#34)
28
54
 
29
55
  ## [0.4.0] - 2020-11-03
@@ -32,20 +58,21 @@ Nothing to record here.
32
58
 
33
59
  ## [0.3.1] - 2020-10-30
34
60
  ### Added
35
- - Add feature to specify configuration file in the command line. (#21)
61
+ - Add a feature to specify configuration file in the command
62
+ line. (#21)
36
63
 
37
64
  ## [0.3.0] - 2020-10-29
38
- ### Added
65
+ ### Changed
39
66
  - Add feature to read argument from the standard input. (#27)
40
67
 
41
68
  ## [0.2.2] - 2020-10-27
42
- ### Added
43
- - Add feature to accept a timestamp pattern in `list` command. (#22)
69
+ ### Changed
70
+ - Add a feature to accept a timestamp pattern in `list` command. (#22)
44
71
 
45
72
  ## [0.2.1] - 2020-10-25
46
73
  ### Added
47
- - Add feature to load the configuration from an external file.
48
- - Add description about the configuration file in README.md
74
+ - Add a feature to load the configuration from an external file.
75
+ - Add a description about the configuration file in README.md.
49
76
 
50
77
  ## [0.2.0] - 2020-10-23
51
78
  ### Added
@@ -55,15 +82,15 @@ Nothing to record here.
55
82
  - Add a new task into `Rakefile` to generate RI docs.
56
83
  - The intention of the task is to verify RI docs.
57
84
 
58
- ### Fixed
85
+ ### Changed
59
86
  - Refactor some tests.
60
87
 
61
88
  ## [0.1.3] - 2020-10-15
62
- ### Fixed
89
+ ### Changed
63
90
  - Add help text for the `conf` command.
64
91
 
65
92
  ## [0.1.2] - 2020-10-15
66
- ### Fixed
93
+ ### Changed
67
94
  - Adapt the API change in `textrepo` (0.4.0).
68
95
 
69
96
  ## [0.1.0] - 2020-10-12
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbnotes (0.4.3)
4
+ rbnotes (0.4.8)
5
5
  textrepo (~> 0.5.4)
6
6
  unicode-display_width (~> 1.7)
7
7
 
@@ -10,7 +10,7 @@ GEM
10
10
  specs:
11
11
  minitest (5.14.2)
12
12
  rake (13.0.1)
13
- textrepo (0.5.4)
13
+ textrepo (0.5.7)
14
14
  unicode-display_width (1.7.0)
15
15
 
16
16
  PLATFORMS
data/README.md CHANGED
@@ -164,10 +164,10 @@ don't have to set `:searcher_options` for them.
164
164
 
165
165
  | searcher | default options in `textrepo` |
166
166
  |:---------|:---------------------------------------------------|
167
- | `grep` | `["-i", "-n", "-R", "-E"]` |
168
- | `egrep` | `["-i", "-n", "-R"]` |
169
- | `ggrep` | `["-i", "-n", "-R", "-E"]` |
170
- | `gegrep` | `["-i", "-n", "-R"]` |
167
+ | `grep` | `["-i", "-n", "-H", "-R", "-E"]` |
168
+ | `egrep` | `["-i", "-n", "-H", "-R"]` |
169
+ | `ggrep` | `["-i", "-n", "-H", "-R", "-E"]` |
170
+ | `gegrep` | `["-i", "-n", "-H", "-R"]` |
171
171
  | `rg` | `["-S", "-n", "--no-heading", "--color", "never"]` |
172
172
 
173
173
  Those searcher names are used in macOS (with Homebrew). Any other OS
@@ -4,4 +4,5 @@
4
4
  :repository_name: "notes"
5
5
  :repository_base: "~"
6
6
  :pager: "bat -l md"
7
+ :picker: "fzf"
7
8
  :editor: "/usr/local/bin/emacsclient"
@@ -4,4 +4,5 @@
4
4
  :repository_name: "notes"
5
5
  :repository_base: "tmp"
6
6
  :pager: "bat -l md"
7
+ :picker: "fzf"
7
8
  :editor: "/usr/local/bin/emacsclient"
@@ -48,6 +48,11 @@ app = App.new
48
48
  begin
49
49
  app.parse_global_options(ARGV)
50
50
  app.run(ARGV)
51
+ rescue Errno::EPIPE => e
52
+ # Fix issue #61: When the pipeline which rbnotes connects is
53
+ # discarded by the other program, the execption was raised. It does
54
+ # not end abnormally for rbnotes. So, just ignores the exception.
55
+ exit 0
51
56
  rescue MissingArgumentError, MissingTimestampError,
52
57
  NoEditorError, ProgramAbortError,
53
58
  Textrepo::InvalidTimestampStringError,
@@ -8,4 +8,10 @@ module Rbnotes
8
8
  require_relative "rbnotes/conf"
9
9
  require_relative "rbnotes/utils"
10
10
  require_relative "rbnotes/commands"
11
+
12
+ class << self
13
+ def utils
14
+ Utils.instance
15
+ end
16
+ end
11
17
  end
@@ -61,7 +61,7 @@ Example usage:
61
61
  #{Rbnotes::NAME} delete [TIMESTAMP]
62
62
  #{Rbnotes::NAME} export [TIMESTAMP [FILENAME]]
63
63
  #{Rbnotes::NAME} import FILE
64
- #{Rbnotes::NAME} list [STAMP_PATTERN]
64
+ #{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
65
65
  #{Rbnotes::NAME} search PATTERN [STAMP_PATTERN]
66
66
  #{Rbnotes::NAME} show [TIMESTAMP]
67
67
  #{Rbnotes::NAME} update [TIMESTAMP]
@@ -27,7 +27,6 @@ module Rbnotes::Commands
27
27
  # If none of the above editor is available, the command fails.
28
28
 
29
29
  class Add < Command
30
- include ::Rbnotes::Utils
31
30
 
32
31
  def description # :nodoc:
33
32
  "Add a new note"
@@ -52,10 +51,10 @@ module Rbnotes::Commands
52
51
  stamp = @opts[:timestamp] || Textrepo::Timestamp.new(Time.now)
53
52
 
54
53
  candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
55
- editor = find_program(candidates)
54
+ editor = Rbnotes.utils.find_program(candidates)
56
55
  raise Rbnotes::NoEditorError, candidates if editor.nil?
57
56
 
58
- tmpfile = run_with_tmpfile(editor, stamp.to_s)
57
+ tmpfile = Rbnotes.utils.run_with_tmpfile(editor, stamp.to_s)
59
58
 
60
59
  unless FileTest.exist?(tmpfile)
61
60
  puts "Cancel adding, since nothing to store"
@@ -13,7 +13,7 @@ module Rbnotes::Commands
13
13
  end
14
14
 
15
15
  def execute(args, conf)
16
- stamp = Rbnotes::Utils.read_timestamp(args)
16
+ stamp = Rbnotes.utils.read_timestamp(args)
17
17
 
18
18
  repo = Textrepo.init(conf)
19
19
  begin
@@ -21,7 +21,7 @@ module Rbnotes::Commands
21
21
  # execute([a String as timestring], Rbnotes::Conf or Hash) -> nil
22
22
 
23
23
  def execute(args, conf)
24
- stamp = Rbnotes::Utils.read_timestamp(args)
24
+ stamp = Rbnotes.utils.read_timestamp(args)
25
25
 
26
26
  repo = Textrepo.init(conf)
27
27
  begin
@@ -1,6 +1,3 @@
1
- require "unicode/display_width"
2
- require "io/console/size"
3
-
4
1
  module Rbnotes::Commands
5
2
 
6
3
  ##
@@ -14,14 +11,25 @@ module Rbnotes::Commands
14
11
  end
15
12
 
16
13
  ##
17
- # Shows a list of notes in the repository. The only argument is
18
- # optional. If it passed, it must be an timestamp pattern. A
19
- # timestamp is an instance of Textrepo::Timestamp class. A
20
- # timestamp pattern is a string which would match several
21
- # Timestamp objects.
14
+ # Shows a list of notes in the repository. Arguments are
15
+ # optional. If several args are passed, each of them must be a
16
+ # timestamp pattern or a keyword.
17
+ #
18
+ # Any order of timestamp patterns and keywords mixture is
19
+ # acceptable. The redundant patterns are just ignored.
20
+ #
21
+ # A timestamp pattern is a string which would match several
22
+ # Timestamp objects. A timestamp is an instance of
23
+ # Textrepo::Timestamp class.
22
24
  #
23
- # Here is
24
- # several examples of timestamp patterns.
25
+ # A keyword must be one of them:
26
+ #
27
+ # - "today" (or "to")
28
+ # - "yeasterday" (or "ye")
29
+ # - "this_week" (or "tw")
30
+ # - "last_week" (or "lw")
31
+ #
32
+ # Here is several examples of timestamp patterns.
25
33
  #
26
34
  # "20201027093600_012": a complete string to represent a timestamp
27
35
  # - this pattern would match exactly one Timestamp object
@@ -43,24 +51,23 @@ module Rbnotes::Commands
43
51
  # execute(Array, Rbnotes::Conf or Hash) -> nil
44
52
 
45
53
  def execute(args, conf)
46
- pattern = args.shift # `nil` is acceptable
47
-
54
+ patterns = Rbnotes.utils.expand_keyword_in_args(args)
48
55
  @repo = Textrepo.init(conf)
49
56
  # newer stamp shoud be above
50
- stamps = @repo.entries(pattern).sort{|a, b| b <=> a}
51
- stamps.each { |timestamp|
52
- puts make_headline(timestamp)
57
+ Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
58
+ puts Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
53
59
  }
54
60
  end
55
61
 
56
62
  def help # :nodoc:
57
63
  puts <<HELP
58
64
  usage:
59
- #{Rbnotes::NAME} list [STAMP_PATTERN]
65
+ #{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
60
66
 
61
67
  Show a list of notes. When no arguments, make a list with all notes
62
68
  in the repository. When specified STAMP_PATTERN, only those match the
63
- pattern are listed.
69
+ pattern are listed. Instead of STAMP_PATTERN, some KEYWORDs could be
70
+ used.
64
71
 
65
72
  STAMP_PATTERN must be:
66
73
 
@@ -69,56 +76,16 @@ STAMP_PATTERN must be:
69
76
  (c) year and month part: "202010"
70
77
  (d) year part only: "2020"
71
78
  (e) date part only: "1030"
72
- HELP
73
- end
74
79
 
75
- # :stopdoc:
80
+ KEYWORD:
76
81
 
77
- private
78
- TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
82
+ - "today" (or "to")
83
+ - "yeasterday" (or "ye")
84
+ - "this_week" (or "tw")
85
+ - "last_week" (or "lw")
79
86
 
80
- ##
81
- # Makes a headline with the timestamp and subject of the notes, it
82
- # looks like as follows:
83
- #
84
- # |<------------------ console column size ------------------->|
85
- # +-- timestamp ---+ +- subject (the 1st line of each note) -+
86
- # | | | |
87
- # 20101010001000_123: I love Macintosh. [EOL]
88
- # 20100909090909_999: This is very very long long loooong subje[EOL]
89
- # ++
90
- # ^--- delimiter (2 characters)
91
- #
92
- # The subject part will truncate when it is long.
93
-
94
- def make_headline(timestamp)
95
- _, column = IO.console_size
96
- delimiter = ": "
97
- subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
98
-
99
- subject = remove_heading_markup(@repo.read(timestamp)[0])
100
-
101
- ts_part = "#{timestamp.to_s} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
102
- sj_part = truncate_str(subject, subject_width)
103
-
104
- ts_part + delimiter + sj_part
105
- end
106
-
107
- def truncate_str(str, size)
108
- count = 0
109
- result = ""
110
- str.each_char { |c|
111
- count += Unicode::DisplayWidth.of(c)
112
- break if count > size
113
- result << c
114
- }
115
- result
116
- end
117
-
118
- def remove_heading_markup(str)
119
- str.sub(/^#+ +/, '')
87
+ HELP
120
88
  end
121
89
 
122
- # :startdoc:
123
90
  end
124
91
  end
@@ -0,0 +1,47 @@
1
+ module Rbnotes::Commands
2
+
3
+ ##
4
+ # Picks a timestamp with a picker program, like `fzf`.
5
+
6
+ class Pick < Command
7
+
8
+ def description # :nodoc:
9
+ "Pick a timestamp with a picker program"
10
+ end
11
+
12
+ def execute(args, conf)
13
+ patterns = Rbnotes.utils.expand_keyword_in_args(args)
14
+ @repo = Textrepo.init(conf)
15
+
16
+ list = []
17
+ Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
18
+ list << Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
19
+ }
20
+
21
+ picker = conf[:picker]
22
+ unless picker.nil?
23
+ require 'open3'
24
+ result = Open3.pipeline_rw(picker) { |stdin, stdout, _|
25
+ stdin.puts list
26
+ stdin.close
27
+ stdout.read
28
+ }
29
+ puts result
30
+ else
31
+ puts list
32
+ end
33
+ end
34
+
35
+ def help # :nodoc:
36
+ puts <<HELP
37
+ usage:
38
+ #{Rbnotes::NAME} pick
39
+
40
+ Pick a timestamp with a picker program, like `fzf`. This command
41
+ refers the configuration setting of ":picker". If no picker program
42
+ is specified, it will behave as same as "list" command.
43
+
44
+ HELP
45
+ end
46
+ end
47
+ end
@@ -39,11 +39,8 @@ module Rbnotes::Commands
39
39
  result = repo.search(pattern, timestamp_pattern)
40
40
  rescue Textrepo::InvalidSearchResultError => e
41
41
  puts e.message
42
- else
43
- result.each { |stamp, num, match|
44
- puts "#{stamp}:#{num}:#{match}"
45
- }
46
42
  end
43
+ print_search_result(result.map{ |e| SearchEntry.new(*e) })
47
44
  end
48
45
 
49
46
  def help # :nodoc:
@@ -63,5 +60,51 @@ STAMP_PATTERN must be:
63
60
  (e) date part only: "1030"
64
61
  HELP
65
62
  end
63
+
64
+ private
65
+
66
+ # Each entry of search result is:
67
+ #
68
+ # [<timestamp>, <line_number>, <matched_text>]
69
+ #
70
+ # The sort must be done in;
71
+ #
72
+ # - descending order for <timestamp>,
73
+ # - ascending ordier for <line_number>.
74
+ #
75
+
76
+ SearchEntry = Struct.new(:timestamp, :line_number, :matched_text) {
77
+ def timestamp_size
78
+ timestamp.to_s.size
79
+ end
80
+
81
+ def line_number_digits_size
82
+ line_number.to_s.size
83
+ end
84
+ }
85
+
86
+ def print_search_result(entries)
87
+ maxcol_stamp = entries.map(&:timestamp_size).max
88
+ maxcol_num = entries.map(&:line_number_digits_size).max
89
+
90
+ sort(entries).each { |e|
91
+ stamp_display = "%- *s" % [maxcol_stamp, e.timestamp]
92
+ num_display = "%*d" % [maxcol_num, e.line_number]
93
+
94
+ puts "#{stamp_display}: #{num_display}: #{e.matched_text}"
95
+ }
96
+ end
97
+
98
+ def sort(search_result)
99
+ search_result.sort { |a, b|
100
+ stamp_comparison = (b.timestamp <=> a.timestamp)
101
+ if stamp_comparison == 0
102
+ a.line_number <=> b.line_number
103
+ else
104
+ stamp_comparison
105
+ end
106
+ }
107
+ end
108
+
66
109
  end
67
110
  end
@@ -19,7 +19,7 @@ module Rbnotes::Commands
19
19
  end
20
20
 
21
21
  def execute(args, conf)
22
- stamp = Rbnotes::Utils.read_timestamp(args)
22
+ stamp = Rbnotes.utils.read_timestamp(args)
23
23
 
24
24
  repo = Textrepo.init(conf)
25
25
  content = repo.read(stamp)
@@ -18,7 +18,6 @@ module Rbnotes::Commands
18
18
  # none of editors is available, the execution fails.
19
19
 
20
20
  class Update < Command
21
- include ::Rbnotes::Utils
22
21
 
23
22
  def description # :nodoc:
24
23
  "Update the content of a note"
@@ -44,8 +43,8 @@ module Rbnotes::Commands
44
43
  end
45
44
  end
46
45
 
47
- target_stamp = Rbnotes::Utils.read_timestamp(args)
48
- editor = find_editor(conf[:editor])
46
+ target_stamp = Rbnotes.utils.read_timestamp(args)
47
+ editor = Rbnotes.utils.find_editor(conf[:editor])
49
48
  repo = Textrepo.init(conf)
50
49
 
51
50
  text = nil
@@ -55,7 +54,7 @@ module Rbnotes::Commands
55
54
  raise Rbnotes::MissingTimestampError, target_stamp
56
55
  end
57
56
 
58
- tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
57
+ tmpfile = Rbnotes.utils.run_with_tmpfile(editor, target_stamp.to_s, text)
59
58
  text = File.readlines(tmpfile, :chomp => true)
60
59
 
61
60
  unless text.empty?
@@ -11,6 +11,7 @@ module Rbnotes
11
11
  MISSING_TIMESTAMP = "missing timestamp: %s"
12
12
  NO_EDITOR = "No editor is available: %s"
13
13
  PROGRAM_ABORT = "External program was aborted: %s"
14
+ UNKNOWN_KEYWORD = "Unknown keyword: %s"
14
15
  end
15
16
 
16
17
  # :startdoc:
@@ -53,4 +54,14 @@ module Rbnotes
53
54
  super(ErrMsg::PROGRAM_ABORT % cmdline.join(" "))
54
55
  end
55
56
  end
57
+
58
+ ##
59
+ # An eeror raised when an unknown keyword was specified as a
60
+ # timestamp string pattern.
61
+
62
+ class UnknownKeywordError < Error
63
+ def initialize(keyword)
64
+ super(ErrMsg::UNKNOWN_KEYWORD % keyword)
65
+ end
66
+ end
56
67
  end
@@ -1,12 +1,18 @@
1
+ require "singleton"
1
2
  require "pathname"
2
3
  require "tmpdir"
4
+ require "date"
5
+ require "io/console/size"
6
+
7
+ require "unicode/display_width"
3
8
 
4
9
  module Rbnotes
5
10
  ##
6
11
  # Defines several utility methods those are intended to be used in
7
12
  # Rbnotes classes.
8
13
  #
9
- module Utils
14
+ class Utils
15
+ include Singleton
10
16
 
11
17
  ##
12
18
  # Finds a external editor program which is specified with the
@@ -26,7 +32,6 @@ module Rbnotes
26
32
  def find_editor(preferred_editor)
27
33
  find_program([preferred_editor, ENV["EDITOR"], "nano", "vi"].compact)
28
34
  end
29
- module_function :find_editor
30
35
 
31
36
  ##
32
37
  # Finds a executable program in given names. When the executable
@@ -60,7 +65,6 @@ module Rbnotes
60
65
  }
61
66
  nil
62
67
  end
63
- module_function :find_program
64
68
 
65
69
  ##
66
70
  # Executes the program with passing the given filename as argument.
@@ -84,7 +88,6 @@ module Rbnotes
84
88
  raise ProgramAbortError, [prog, tmpfile] unless rc
85
89
  tmpfile
86
90
  end
87
- module_function :run_with_tmpfile
88
91
 
89
92
  ##
90
93
  # Generates a Textrepo::Timestamp object from a String which comes
@@ -98,7 +101,6 @@ module Rbnotes
98
101
  str = args.shift || read_arg($stdin)
99
102
  Textrepo::Timestamp.parse_s(str)
100
103
  end
101
- module_function :read_timestamp
102
104
 
103
105
  ##
104
106
  # Reads an argument from the IO object. Typically, it is intended
@@ -119,7 +121,111 @@ module Rbnotes
119
121
  nil
120
122
  end
121
123
  end
122
- module_function :read_arg
124
+
125
+ ##
126
+ # Parses the given arguments and expand keywords if found. Each
127
+ # of the arguments is assumed to represent a timestamp pattern (or
128
+ # a keyword to be expand into several timestamp pattern). Returns
129
+ # an Array of timestamp partterns (each pattern is a String
130
+ # object).
131
+ #
132
+ # A timestamp pattern looks like:
133
+ #
134
+ # (a) full qualified timestamp (with suffix): "20201030160200"
135
+ # (b) year and date part: "20201030"
136
+ # (c) year and month part: "202010"
137
+ # (d) year part only: "2020"
138
+ # (e) date part only: "1030"
139
+ #
140
+ # KEYWORD:
141
+ #
142
+ # - "today" (or "to")
143
+ # - "yeasterday" (or "ye")
144
+ # - "this_week" (or "tw")
145
+ # - "last_week" (or "lw")
146
+ #
147
+ # :call-seq:
148
+ # expand_keyword_in_args(Array of Strings) -> Array of Strings
149
+
150
+ def expand_keyword_in_args(args)
151
+ return [nil] if args.empty?
152
+
153
+ patterns = []
154
+ while args.size > 0
155
+ arg = args.shift
156
+ if ["today", "to", "yesterday", "ye",
157
+ "this_week", "tw", "last_week", "lw"].include?(arg)
158
+ patterns.concat(Rbnotes.utils.expand_keyword(arg))
159
+ else
160
+ patterns << arg
161
+ end
162
+ end
163
+ patterns.sort.uniq
164
+ end
165
+
166
+ ##
167
+ # Expands a keyword to timestamp strings.
168
+ #
169
+ # :call-seq:
170
+ # expand_keyword(keyword as String) -> Array of timestamp Strings
171
+
172
+ def expand_keyword(keyword)
173
+ patterns = []
174
+ case keyword
175
+ when "today", "to"
176
+ patterns << timestamp_pattern(date_of_today)
177
+ when "yesterday", "ye"
178
+ patterns << timestamp_pattern(date_of_yesterday)
179
+ when "this_week", "tw"
180
+ patterns.concat(dates_in_this_week.map { |d| timestamp_pattern(d) })
181
+ when "last_week", "lw"
182
+ patterns.concat(dates_in_last_week.map { |d| timestamp_pattern(d) })
183
+ else
184
+ raise UnknownKeywordError, keyword
185
+ end
186
+ patterns
187
+ end
188
+
189
+ ##
190
+ # Makes a headline with the timestamp and subject of the notes, it
191
+ # looks like as follows:
192
+ #
193
+ # |<------------------ console column size ------------------->|
194
+ # +-- timestamp ---+ +- subject (the 1st line of each note) -+
195
+ # | | | |
196
+ # 20101010001000_123: I love Macintosh. [EOL]
197
+ # 20100909090909_999: This is very very long long loooong subje[EOL]
198
+ # ++
199
+ # ^--- delimiter (2 characters)
200
+ #
201
+ # The subject part will truncate when it is long.
202
+
203
+ def make_headline(timestamp, text)
204
+ _, column = IO.console_size
205
+ delimiter = ": "
206
+ timestamp_width = timestamp.to_s.size
207
+ subject_width = column - timestamp_width - delimiter.size - 1
208
+
209
+ subject = remove_heading_markup(text[0])
210
+
211
+ ts_part = "#{timestamp.to_s} "[0..(timestamp_width - 1)]
212
+ sj_part = truncate_str(subject, subject_width)
213
+
214
+ ts_part + delimiter + sj_part
215
+ end
216
+
217
+ ##
218
+ # Finds all notes those timestamps match to given patterns in the
219
+ # given repository. Returns an Array contains Timestamp objects.
220
+ #
221
+ # :call-seq:
222
+ # find_notes(Array of timestamp patterns, Textrepo::Repository)
223
+
224
+ def find_notes(timestamp_patterns, repo)
225
+ timestamp_patterns.map { |pat|
226
+ repo.entries(pat)
227
+ }.flatten.sort{ |a, b| b <=> a }.uniq
228
+ end
123
229
 
124
230
  # :stopdoc:
125
231
 
@@ -132,11 +238,70 @@ module Rbnotes
132
238
  }
133
239
  found.compact[0]
134
240
  end
135
- module_function :search_in_path
136
241
 
137
242
  def add_extension(basename)
138
243
  "#{basename}.md"
139
244
  end
140
- module_function :add_extension
245
+
246
+ def timestamp_pattern(date)
247
+ date.strftime("%Y%m%d")
248
+ end
249
+
250
+ def date_of_today
251
+ date(Time.now)
252
+ end
253
+
254
+ def date_of_yesterday
255
+ date(Time.now).prev_day
256
+ end
257
+
258
+ def date(time)
259
+ Date.new(time.year, time.mon, time.day)
260
+ end
261
+
262
+ def dates_in_this_week
263
+ dates_in_week(start_date_in_this_week)
264
+ end
265
+
266
+ def dates_in_last_week
267
+ dates_in_week(start_date_in_last_week)
268
+ end
269
+
270
+ def start_date_in_this_week
271
+ today = Time.now
272
+ Date.new(today.year, today.mon, today.day).prev_day(wday(today))
273
+ end
274
+
275
+ def start_date_in_last_week
276
+ start_date_in_this_week.prev_day(7)
277
+ end
278
+
279
+ def wday(time)
280
+ (time.wday - 1) % 7
281
+ end
282
+
283
+ def dates_in_week(start_date)
284
+ dates = [start_date]
285
+ 1.upto(6) { |i| dates << start_date.next_day(i) }
286
+ dates
287
+ end
288
+
289
+ def truncate_str(str, size)
290
+ count = 0
291
+ result = ""
292
+ str.each_char { |c|
293
+ count += Unicode::DisplayWidth.of(c)
294
+ break if count > size
295
+ result << c
296
+ }
297
+ result
298
+ end
299
+
300
+ def remove_heading_markup(str)
301
+ str.sub(/^#+ +/, '')
302
+ end
303
+
304
+ # :startdoc:
305
+
141
306
  end
142
307
  end
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.4.3"
3
- RELEASE = "2020-11-08"
2
+ VERSION = "0.4.8"
3
+ RELEASE = "2020-11-16"
4
4
  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.4.3
4
+ version: 0.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - mnbi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-08 00:00:00.000000000 Z
11
+ date: 2020-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: textrepo
@@ -68,6 +68,7 @@ files:
68
68
  - lib/rbnotes/commands/help.rb
69
69
  - lib/rbnotes/commands/import.rb
70
70
  - lib/rbnotes/commands/list.rb
71
+ - lib/rbnotes/commands/pick.rb
71
72
  - lib/rbnotes/commands/search.rb
72
73
  - lib/rbnotes/commands/show.rb
73
74
  - lib/rbnotes/commands/update.rb