rbnotes 0.4.3 → 0.4.8

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: 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