rbnotes 0.4.4 → 0.4.9

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