rbnotes 0.4.4 → 0.4.9

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: a04bd4d45d7c2f4227f355672baa860602ae3b9e481e7a384daf4283f4ad6754
4
- data.tar.gz: bc707b20ea91ab655f6e355026164c01844c82ea3869c18fd5ca1040c968934d
3
+ metadata.gz: 4e4e20e7274cc9e4f9d013340447f7fb06a7dcf45d8536d71e6c6f456a29d310
4
+ data.tar.gz: e41d785998768d9b4655575ae5303240de437243b6349ecd4ce147a5a051359b
5
5
  SHA512:
6
- metadata.gz: d93df0df9e002a8bae26b7d9e1e89acf19d84551635d58f3ec747c78f3764ac004a784977a3d9940783a8b4661b2caafd2b55502644bca4bb69947d91593bd79
7
- data.tar.gz: 913c240b04215cb5502146f4d1edcd5c1ddc273de31baace3f0d8dff600dcf759e5983aee5cce72fb5079dc26e1a22b43c0aad46e1cc766c4127697090fb0037
6
+ metadata.gz: 5b2f313ceab8c9a18205189235d72f13c48b7724ba8b15e2651bfb79e1af62e55e1fc1d855eaa0b6eadb3b715e3fd85bd70fe692bbf18d706191a81532a27b0d
7
+ data.tar.gz: 430276b6461f3502e8f74463451c67a4ddfe40fe7abd5a676abbb6f0ac24980890de6f6ac22f5a7dd64827283c7b101323d4c21b6b210e8c99a577823244885f
@@ -7,8 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
  ## [Unreleased]
8
8
  Nothing to record here.
9
9
 
10
+ ## [0.4.9] - 2020-11-17
11
+ ### Added
12
+ - Add a new option `--week` to the `list` command. (#67)
13
+
14
+ ## [0.4.8] - 2020-11-16
15
+ ### Fixed
16
+ - Fix issue #65: messy output of the `search` command.
17
+
18
+ ## [0.4.7] - 2020-11-15
19
+ ### Changed
20
+ - Beautify output of the `search` command. (#63)
21
+
22
+ ### Fixed
23
+ - Fix issue #61: `list` command fails in pipeline.
24
+
25
+ ## [0.4.6] - 2020-11-13
26
+ ### Added
27
+ - Add a new command `pick` to select a note with picker program. (#59)
28
+
29
+ ## [0.4.5] - 2020-11-12
30
+ ### Changed
31
+ - Add a feature to accept multiple args for `list`. (#57)
32
+
33
+ ### Fixed
34
+ - Fix issue #54: Notes list does not sort correctly.
35
+
10
36
  ## [0.4.4] - 2020-11-09
11
- ###
37
+ ### Changed
12
38
  - Add a feature to use a keyword as an argument for `list`. (#47)
13
39
 
14
40
  ## [0.4.3] - 2020-11-08
@@ -17,17 +43,17 @@ Nothing to record here.
17
43
  - Add individual help for each command. (#42)
18
44
 
19
45
  ### Fixed
20
- - Fix `add` fails without modification (#48)
46
+ - Fix issue #48: `add` fails without modification.
21
47
 
22
48
  ## [0.4.2] - 2020-11-05
23
- ### Added
49
+ ### Changed
24
50
  - Add a feature to keep the timestamp in `update` command. (#44)
25
51
 
26
52
  ### Fixed
27
53
  - Fix issue #45: hanging up of `add` command.
28
54
 
29
55
  ## [0.4.1] - 2020-11-04
30
- ### Added
56
+ ### Changed
31
57
  - Add a feature to accept a timestamp in `add` command. (#34)
32
58
 
33
59
  ## [0.4.0] - 2020-11-03
@@ -36,20 +62,21 @@ Nothing to record here.
36
62
 
37
63
  ## [0.3.1] - 2020-10-30
38
64
  ### Added
39
- - Add feature to specify configuration file in the command line. (#21)
65
+ - Add a feature to specify configuration file in the command
66
+ line. (#21)
40
67
 
41
68
  ## [0.3.0] - 2020-10-29
42
- ### Added
69
+ ### Changed
43
70
  - Add feature to read argument from the standard input. (#27)
44
71
 
45
72
  ## [0.2.2] - 2020-10-27
46
- ### Added
47
- - Add feature to accept a timestamp pattern in `list` command. (#22)
73
+ ### Changed
74
+ - Add a feature to accept a timestamp pattern in `list` command. (#22)
48
75
 
49
76
  ## [0.2.1] - 2020-10-25
50
77
  ### Added
51
- - Add feature to load the configuration from an external file.
52
- - Add description about the configuration file in README.md
78
+ - Add a feature to load the configuration from an external file.
79
+ - Add a description about the configuration file in README.md.
53
80
 
54
81
  ## [0.2.0] - 2020-10-23
55
82
  ### Added
@@ -59,15 +86,15 @@ Nothing to record here.
59
86
  - Add a new task into `Rakefile` to generate RI docs.
60
87
  - The intention of the task is to verify RI docs.
61
88
 
62
- ### Fixed
89
+ ### Changed
63
90
  - Refactor some tests.
64
91
 
65
92
  ## [0.1.3] - 2020-10-15
66
- ### Fixed
93
+ ### Changed
67
94
  - Add help text for the `conf` command.
68
95
 
69
96
  ## [0.1.2] - 2020-10-15
70
- ### Fixed
97
+ ### Changed
71
98
  - Adapt the API change in `textrepo` (0.4.0).
72
99
 
73
100
  ## [0.1.0] - 2020-10-12
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbnotes (0.4.4)
4
+ rbnotes (0.4.9)
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
@@ -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,7 +1,3 @@
1
- require "date"
2
- require "unicode/display_width"
3
- require "io/console/size"
4
-
5
1
  module Rbnotes::Commands
6
2
 
7
3
  ##
@@ -15,11 +11,23 @@ module Rbnotes::Commands
15
11
  end
16
12
 
17
13
  ##
18
- # Shows a list of notes in the repository. The only argument is
19
- # optional. If it passed, it must be an timestamp pattern. A
20
- # timestamp is an instance of Textrepo::Timestamp class. A
21
- # timestamp pattern is a string which would match several
22
- # 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.
24
+ #
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")
23
31
  #
24
32
  # Here is several examples of timestamp patterns.
25
33
  #
@@ -43,37 +51,45 @@ module Rbnotes::Commands
43
51
  # execute(Array, Rbnotes::Conf or Hash) -> nil
44
52
 
45
53
  def execute(args, conf)
46
- arg = args.shift
47
- patterns = []
48
-
49
- case arg.to_s
50
- when "today", "to"
51
- patterns << Textrepo::Timestamp.new(Time.now).to_s[0..7]
52
- when "yesterday", "ye"
53
- t = Time.now
54
- patterns << Date.new(t.year, t.mon, t.day).prev_day.strftime("%Y%m%d")
55
- when "this_week", "tw"
56
- patterns.concat(dates_in_this_week)
57
- when "last_week", "lw"
58
- patterns.concat(dates_in_last_week)
54
+ @opts = {}
55
+ while args.size > 0
56
+ arg = args.shift
57
+ case arg
58
+ when "-w", "--week"
59
+ @opts[:enum_week] = true
60
+ else
61
+ args.unshift(arg)
62
+ break
63
+ end
64
+ end
65
+
66
+ patterns = nil
67
+ if @opts[:enum_week]
68
+ arg = args.shift || Textrepo::Timestamp.now[0, 8]
69
+ case arg.size
70
+ when "yyyymodd".size, "yyyymoddhhmiss".size, "yyyymoddhhmiss_sfx".size
71
+ stamp_str = "#{arg}000000"[0, 14]
72
+ timestamp = Textrepo::Timestamp.parse_s(stamp_str)
73
+ patterns = Rbnotes.utils.timestamp_patterns_in_week(timestamp)
74
+ else
75
+ raise InvalidTimestampPatternError,
76
+ "cannot convert to a date [%s]" % args.unshift(arg)
77
+ end
59
78
  else
60
- patterns << arg
79
+ patterns = Rbnotes.utils.expand_keyword_in_args(args)
61
80
  end
62
81
 
63
82
  @repo = Textrepo.init(conf)
64
- stamps = patterns.map { |pat|
65
- @repo.entries(pat).sort{|a, b| b <=> a}
66
- }.flatten
67
83
  # newer stamp shoud be above
68
- stamps.each { |timestamp|
69
- puts make_headline(timestamp)
84
+ Rbnotes.utils.find_notes(patterns, @repo).each { |timestamp|
85
+ puts Rbnotes.utils.make_headline(timestamp, @repo.read(timestamp))
70
86
  }
71
87
  end
72
88
 
73
89
  def help # :nodoc:
74
90
  puts <<HELP
75
91
  usage:
76
- #{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
92
+ #{Rbnotes::NAME} list [-w|--week][STAMP_PATTERN|KEYWORD]
77
93
 
78
94
  Show a list of notes. When no arguments, make a list with all notes
79
95
  in the repository. When specified STAMP_PATTERN, only those match the
@@ -95,79 +111,18 @@ KEYWORD:
95
111
  - "this_week" (or "tw")
96
112
  - "last_week" (or "lw")
97
113
 
98
- HELP
99
- end
100
-
101
- # :stopdoc:
102
-
103
- private
104
- TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
105
-
106
- ##
107
- # Makes a headline with the timestamp and subject of the notes, it
108
- # looks like as follows:
109
- #
110
- # |<------------------ console column size ------------------->|
111
- # +-- timestamp ---+ +- subject (the 1st line of each note) -+
112
- # | | | |
113
- # 20101010001000_123: I love Macintosh. [EOL]
114
- # 20100909090909_999: This is very very long long loooong subje[EOL]
115
- # ++
116
- # ^--- delimiter (2 characters)
117
- #
118
- # The subject part will truncate when it is long.
119
-
120
- def make_headline(timestamp)
121
- _, column = IO.console_size
122
- delimiter = ": "
123
- subject_width = column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
114
+ An option "--week" is also acceptable. It specifies to enumerate all
115
+ days of a week. Typically, the option is used with a STAMP_PATTERN
116
+ which specifies a date, such "20201117", then it enumerates all days
117
+ of the week which contains "17th November 2020".
124
118
 
125
- subject = remove_heading_markup(@repo.read(timestamp)[0])
119
+ A STAMP_PATTERN other than (a) and (b) causes an error if it was used
120
+ with "--week" option.
126
121
 
127
- ts_part = "#{timestamp.to_s} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
128
- sj_part = truncate_str(subject, subject_width)
129
-
130
- ts_part + delimiter + sj_part
131
- end
132
-
133
- def truncate_str(str, size)
134
- count = 0
135
- result = ""
136
- str.each_char { |c|
137
- count += Unicode::DisplayWidth.of(c)
138
- break if count > size
139
- result << c
140
- }
141
- result
142
- end
143
-
144
- def remove_heading_markup(str)
145
- str.sub(/^#+ +/, '')
146
- end
147
-
148
- # week day for Monday start calendar
149
- def wday(time)
150
- (time.wday - 1) % 7
151
- end
152
-
153
- def dates_in_this_week
154
- to = Time.now
155
- start = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
156
- dates_in_week(start)
157
- end
158
-
159
- def dates_in_last_week
160
- to = Time.now
161
- start_of_this_week = Date.new(to.year, to.mon, to.day).prev_day(wday(to))
162
- dates_in_week(start_of_this_week.prev_day(7))
163
- end
164
-
165
- def dates_in_week(start_date)
166
- dates = [start_date]
167
- 1.upto(6) { |i| dates << start_date.next_day(i) }
168
- dates.map { |d| d.strftime("%Y%m%d") }
122
+ When no STAMP_PATTERN was specified with "--week" option, the output
123
+ would be as same as the KEYWORD, "this_week" was specified.
124
+ HELP
169
125
  end
170
126
 
171
- # :startdoc:
172
127
  end
173
128
  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?
@@ -7,10 +7,12 @@ module Rbnotes
7
7
  # :stopdoc:
8
8
 
9
9
  module ErrMsg
10
- MISSING_ARGUMENT = "missing argument: %s"
11
- MISSING_TIMESTAMP = "missing timestamp: %s"
10
+ MISSING_ARGUMENT = "Missing argument: %s"
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"
15
+ INVALID_TIMESTAMP_PATTERN = "Invalid timestamp pattern: %s"
14
16
  end
15
17
 
16
18
  # :startdoc:
@@ -53,4 +55,24 @@ module Rbnotes
53
55
  super(ErrMsg::PROGRAM_ABORT % cmdline.join(" "))
54
56
  end
55
57
  end
58
+
59
+ ##
60
+ # An eeror raised when an unknown keyword was specified as a
61
+ # timestamp string pattern.
62
+
63
+ class UnknownKeywordError < Error
64
+ def initialize(keyword)
65
+ super(ErrMsg::UNKNOWN_KEYWORD % keyword)
66
+ end
67
+ end
68
+
69
+ ##
70
+ # An error raised when an invalid timestamp pattern was specified.
71
+
72
+ class InvalidTimestampPatternError < Error
73
+ def initialize(pattern)
74
+ super(ErrMsg::INVALID_TIMESTAMP_PATTERN % pattern)
75
+ end
76
+ end
77
+
56
78
  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,122 @@ 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
229
+
230
+ ##
231
+ # Enumerates all timestamp patterns in a week which contains a
232
+ # given timestamp as a day of the week.
233
+ #
234
+ # :call-seq:
235
+ # timestamp_patterns_in_week(timestamp) -> [Array of Strings]
236
+
237
+ def timestamp_patterns_in_week(timestamp)
238
+ dates_in_week(start_date_in_the_week(timestamp.time)).map { |date| timestamp_pattern(date) }
239
+ end
123
240
 
124
241
  # :stopdoc:
125
242
 
@@ -132,11 +249,74 @@ module Rbnotes
132
249
  }
133
250
  found.compact[0]
134
251
  end
135
- module_function :search_in_path
136
252
 
137
253
  def add_extension(basename)
138
254
  "#{basename}.md"
139
255
  end
140
- module_function :add_extension
256
+
257
+ def timestamp_pattern(date)
258
+ date.strftime("%Y%m%d")
259
+ end
260
+
261
+ def date_of_today
262
+ date(Time.now)
263
+ end
264
+
265
+ def date_of_yesterday
266
+ date(Time.now).prev_day
267
+ end
268
+
269
+ def date(time)
270
+ Date.new(time.year, time.mon, time.day)
271
+ end
272
+
273
+ def dates_in_this_week
274
+ dates_in_week(start_date_in_this_week)
275
+ end
276
+
277
+ def dates_in_last_week
278
+ dates_in_week(start_date_in_last_week)
279
+ end
280
+
281
+ def start_date_in_this_week
282
+ start_date_in_the_week(Time.now)
283
+ end
284
+
285
+ def start_date_in_last_week
286
+ start_date_in_this_week.prev_day(7)
287
+ end
288
+
289
+ def start_date_in_the_week(time)
290
+ parts = [:year, :mon, :day].map { |sym| time.send(sym) }
291
+ Date.new(*parts).prev_day(wday(time))
292
+ end
293
+
294
+ def wday(time)
295
+ (time.wday - 1) % 7
296
+ end
297
+
298
+ def dates_in_week(start_date)
299
+ dates = [start_date]
300
+ 1.upto(6) { |i| dates << start_date.next_day(i) }
301
+ dates
302
+ end
303
+
304
+ def truncate_str(str, size)
305
+ count = 0
306
+ result = ""
307
+ str.each_char { |c|
308
+ count += Unicode::DisplayWidth.of(c)
309
+ break if count > size
310
+ result << c
311
+ }
312
+ result
313
+ end
314
+
315
+ def remove_heading_markup(str)
316
+ str.sub(/^#+ +/, '')
317
+ end
318
+
319
+ # :startdoc:
320
+
141
321
  end
142
322
  end
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.4.4"
3
- RELEASE = "2020-11-09"
2
+ VERSION = "0.4.9"
3
+ RELEASE = "2020-11-17"
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.4
4
+ version: 0.4.9
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-09 00:00:00.000000000 Z
11
+ date: 2020-11-17 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