rbnotes 0.4.10 → 0.4.11

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: 7e6de4675ffb2b409f72f240e4509a6d254793d977a8dd68cc54cf6d0c839e3d
4
- data.tar.gz: 364d4deb47d049af7145024cfb3151639ea9cf0504963c1b72e5a15e465a84a6
3
+ metadata.gz: 4170ccc84305971d10f3726351cdc4138b5fabaa7293bc10a827ed0458f28b8c
4
+ data.tar.gz: 291efc32e2e65462e8c6996ecbbc7ffbb4e6887a5d0065bbb843d01dfbb8c78c
5
5
  SHA512:
6
- metadata.gz: bf90b255e23257a921f7a9254d4df5233f97394e73368f27f6a4a8e96989fa3aa76ab55fca69b201ecd9a15264d5d07cf974ee9b745854ca80d3c962accbe37a
7
- data.tar.gz: c9fca12a0a6a71562a968775b09e3ebf7bda202ce7251666ab7b727236c47f8b5981d7ee703b94bfc22fae038cc161f76b10ed413ca28bcac3a9d9e0417f7f83
6
+ metadata.gz: 1f6d2659c1a50f462867a53bff3f7b45e8b38035c3af44407b70d359c6af9409481f1033059384bd8d6b42d03981ce4f955ac87ee5fe7e99f5b50465d47f4e1b
7
+ data.tar.gz: f5949cfe44eef0951a80ec8949b6bb8a3440d5b483ddacdaf652d8b4dd258a7eb53db42c40290e9a2ebd6e3a1bf6e204f2382b41dd40b23ac8b271dd05547672
@@ -5,7 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
- Nothing to record here.
8
+ ## [0.4.11] - 2020-12-07
9
+ ### Added
10
+ - Add a new command `statistics`. (#73)
11
+ - limited features
12
+ - Add a completion file for `zsh`.
13
+ - a new file `etc/zsh/_rbnotes`
14
+
15
+ ### Changed
16
+ - Add a new option for `import` to use `mtime`. (#82)
17
+ - Add a feature to show multiple notes at once. (#79)
18
+
19
+ ### Fixed
20
+ - Fix issue #77: no error with a non-existing config file.
9
21
 
10
22
  ## [0.4.10] - 2020-11-20
11
23
  ### Added
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbnotes (0.4.10)
4
+ rbnotes (0.4.11)
5
5
  textrepo (~> 0.5.4)
6
6
  unicode-display_width (~> 1.7)
7
7
 
@@ -0,0 +1,93 @@
1
+ #compdef rbnotes
2
+
3
+ local __rbnotes_cmd __rbnotes_debug
4
+
5
+ __rbnotes_process() {
6
+ }
7
+
8
+ function _rbnotes() {
9
+ local context curcontext=$curcontext stat line
10
+ typeset -A opt_args
11
+ local ret=1
12
+
13
+ _arguments \
14
+ -C \
15
+ '(- *)'{-v,--version}'[print version]' \
16
+ '(- *)'{-h,--help}'[show help]' \
17
+ '(- *)'{-c,--conf}'[config file]: :->conffile' \
18
+ '1: :__rbnotes_commands' \
19
+ '*:: :->args'
20
+
21
+ case $state in
22
+ (conffile)
23
+ _files -g "*.yml" && ret=0
24
+ ;;
25
+ (args)
26
+ case $words[1] in
27
+ (add)
28
+ _arguments \
29
+ -C \
30
+ '(-t --timestamp)'{-t,--timestamp}'[set timestamp]' \
31
+ '(-)*:: :->null_state' \
32
+ && ret=0
33
+ ;;
34
+ (export)
35
+ _directories && ret=0
36
+ ;;
37
+ (help)
38
+ _arguments \
39
+ -C \
40
+ '1: :__rbnotes_commands' \
41
+ && ret=0
42
+ ;;
43
+ (import)
44
+ _files -g '*.md' && ret=0
45
+ ;;
46
+ (list|pick)
47
+ _arguments \
48
+ -C \
49
+ '1: :__rbnotes_list_keywords' \
50
+ && ret=0
51
+ ;;
52
+ (update)
53
+ _arguments \
54
+ -C \
55
+ '(-k --keep)'{-k,--keep}'[keep timestamp]' \
56
+ '(-)*:: :->nul_state' \
57
+ && ret=0
58
+ ;;
59
+ esac
60
+ ;;
61
+ esac
62
+
63
+ return ret
64
+ }
65
+
66
+ __rbnotes_commands() {
67
+ local -a _cmds
68
+ _cmds=( $(rbnotes commands -d) )
69
+ _describe -t commands Commands _cmds
70
+ }
71
+
72
+ __rbnotes_list_keywords() {
73
+ local -a _kw _this_month _this_year
74
+ _this_month=$(date "+%Y%m")
75
+ _last_month=$(date -v-1m "+%Y%m")
76
+ _this_year=$(date "+%Y")
77
+ _kw=(
78
+ {to,today}':Today'
79
+ {ye,yesterday}':Yesterday'
80
+ {tw,this_week}':This week'
81
+ {lw,last_week}':Last week'
82
+ "${_this_month}:This month"
83
+ "${_last_month}:Last month"
84
+ "${_this_year}:This year"
85
+ )
86
+ _describe -t keywords Keywords _kw
87
+ }
88
+
89
+ _rbnotes "$@"
90
+
91
+ # Local Variables:
92
+ # mode: shell-script
93
+ # End:
@@ -57,6 +57,7 @@ rescue MissingArgumentError, MissingTimestampError,
57
57
  NoEditorError, ProgramAbortError,
58
58
  Textrepo::InvalidTimestampStringError,
59
59
  InvalidTimestampPatternError,
60
+ NoConfFileError,
60
61
  ArgumentError,
61
62
  Errno::EACCES => e
62
63
  puts e.message
@@ -8,6 +8,7 @@ module Rbnotes
8
8
  require_relative "rbnotes/conf"
9
9
  require_relative "rbnotes/utils"
10
10
  require_relative "rbnotes/commands"
11
+ require_relative "rbnotes/statistics"
11
12
 
12
13
  class << self
13
14
  def utils
@@ -61,10 +61,11 @@ Example usage:
61
61
  #{Rbnotes::NAME} commands [-d]
62
62
  #{Rbnotes::NAME} delete [TIMESTAMP]
63
63
  #{Rbnotes::NAME} export [TIMESTAMP [FILENAME]]
64
- #{Rbnotes::NAME} import FILE
64
+ #{Rbnotes::NAME} import [-m|--use-mtime] FILE
65
65
  #{Rbnotes::NAME} list [STAMP_PATTERN|KEYWORD]
66
66
  #{Rbnotes::NAME} search PATTERN [STAMP_PATTERN]
67
- #{Rbnotes::NAME} show [TIMESTAMP]
67
+ #{Rbnotes::NAME} show [TIMESTAMP...]
68
+ #{Rbnotes::NAME} statistics ([-y]|[-m])
68
69
  #{Rbnotes::NAME} update [-k] [TIMESTAMP]
69
70
 
70
71
  Further help for each command:
@@ -4,9 +4,12 @@ module Rbnotes::Commands
4
4
  # Imports a existing file which specified by the argument as a note.
5
5
  #
6
6
  # A timestamp is generated referring to the birthtime of the given
7
- # file. If birthtime is not available on the system, use mtime
7
+ # file. If birthtime is not available on the system, uses mtime
8
8
  # (modification time).
9
9
  #
10
+ # When the option, "-m" (or "--use-mtime") is specified, uses mtime
11
+ # instead of birthtime.
12
+ #
10
13
  # Occasionally, there is another note which has the same timestmap
11
14
  # in the repository. Then, tries to create a new timestamp with a
12
15
  # suffix. Unluckily, when such timestamp with a suffix already
@@ -25,11 +28,29 @@ module Rbnotes::Commands
25
28
  # execute([PATHNAME], Rbnotes::Conf or Hash) -> nil
26
29
 
27
30
  def execute(args, conf)
31
+ @opts = {}
32
+ while args.size > 0
33
+ arg = args.shift
34
+ case arg
35
+ when "-m", "--use-mtime"
36
+ @opts[:use_mtime] = true
37
+ else
38
+ args.unshift(arg)
39
+ break
40
+ end
41
+ end
42
+
28
43
  file = args.shift
29
44
  unless file.nil?
30
45
  st = File::Stat.new(file)
31
- btime = st.respond_to?(:birthtime) ? st.birthtime : st.mtime
32
- stamp = Textrepo::Timestamp.new(btime)
46
+ time = nil
47
+ if @opts[:use_mtime]
48
+ time = st.mtime
49
+ else
50
+ time = st.respond_to?(:birthtime) ? st.birthtime : st.mtime
51
+ end
52
+
53
+ stamp = Textrepo::Timestamp.new(time)
33
54
  puts "Import [%s] (timestamp [%s]) ..." % [file, stamp]
34
55
 
35
56
  repo = Textrepo.init(conf)
@@ -72,7 +93,7 @@ module Rbnotes::Commands
72
93
  puts "Cannot create a text into the repository with the" \
73
94
  " specified file [%s]." % file
74
95
  puts "For, the birthtime [%s] is identical to some notes" \
75
- " already exists in the reopsitory." % btime
96
+ " already exists in the reopsitory." % time
76
97
  puts "Change the birthtime of the target file, then retry."
77
98
  else
78
99
  puts "... Done."
@@ -86,7 +107,7 @@ module Rbnotes::Commands
86
107
  def help # :nodoc:
87
108
  puts <<HELP
88
109
  usage:
89
- #{Rbnotes::NAME} import FILE
110
+ #{Rbnotes::NAME} import [-m|--use-mtime] FILE
90
111
 
91
112
  Imports a existing file which specified by the argument as a note.
92
113
 
@@ -1,52 +1,97 @@
1
1
  module Rbnotes::Commands
2
2
 
3
3
  ##
4
- # Shows the content of the note specified by the argument. The
4
+ # Shows the content of the notes specified by arguments. Each
5
5
  # argument must be a string which can be converted into
6
6
  # Textrepo::Timestamp object.
7
7
  #
8
- # A string for Timestamp must be:
8
+ # A string for Textrepo::Timestamp must be:
9
9
  #
10
10
  # "20201106112600" : year, date, time and sec
11
11
  # "20201106112600_012" : with suffix
12
12
  #
13
- # If no argument is passed, reads the standard input for an argument.
13
+ # If no argument is passed, reads the standard input for arguments.
14
14
 
15
15
  class Show < Command
16
16
 
17
17
  def description # :nodoc:
18
- "Show the content of a note"
18
+ "Show the content of notes"
19
19
  end
20
20
 
21
21
  def execute(args, conf)
22
- stamp = Rbnotes.utils.read_timestamp(args)
23
-
22
+ stamps = Rbnotes.utils.read_multiple_timestamps(args)
24
23
  repo = Textrepo.init(conf)
25
- content = repo.read(stamp)
24
+
25
+ content = stamps.map { |stamp| [stamp, repo.read(stamp)] }.to_h
26
26
 
27
27
  pager = conf[:pager]
28
28
  unless pager.nil?
29
- require 'open3'
30
- Open3.pipeline_w(pager) { |stdin|
31
- stdin.puts content
32
- stdin.close
33
- }
29
+ puts_with_pager(pager, make_output(content))
34
30
  else
35
- puts content
31
+ puts make_output(content)
36
32
  end
37
33
  end
38
34
 
39
35
  def help # :nodoc:
40
36
  puts <<HELP
41
37
  usage:
42
- #{Rbnotes::NAME} show [TIMESTAMP]
38
+ #{Rbnotes::NAME} show [TIMESTAMP...]
43
39
 
44
- Show the content of given note. TIMESTAMP must be a fully qualified
40
+ Show the content of given notes. TIMESTAMP must be a fully qualified
45
41
  one, such "20201016165130" or "20201016165130_012" if it has a suffix.
46
42
 
47
43
  The command try to read its argument from the standard input when no
48
44
  argument was passed in the command line.
49
45
  HELP
50
46
  end
47
+
48
+ # :stopdoc:
49
+
50
+ private
51
+
52
+ def puts_with_pager(pager, output)
53
+ require "open3"
54
+ Open3.pipeline_w(pager) { |stdin|
55
+ stdin.puts output
56
+ stdin.close
57
+ }
58
+ end
59
+
60
+ require "io/console/size"
61
+
62
+ def make_output(content)
63
+ if content.size <= 1
64
+ return content.values[0]
65
+ end
66
+
67
+ _, column = IO.console_size
68
+ output = content.map { |timestamp, text|
69
+ ary = [make_heading(timestamp, [column, 72].min)]
70
+ ary.concat(text)
71
+ ary
72
+ }
73
+
74
+ output = insert_delimiter(output, "")
75
+ output.flatten
76
+ end
77
+
78
+ def make_heading(timestamp, column)
79
+ stamp_str = timestamp.to_s
80
+ length = column - (stamp_str.size + 2)
81
+ "#{stamp_str} #{Array.new(length, '-').join}"
82
+ end
83
+
84
+ def insert_delimiter(ary, delimiter = "")
85
+ result = []
86
+ ary.each { |e|
87
+ result << e
88
+ result << delimiter
89
+ }
90
+ result.delete_at(-1)
91
+ result
92
+ end
93
+
94
+ # :startdoc:
95
+
51
96
  end
52
97
  end
@@ -0,0 +1,55 @@
1
+ module Rbnotes::Commands
2
+ ##
3
+ # Shows statistics.
4
+
5
+ class Statistics < Command
6
+
7
+ def description # :nodoc:
8
+ "Show statistics values"
9
+ end
10
+
11
+ def execute(args, conf)
12
+ report = :total
13
+ while args.size > 0
14
+ arg = args.shift
15
+ case arg
16
+ when "-y", "--yearly"
17
+ report = :yearly
18
+ break
19
+ when "-m", "--monthly"
20
+ report = :monthly
21
+ break
22
+ else
23
+ args.unshift(arg)
24
+ raise ArgumentError, "invalid option or argument: %s" % args.join(" ")
25
+ end
26
+ end
27
+
28
+ stats = Rbnotes::Statistics.new(conf)
29
+ case report
30
+ when :yearly
31
+ stats.yearly_report
32
+ when :monthly
33
+ stats.monthly_report
34
+ else
35
+ stats.total_report
36
+ end
37
+ end
38
+
39
+ def help
40
+ puts <<HELP
41
+ usage:
42
+ #{Rbnotes::NAME} statistics ([-y|--yearly]|[-m|--monthly])
43
+
44
+ option:
45
+ -y, --yearly : print yearly report
46
+ -m, --monthly : print monthly report
47
+
48
+ Show statistics.
49
+
50
+ In the version #{Rbnotes::VERSION}, only number of notes is supported.
51
+ HELP
52
+ end
53
+
54
+ end
55
+ end
@@ -32,16 +32,18 @@ module Rbnotes
32
32
  DIRNAME_COMMON_CONF = ".config"
33
33
 
34
34
  def initialize(conf_path = nil) # :nodoc:
35
- @conf_path = conf_path || File.join(base_path, FILENAME_CONF)
36
-
35
+ @conf_path = conf_path
37
36
  @conf = {}
38
- if FileTest.exist?(@conf_path)
37
+
38
+ if use_default_values?
39
+ @conf.merge!(DEFAULT_VALUES)
40
+ else
41
+ @conf_path ||= default_conf_file
42
+ raise NoConfFileError, @conf_path unless File.exist?(@conf_path)
43
+
39
44
  yaml_str = File.open(@conf_path, "r") { |f| f.read }
40
45
  @conf = YAML.load(yaml_str)
41
- else
42
- @conf.merge(DEFAULT_VALUES)
43
46
  end
44
- self
45
47
  end
46
48
 
47
49
  def_delegators(:@conf,
@@ -99,10 +101,19 @@ module Rbnotes
99
101
  end
100
102
  return path
101
103
  end
102
- end
104
+
105
+ def default_conf_file
106
+ File.join(base_path, FILENAME_CONF)
107
+ end
108
+
109
+ def use_default_values?
110
+ @conf_path.nil? && !File.exist?(default_conf_file)
111
+ end
103
112
 
104
113
  # :startdoc:
105
114
 
115
+ end
116
+
106
117
  class << self
107
118
  ##
108
119
  # Gets the instance of Rbnotes::Conf. An optional argument is to
@@ -13,6 +13,7 @@ module Rbnotes
13
13
  PROGRAM_ABORT = "External program was aborted: %s"
14
14
  UNKNOWN_KEYWORD = "Unknown keyword: %s"
15
15
  INVALID_TIMESTAMP_PATTERN = "Invalid timestamp pattern: %s"
16
+ NO_CONF_FILE = "No configuration file: %s"
16
17
  end
17
18
 
18
19
  # :startdoc:
@@ -75,4 +76,14 @@ module Rbnotes
75
76
  end
76
77
  end
77
78
 
79
+ ##
80
+ # An error raised when the specified configuration file does not
81
+ # exist.
82
+
83
+ class NoConfFileError < Error
84
+ def initialize(filename)
85
+ super(ErrMsg::NO_CONF_FILE % filename)
86
+ end
87
+ end
88
+
78
89
  end
@@ -0,0 +1,101 @@
1
+ module Rbnotes
2
+ ##
3
+ # Calculates statistics of the repository.
4
+ class Statistics
5
+ include Enumerable
6
+
7
+ def initialize(conf)
8
+ @repo = Textrepo.init(conf)
9
+ @values = construct_values(@repo)
10
+ end
11
+
12
+ def total_report
13
+ puts @repo.entries.size
14
+ end
15
+
16
+ def yearly_report
17
+ self.each_year { |year, monthly_values|
18
+ num_of_notes = monthly_values.map { |_mon, values| values.size }.sum
19
+ puts "#{year}: #{num_of_notes}"
20
+ }
21
+ end
22
+
23
+ def monthly_report
24
+ self.each { |year, mon, values|
25
+ num_of_notes = values.size
26
+ puts "#{year}/#{mon}: #{num_of_notes}"
27
+ }
28
+ end
29
+
30
+ def each(&block)
31
+ if block.nil?
32
+ @values.map { |year, monthly_values|
33
+ monthly_values.each { |mon, values|
34
+ [year, mon, values]
35
+ }
36
+ }.to_enum(:each)
37
+ else
38
+ @values.each { |year, monthly_values|
39
+ monthly_values.each { |mon, values|
40
+ yield [year, mon, values]
41
+ }
42
+ }
43
+ end
44
+ end
45
+
46
+ def years
47
+ @values.keys
48
+ end
49
+
50
+ def months(year)
51
+ @values[year] || []
52
+ end
53
+
54
+ def each_year(&block)
55
+ if block.nil?
56
+ @values.map { |year, monthly_values|
57
+ [year, monthly_values]
58
+ }.to_enum(:each)
59
+ else
60
+ @values.each { |year, monthly_values|
61
+ yield [year, monthly_values]
62
+ }
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def construct_values(repo)
69
+ values = {}
70
+ repo.each { |timestamp, text|
71
+ value = StatisticValue.new(timestamp, text)
72
+ y = value.year
73
+ m = value.mon
74
+ values[y] ||= {}
75
+ values[y][m] ||= []
76
+
77
+ values[y][m] << value
78
+ }
79
+ values
80
+ end
81
+
82
+ class StatisticValue
83
+
84
+ attr_reader :lines
85
+
86
+ def initialize(timestamp, text)
87
+ @timestamp = timestamp
88
+ @lines = text.size
89
+ end
90
+
91
+ def year
92
+ @timestamp[:year]
93
+ end
94
+
95
+ def mon
96
+ @timestamp[:mon]
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -103,23 +103,16 @@ module Rbnotes
103
103
  end
104
104
 
105
105
  ##
106
- # Reads an argument from the IO object. Typically, it is intended
107
- # to be used with STDIN.
106
+ # Generates multiple Textrepo::Timestamp objects from the command
107
+ # line arguments. When no argument is given, try to read from
108
+ # STDIN.
108
109
  #
109
110
  # :call-seq:
110
- # read_arg(IO) -> String
111
+ # read_multiple_timestamps(args) -> [String]
111
112
 
112
- def read_arg(io)
113
- # assumes the reading line looks like:
114
- #
115
- # foo bar baz ...
116
- #
117
- # then, only the first string is interested
118
- begin
119
- io.gets.split(":")[0].rstrip
120
- rescue NoMethodError => _
121
- nil
122
- end
113
+ def read_multiple_timestamps(args)
114
+ strings = args.size < 1 ? read_multiple_args($stdin) : args
115
+ strings.map { |str| Textrepo::Timestamp.parse_s(str) }
123
116
  end
124
117
 
125
118
  ##
@@ -241,6 +234,41 @@ module Rbnotes
241
234
  # :stopdoc:
242
235
 
243
236
  private
237
+
238
+ ##
239
+ # Reads an argument from the IO object. Typically, it is intended
240
+ # to be used with STDIN.
241
+ #
242
+ # :call-seq:
243
+ # read_arg(IO) -> String
244
+
245
+ def read_arg(io)
246
+ read_multiple_args(io)[0]
247
+ end
248
+
249
+ ##
250
+ # Reads arguments from the IO object. Typically, it is intended
251
+ # to be used with STDIN.
252
+ #
253
+ # :call-seq:
254
+ # read_multiple_arg(IO) -> [String]
255
+
256
+ def read_multiple_args(io)
257
+ strings = io.readlines
258
+ strings.map { |str|
259
+ # assumes the reading line looks like:
260
+ #
261
+ # foo bar baz ...
262
+ #
263
+ # then, only the first string is interested
264
+ begin
265
+ str.split(":")[0].rstrip
266
+ rescue NoMethodError => _
267
+ nil
268
+ end
269
+ }.compact
270
+ end
271
+
244
272
  def search_in_path(name)
245
273
  search_paths = ENV["PATH"].split(":")
246
274
  found = search_paths.map { |path|
@@ -1,4 +1,4 @@
1
1
  module Rbnotes
2
- VERSION = "0.4.10"
3
- RELEASE = "2020-11-20"
2
+ VERSION = "0.4.11"
3
+ RELEASE = "2020-12-07"
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.10
4
+ version: 0.4.11
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-20 00:00:00.000000000 Z
11
+ date: 2020-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: textrepo
@@ -59,6 +59,7 @@ files:
59
59
  - conf/config.yml
60
60
  - conf/config_deve.yml
61
61
  - conf/config_test.yml
62
+ - etc/zsh/_rbnotes
62
63
  - exe/rbnotes
63
64
  - lib/rbnotes.rb
64
65
  - lib/rbnotes/commands.rb
@@ -72,9 +73,11 @@ files:
72
73
  - lib/rbnotes/commands/pick.rb
73
74
  - lib/rbnotes/commands/search.rb
74
75
  - lib/rbnotes/commands/show.rb
76
+ - lib/rbnotes/commands/statistics.rb
75
77
  - lib/rbnotes/commands/update.rb
76
78
  - lib/rbnotes/conf.rb
77
79
  - lib/rbnotes/error.rb
80
+ - lib/rbnotes/statistics.rb
78
81
  - lib/rbnotes/utils.rb
79
82
  - lib/rbnotes/version.rb
80
83
  - rbnotes.gemspec