rbnotes 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e3b44e20631a0216391b389aee7faca4ecebe31502bbff92606c938023a98240
4
+ data.tar.gz: 4a39041ad77631da13be01ebde0745edd38f22f52b5c9ad2ae1a002053739c1b
5
+ SHA512:
6
+ metadata.gz: e98fabf28f56131caf2ec40c1111aa74a0819d923937cd033eeed6921c5ce43102657e8c3d787a8b9718b3057e7505921d2f7d316a327a041e80dcc2bce4b2ae
7
+ data.tar.gz: 672cd941dd68c545056c752a714bc29df9a9de5b14ce00b8c6568562df619e796c7cea77b376380bb3c32c06d680f834552c3c6367033bf2ab02b7460c4e1b0f
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.2
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,40 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/)
5
+ and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+ Nothing to record here.
9
+
10
+ ## [0.2.1] - 2020-10-25
11
+ ### Added
12
+ - Add feature to load the configuration from an external file.
13
+ - Add description about the configuration file in README.md
14
+
15
+ ## [0.2.0] - 2020-10-23
16
+ ### Added
17
+ - Add more commands (add/update/delete).
18
+ - Add and update commands use a external editor to edit a note.
19
+ - Delete command remove the specified note from the repository.
20
+ - Add a new task into `Rakefile` to generate RI docs.
21
+ - The intention of the task is to verify RI docs.
22
+
23
+ ### Fixed
24
+ - Refactor some tests.
25
+
26
+ ## [0.1.3] - 2020-10-15
27
+ ### Fixed
28
+ - Add help text for the `conf` command.
29
+
30
+ ## [0.1.2] - 2020-10-15
31
+ ### Fixed
32
+ - Adapt the API change in `textrepo` (0.4.0).
33
+
34
+ ## [0.1.0] - 2020-10-12
35
+ ### Added
36
+ - Import files those are part of `examples` in the `textrepo` gem.
37
+ - All commands in the example version works fine.
38
+ - Wrote tests for all commands.
39
+ - All files those are generated by `bundler`.
40
+ - Add CHANGELOG.md to start recording changes.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rbnotes.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 13.0"
7
+ gem "minitest", "~> 5.0"
8
+
9
+ gem 'textrepo', "~> 0.4"
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rbnotes (0.2.1)
5
+ textrepo (~> 0.4)
6
+ unicode-display_width (~> 1.7)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ minitest (5.14.2)
12
+ rake (13.0.1)
13
+ textrepo (0.4.2)
14
+ unicode-display_width (1.7.0)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ minitest (~> 5.0)
21
+ rake (~> 13.0)
22
+ rbnotes!
23
+ textrepo (~> 0.4)
24
+
25
+ BUNDLED WITH
26
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 mnbi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,156 @@
1
+ # Rbnotes
2
+
3
+ [![Build Status](https://travis-ci.org/mnbi/rbnotes.svg?branch=main)](https://travis-ci.org/mnbi/rbnotes)
4
+
5
+ Rbnotes is a simple utility to write a note in the single repository.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'rbnotes, github: 'mnbi/rbnotes, branch: 'main'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ ## Usage
20
+
21
+ General syntax is:
22
+
23
+ ``` shell
24
+ rbnotes [global_opts] [command] [command_opts] [args]
25
+ ```
26
+
27
+ ### Commands
28
+
29
+ - import
30
+ - imports existing files
31
+ - list
32
+ - lists notes in the repository with their timestamps and subject
33
+ - show
34
+ - shows the content of a note
35
+ - add
36
+ - adds a new note to the repository using an external editor
37
+ - update
38
+ - update the content of the specified note using an external editor
39
+ - delete
40
+ - deletes the specified note form the repository
41
+
42
+ ### Configuration file
43
+
44
+ The `rbnotes` command reads the configuration file during its startup.
45
+ This section describes the specification of the configuration file.
46
+
47
+ #### Location
48
+
49
+ Searches the configuration file in the following order:
50
+
51
+ 1. `$XDG_CONFIG_HOME/rbnotes/config.yml`
52
+ 2. `$HOME/.config/rbnotes/config.yml`
53
+
54
+ None of them is found, then the default configuration is used.
55
+
56
+ #### Content
57
+
58
+ The format of the configuration file must be written in YAML.
59
+
60
+ The configuration of `rbnotes` is represented as a Hash object in the
61
+ program code. So, each line of the configuration YAML looks like:
62
+
63
+ > name: value
64
+
65
+ Name must be written as Ruby's symbol, such `:repository_type`. On
66
+ the other hand, value will be written in 2 types. Some are Ruby's
67
+ symbols, the others are strings surrounded with double/single
68
+ quotations.
69
+
70
+ A real example:
71
+
72
+ ``` yaml
73
+ ---
74
+ :run_mode: :development
75
+ :repository_type: :file_system
76
+ :repository_name: "notes"
77
+ :repository_base: "~"
78
+ :pager: "bat"
79
+ :editor: "/usr/local/bin/emacsclient"
80
+ ```
81
+
82
+ #### Variables
83
+
84
+ ##### :run-mode (mandatory)
85
+
86
+ - :production (default)
87
+ - :development
88
+ - :test
89
+
90
+ The run-mode affects to behavior of `rbnotes`, such logging
91
+ information, the location of the repository, ..., etc.
92
+
93
+ ##### :repository_type (mandatory)
94
+
95
+ - :file_system (default)
96
+
97
+ This value depends on classes those derived from
98
+ `Textrepo::Repository` of `textrepo`. Currently (`textrepo` 0.4.x),
99
+ Textrepo::FileSystemRepository is the only one class usable for
100
+ `rbnotes`.
101
+
102
+ ##### :repository_name (mandatory)
103
+
104
+ User can set an arbitrary string as this value. However, the value
105
+ will be used as a part of the repository location in the file system.
106
+ Some characters in the string may cause a problem.
107
+
108
+ In addition to this, the run-mode affects the actual value when it is
109
+ used in the program.
110
+
111
+ | original | :production | :development | :test |
112
+ |:------ |:------------|:-------------|:-----------|
113
+ | notes | notes | notes_dev | notes_test |
114
+
115
+ ##### :repository_base (mandatory)
116
+
117
+ This value is used as a base directory of the repository. That is,
118
+ the values constructs the root path of the repository with
119
+ the `:repository_name` value. It would be:
120
+
121
+ > :repository_base/:repository_name
122
+
123
+ The value must be an absolute path. The short-hand notation of the
124
+ home directory ("~") is usable.
125
+
126
+ ##### Miscellaneous variables (optional)
127
+
128
+ - :pager : specify a pager program
129
+ - :editor : specify a editor program
130
+
131
+ ##### Default values for mandatory variables
132
+
133
+ All mandatory variables have their default values. Here is the list
134
+ of them:
135
+
136
+ | variable | default value |
137
+ |:-----------------|:--------------|
138
+ | :run_mode | :production |
139
+ | :repository_type | :file_system |
140
+ | :repository_name | "notes" |
141
+ | :repository_base | "~" |
142
+
143
+ ## Development
144
+
145
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
146
+
147
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
148
+
149
+ ## Contributing
150
+
151
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mnbi/rbnotes.
152
+
153
+
154
+ ## License
155
+
156
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,30 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
11
+
12
+ task test: [:clean, :setup_test]
13
+
14
+ desc "Setup test data"
15
+ task :setup_test do
16
+ print "Set up to execute tests..."
17
+ load("test/fixtures/setup_test_repo.rb", true)
18
+ puts "done."
19
+ end
20
+
21
+ CLEAN << "test/sandbox"
22
+ CLOBBER << "test/fixtures/test_repo"
23
+
24
+ require "rdoc/task"
25
+
26
+ RDoc::Task.new do |rdoc|
27
+ rdoc.generator = "ri"
28
+ rdoc.rdoc_dir = "doc"
29
+ rdoc.rdoc_files.include("lib/**/*.rb")
30
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rbnotes"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rbnotes"
4
+
5
+ include Rbnotes
6
+
7
+ DEBUG = true
8
+
9
+ class App
10
+ def run(args)
11
+ cmd = args.shift
12
+ Commands.load(cmd).execute(args, Rbnotes.conf)
13
+ end
14
+ end
15
+
16
+ app = App.new
17
+ begin
18
+ app.run(ARGV)
19
+ rescue MissingArgumentError, MissingTimestampError,
20
+ NoEditorError, ProgramAbortError => e
21
+ puts e.message
22
+ exit 1
23
+ end
@@ -0,0 +1,9 @@
1
+ require "textrepo"
2
+
3
+ module Rbnotes
4
+ require_relative "rbnotes/version"
5
+ require_relative "rbnotes/error"
6
+ require_relative "rbnotes/conf"
7
+ require_relative "rbnotes/utils"
8
+ require_relative "rbnotes/commands"
9
+ end
@@ -0,0 +1,144 @@
1
+ module Rbnotes
2
+ ##
3
+ # This module defines all command classes of rbnotes. Each command
4
+ # class must be derived from Rbnotes::Commands::Command class.
5
+ module Commands
6
+ ##
7
+ # The base class for a command class.
8
+ class Command
9
+ ##
10
+ # :call-seq:
11
+ # Array, Hash -> nil
12
+ # - Array: arguments for each command
13
+ # - Hash : rbnotes configuration
14
+ #
15
+ def execute(args, conf)
16
+ Builtins::DEFAULT_CMD.new.execute(args, conf)
17
+ end
18
+ end
19
+
20
+ # :stopdoc:
21
+
22
+ # Built-in commands:
23
+ # - repo: prints the absolute path of the repository.
24
+ # - conf: prints all of the current configuration settings.
25
+ # - stamp: converts given TIME_STR into a timestamp.
26
+ # - time: converts given STAMP into a time string.
27
+ module Builtins
28
+ class Help < Command
29
+ def execute(_, _)
30
+ puts <<USAGE
31
+ usage: rbnotes [command] [args]
32
+
33
+ command:
34
+ import FILE : import a FILE into the repository
35
+ list NUM : list NUM notes
36
+ show STAMP : show the note specified with STAMP
37
+ delete STAMP : delete the note specified with STAMP
38
+
39
+ conf : print the current configuraitons
40
+ repo : print the repository path
41
+ stamp TIME_STR : convert TIME_STR into a timestamp
42
+ time STAMP : convert STAMP into a time string
43
+ version : print version
44
+ help : show help
45
+ USAGE
46
+ end
47
+ end
48
+
49
+ class Version < Command
50
+ def execute(_, _)
51
+ rbnotes_version = "rbnotes #{Rbnotes::VERSION} (#{Rbnotes::RELEASE})"
52
+ textrepo_version = "textrepo #{Textrepo::VERSION}"
53
+ puts "#{rbnotes_version} [#{textrepo_version}]"
54
+ end
55
+ end
56
+
57
+ class Repo < Command
58
+ def execute(_, conf)
59
+ name = conf[:repository_name]
60
+ base = conf[:repository_base]
61
+ type = conf[:repository_type]
62
+
63
+ puts case type
64
+ when :file_system
65
+ "#{base}/#{name}"
66
+ else
67
+ "#{base}/#{name}"
68
+ end
69
+ end
70
+ end
71
+
72
+ class Conf < Command
73
+ def execute(_, conf)
74
+ conf.keys.sort.each { |k|
75
+ puts "#{k}=#{conf[k]}"
76
+ }
77
+ end
78
+ end
79
+
80
+ require "time"
81
+
82
+ class Stamp < Command
83
+ def execute(args, _)
84
+ time_str = args.shift
85
+ unless time_str.nil?
86
+ puts Textrepo::Timestamp.new(::Time.parse(time_str)).to_s
87
+ else
88
+ puts "not specified TIME_STR"
89
+ super
90
+ end
91
+ end
92
+ end
93
+
94
+ class Time < Command
95
+ def execute(args, _)
96
+ stamp = args.shift
97
+ unless stamp.nil?
98
+ puts ::Time.new(*Textrepo::Timestamp.split_stamp(stamp).map(&:to_i)).to_s
99
+ else
100
+ puts "not specified STAMP"
101
+ super
102
+ end
103
+ end
104
+ end
105
+
106
+ DEFAULT_CMD = Help
107
+ end
108
+
109
+ DEFAULT_CMD_NAME = "help"
110
+
111
+ # :startdoc:
112
+
113
+ class << self
114
+ ##
115
+ # Loads a class to perfom the command, then returns an instance
116
+ # of the class.
117
+ #
118
+ # :call-seq:
119
+ # "import" -> an object of Rbnotes::Commnads::Import
120
+ # "list" -> an object of Rbnotes::Commands::List
121
+ # "show" -> an object of Rbnotes::Commands::Show
122
+ #
123
+ def load(cmd_name)
124
+ cmd_name ||= DEFAULT_CMD_NAME
125
+ klass_name = cmd_name.capitalize
126
+
127
+ klass = nil
128
+ if Builtins.const_defined?(klass_name, false)
129
+ klass = Builtins::const_get(klass_name, false)
130
+ else
131
+ begin
132
+ require_relative "commands/#{cmd_name}"
133
+ klass = const_get(klass_name, false)
134
+ rescue LoadError => _
135
+ STDERR.puts "unknown command: #{cmd_name}"
136
+ klass = Builtins::DEFAULT_CMD
137
+ end
138
+ end
139
+ klass.new
140
+ end
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,49 @@
1
+ module Rbnotes::Commands
2
+ ##
3
+ # Adds a new note to the repository. A new timestamp is generated
4
+ # at the execution time, then it is attached to the note. If the
5
+ # timestamp has already existed in the repository, the command
6
+ # fails.
7
+ #
8
+ # This command starts the external editor program to prepare text to
9
+ # store. The editor program will be searched in the following order:
10
+ #
11
+ # 1. conf[:editor] (conf is the 1st arg of execute method)
12
+ # 2. ENV["EDITOR"]
13
+ # 3. "nano"
14
+ # 4. "vi"
15
+ #
16
+ # If none of the above editor is available, the command fails.
17
+ #
18
+ class Add < Command
19
+ include ::Rbnotes::Utils
20
+
21
+ def execute(args, conf)
22
+ newstamp = Textrepo::Timestamp.new(Time.now)
23
+
24
+ candidates = [conf[:editor], ENV["EDITOR"], "nano", "vi"].compact
25
+ editor = find_program(candidates)
26
+ raise Rbnotes::NoEditorError, candidates if editor.nil?
27
+
28
+ tmpfile = run_with_tmpfile(editor, newstamp.to_s)
29
+ text = File.readlines(tmpfile)
30
+
31
+ repo = Textrepo.init(conf)
32
+ begin
33
+ repo.create(newstamp, text)
34
+ rescue Textrepo::DuplicateTimestampError => e
35
+ puts e.message
36
+ puts "Just wait a second, then retry."
37
+ rescue Textrepo::EmptyTextError => e
38
+ puts e.message
39
+ rescue StandardError => e
40
+ puts e.message
41
+ else
42
+ puts "Add a note [%s]" % newstamp.to_s
43
+ ensure
44
+ # Don't forget to remove the temporary file.
45
+ File.delete(tmpfile)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,35 @@
1
+ # :markup: markdown
2
+
3
+ # Delete command deletes one note in the repository, which specified
4
+ # with a given timestamp string. The timestamp string must be a fully
5
+ # qualified one, like "20201016165130". The argument to specify a
6
+ # note is mandatory. If no argument was passed, it would print help
7
+ # message and exit.
8
+ #
9
+ # It does nothing when the specified note does not exist except to
10
+ # print error message.
11
+
12
+ # :stopdoc:
13
+
14
+ module Rbnotes
15
+ class Commands::Delete < Commands::Command
16
+ def execute(args, conf)
17
+ stamp_str = args.shift
18
+ unless stamp_str.nil?
19
+ repo = Textrepo.init(conf)
20
+ begin
21
+ stamp = Textrepo::Timestamp.parse_s(stamp_str)
22
+ repo.delete(stamp)
23
+ rescue Textrepo::MissingTimestampError => e
24
+ puts e.message
25
+ rescue StandardError => e
26
+ puts e.message
27
+ else
28
+ puts "Delete [%s]" % stamp.to_s
29
+ end
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,62 @@
1
+ module Rbnotes
2
+ class Commands::Import < Commands::Command
3
+ def execute(args, conf)
4
+ file = args.shift
5
+ unless file.nil?
6
+ st = File::Stat.new(file)
7
+ btime = st.respond_to?(:birthtime) ? st.birthtime : st.mtime
8
+ stamp = Textrepo::Timestamp.new(btime)
9
+ puts "Import [%s] (timestamp [%s]) ..." % [file, stamp]
10
+
11
+ repo = Textrepo.init(conf)
12
+ content = nil
13
+ File.open(file, "r") {|f| content = f.readlines(chomp: true)}
14
+
15
+ count = 0
16
+ while count <= 999
17
+ begin
18
+ repo.create(stamp, content)
19
+ break # success to create a note
20
+ rescue Textrepo::DuplicateTimestampError => _
21
+ puts "A text with the timestamp [%s] has been already exists" \
22
+ " in the repository." % stamp
23
+
24
+ repo_text = repo.read(stamp)
25
+ if content == repo_text
26
+ # if the content is the same to the target file,
27
+ # the specified file has been already imported.
28
+ # Then, there is nothing to do. Just exit.
29
+ puts "The note [%s] in the repository exactly matches" \
30
+ " the specified file." % stamp
31
+ puts "It seems there is no need to import the file [%s]." % file
32
+ exit # normal end
33
+ else
34
+ puts "The text in the repository does not match the" \
35
+ " specified file."
36
+ count += 1
37
+ stamp = Textrepo::Timestamp.new(stamp.time, count)
38
+ puts "Try to create a note again with a new " \
39
+ "timestamp [%s]." % stamp
40
+ end
41
+ rescue Textrepo::EmptyTextError => _
42
+ puts "... aborted."
43
+ puts "The specified file is empyt."
44
+ exit 1 # error
45
+ end
46
+ end
47
+ if count > 999
48
+ puts "Cannot create a text into the repository with the" \
49
+ " specified file [%s]." % file
50
+ puts "For, the birthtime [%s] is identical to some notes" \
51
+ " already exists in the reopsitory." % btime
52
+ puts "Change the birthtime of the target file, then retry."
53
+ else
54
+ puts "... Done."
55
+ end
56
+ else
57
+ puts "not supecified FILE"
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,57 @@
1
+ require "unicode/display_width"
2
+ require "io/console/size"
3
+
4
+ module Rbnotes
5
+ class Commands::List < Commands::Command
6
+ def execute(args, conf)
7
+ @row, @column = IO.console_size
8
+ max = (args.shift || @row - 3).to_i
9
+
10
+ @repo = Textrepo.init(conf)
11
+ notes = @repo.entries.sort{|a, b| b <=> a}
12
+ notes[0, max].each { |timestamp_str|
13
+ puts make_headline(timestamp_str)
14
+ }
15
+ end
16
+
17
+ private
18
+ TIMESTAMP_STR_MAX_WIDTH = "yyyymoddhhmiss_sfx".size
19
+ # Makes a headline with the timestamp and subject of the notes, it
20
+ # looks like as follows:
21
+ #
22
+ # |<------------------ console column size --------------------->|
23
+ # +-- timestamp ---+ +--- subject (the 1st line of each note) --+
24
+ # | | | |
25
+ # 20101010001000_123: # I love Macintosh. [EOL]
26
+ # 20100909090909_999: # This is very very long long loooong subje[EOL]
27
+ # ++
28
+ # ^--- delimiter (2 characters)
29
+ #
30
+ # The subject part will truncate when it is long.
31
+ def make_headline(timestamp_str)
32
+
33
+ delimiter = ": "
34
+ subject_width = @column - TIMESTAMP_STR_MAX_WIDTH - delimiter.size - 1
35
+
36
+ subject = @repo.read(Textrepo::Timestamp.parse_s(timestamp_str))[0]
37
+ prefix = '# '
38
+ subject = prefix + subject.lstrip if subject[0, 2] != prefix
39
+
40
+ ts_part = "#{timestamp_str} "[0..(TIMESTAMP_STR_MAX_WIDTH - 1)]
41
+ sj_part = truncate_str(subject, subject_width)
42
+
43
+ ts_part + delimiter + sj_part
44
+ end
45
+
46
+ def truncate_str(str, size)
47
+ count = 0
48
+ result = ""
49
+ str.each_char { |c|
50
+ count += Unicode::DisplayWidth.of(c)
51
+ break if count > size
52
+ result << c
53
+ }
54
+ result
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,25 @@
1
+ module Rbnotes
2
+ class Commands::Show < Commands::Command
3
+ def execute(args, conf)
4
+ stamp_str = args.shift
5
+ unless stamp_str.nil?
6
+ repo = Textrepo.init(conf)
7
+ stamp = Textrepo::Timestamp.parse_s(stamp_str)
8
+ content = repo.read(stamp)
9
+
10
+ pager = conf[:pager]
11
+ unless pager.nil?
12
+ require 'open3'
13
+ Open3.pipeline_w(pager) { |stdin|
14
+ stdin.puts content
15
+ stdin.close
16
+ }
17
+ else
18
+ puts content
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module Rbnotes::Commands
2
+ ##
3
+ # Updates the content of the note associated with given timestamp.
4
+ # Actual modification is done interactively by the external editor.
5
+ # The timestamp associated with the note will be updated to new one,
6
+ # which is generated while the command exection.
7
+ #
8
+ # A timestamp string must be specified as the only argument. It
9
+ # must exactly match to the one of the target note in the
10
+ # repository. When the given timestamp was not found, the command
11
+ # fails.
12
+ #
13
+ # Timestamp which is associated to the target note will be newly
14
+ # generated with the command execution time. That is, the timestamp
15
+ # before the command exection will be obsolete.
16
+ #
17
+ # This command starts the external editor program to edit the
18
+ # content of the note. The editor program will be searched as same
19
+ # as add command.
20
+ #
21
+ # If none of editors is available, the command fails.
22
+ #
23
+ class Update < Command
24
+ include ::Rbnotes::Utils
25
+
26
+ ##
27
+ # The 1st and only one argument is the timestamp to speficy the
28
+ # note to update. Returns the new timestamp which is associated
29
+ # to the note updated.
30
+ #
31
+ # :call-seq:
32
+ # "20201020112233" -> "20201021123400"
33
+ #
34
+ def execute(args, conf)
35
+ raise Rbnotes::MissingArgumentError, args if args.size < 1
36
+
37
+ target_stamp = nil
38
+ begin
39
+ target_stamp = Textrepo::Timestamp.parse_s(args.shift)
40
+ rescue ArgumentError => e
41
+ raise Rbnotes::MissingArgumentError, args
42
+ end
43
+
44
+ editor = find_editor(conf[:editor])
45
+
46
+ repo = Textrepo.init(conf)
47
+
48
+ text = nil
49
+ begin
50
+ text = repo.read(target_stamp)
51
+ rescue Textrepo::MissingTimestampError => _
52
+ raise Rbnotes::MissingTimestampError, target_stamp
53
+ end
54
+
55
+ tmpfile = run_with_tmpfile(editor, target_stamp.to_s, text)
56
+ text = File.readlines(tmpfile)
57
+
58
+ unless text.empty?
59
+ newstamp = nil
60
+ begin
61
+ newstamp = repo.update(target_stamp, text)
62
+ rescue StandardError => e
63
+ puts e.message
64
+ else
65
+ puts "Update the note [%s -> %s]" % [target_stamp, newstamp]
66
+ ensure
67
+ # Don't forget to remove the temporary file.
68
+ File.delete(tmpfile)
69
+ end
70
+ else
71
+ puts "Nothing is updated, since the specified content is empty."
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,118 @@
1
+ require "forwardable"
2
+ require "yaml"
3
+
4
+ module Rbnotes
5
+ ##
6
+ # Holds the configuration settings. Each value of setting can be
7
+ # retrieved like a Hash object. Here is some examples.
8
+ #
9
+ # conf = Rbnotes.conf
10
+ # type = conf[:repository_type]
11
+ # name = conf[:repository_name]
12
+ # base = conf[:repository_base]
13
+
14
+ class Conf
15
+ extend Forwardable
16
+ include Enumerable
17
+
18
+ ##
19
+ # Name of the file to store configuration settings.
20
+
21
+ FILENAME_CONF = "config.yml"
22
+
23
+ ##
24
+ # Name of the directory indicates which belongs to "rbnotes".
25
+
26
+ DIRNAME_RBNOTES = "rbnotes"
27
+
28
+ ##
29
+ # Name of the directory which is used to indicate to put
30
+ # configuration files. Many tools use this name as the role.
31
+
32
+ DIRNAME_COMMON_CONF = ".config"
33
+
34
+ def initialize(conf_path = nil) # :nodoc:
35
+ @conf_path = conf_path || File.join(base_path, FILENAME_CONF)
36
+
37
+ @conf = {}
38
+ if FileTest.exist?(@conf_path)
39
+ yaml_str = File.open(@conf_path, "r") { |f| f.read }
40
+ @conf = YAML.load(yaml_str)
41
+ else
42
+ @conf.merge(DEFAULT_VALUES)
43
+ end
44
+ self
45
+ end
46
+
47
+ def_delegators(:@conf,
48
+ :empyt?, :keys, :values,
49
+ :has_key?, :include?, :key?, :member?,
50
+ :length, :size,
51
+ :to_s,
52
+ :each, :each_pair)
53
+
54
+ ##
55
+ # Retrieves the value for the given key.
56
+ #
57
+ # :call-seq:
58
+ # self[sym] -> value
59
+
60
+ def [](sym)
61
+ mode = @conf[:run_mode] || :production
62
+ value = @conf[sym] || DEFAULT_VALUES[sym]
63
+ if [:repository_name].include?(sym)
64
+ value += MODE_POSTFIX[mode]
65
+ end
66
+ value
67
+ end
68
+
69
+ # :stopdoc:
70
+
71
+ def initialize_copy(_)
72
+ @conf = @conf.dup
73
+ end
74
+
75
+ def freeze; @conf.freeze; super; end
76
+ def taint; @conf.taint; super; end
77
+ def untaint; @conf.untaint; super; end
78
+
79
+ private
80
+ DEFAULT_VALUES = {
81
+ :repository_type => :file_system,
82
+ :repository_name => "notes",
83
+ :repository_base => "~",
84
+ }
85
+
86
+ MODE_POSTFIX = {
87
+ :production => "",
88
+ :development => "_deve",
89
+ :test => "_test",
90
+ }
91
+
92
+ def base_path
93
+ path = nil
94
+ xdg, user = ["XDG_CONFIG_HOME", "HOME"].map{|n| ENV[n]}
95
+ if xdg
96
+ path = File.join(File.expand_path(xdg), DIRNAME_RBNOTES)
97
+ else
98
+ path = File.join(user, DIRNAME_COMMON_CONF, DIRNAME_RBNOTES)
99
+ end
100
+ return path
101
+ end
102
+ end
103
+
104
+ # :startdoc:
105
+
106
+ class << self
107
+ ##
108
+ # Gets the instance of Rbnotes::Conf. An optional argument is to
109
+ # specify the absolute path for the configuration file.
110
+ #
111
+ # :call-seq:
112
+ # conf(String) => Rbnotes::Conf
113
+
114
+ def conf(conf_path = nil)
115
+ Conf.new(conf_path)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,54 @@
1
+ module Rbnotes
2
+ ##
3
+ # A base class for each error class of rbnotes.
4
+ #
5
+ class Error < StandardError; end
6
+
7
+ # :stopdoc:
8
+ module ErrMsg
9
+ MISSING_ARGUMENT = "missing argument: %s"
10
+ MISSING_TIMESTAMP = "missing timestamp: %s"
11
+ NO_EDITOR = "No editor is available: %s"
12
+ PROGRAM_ABORT = "External program was aborted: %s"
13
+ end
14
+
15
+ # :startdoc:
16
+
17
+ ##
18
+ # An error raised if an essential argument was missing.
19
+ #
20
+ class MissingArgumentError < Error
21
+ def initialize(args)
22
+ super(ErrMsg::MISSING_ARGUMENT % args.to_s)
23
+ end
24
+ end
25
+
26
+ ##
27
+ # An error raised if a given timestamp was not found in the
28
+ # repository.
29
+ #
30
+ class MissingTimestampError < Error
31
+ def initialize(timestamp)
32
+ super(ErrMsg::MISSING_TIMESTAMP % timestamp)
33
+ end
34
+ end
35
+
36
+ ##
37
+ # An error raised if no external editor is available to edit a note,
38
+ # even "nano" or "vi".
39
+ #
40
+ class NoEditorError < Error
41
+ def initialize(names)
42
+ super(ErrMsg::NO_EDITOR % names.to_s)
43
+ end
44
+ end
45
+
46
+ ##
47
+ # An error raised when a external program such an editor was aborted
48
+ # during its execution.
49
+ class ProgramAbortError < Error
50
+ def initialize(cmdline)
51
+ super(ErrMsg::PROGRAM_ABORT % cmdline.join(" "))
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,107 @@
1
+ require "pathname"
2
+ require "tmpdir"
3
+
4
+ module Rbnotes
5
+ ##
6
+ # Defines several utility methods those are intended to be used in
7
+ # Rbnotes classes.
8
+ #
9
+ module Utils
10
+
11
+ ##
12
+ # Finds a external editor program which is specified with the
13
+ # argument, then returns the absolute path of the editor. If the
14
+ # specified editor was not found, then search default editors in
15
+ # the command search paths (i.e. `ENV["PATH"]). See also the
16
+ # document for `find_program`.
17
+ #
18
+ # The default editors to search in the search paths are:
19
+ #
20
+ # 1. ENV["EDITOR"]
21
+ # 2. "nano"
22
+ # 3. "vi"
23
+ #
24
+ # When all the default editors were not found, returns `nil`.
25
+ #
26
+ def find_editor(preferred_editor)
27
+ find_program([preferred_editor, ENV["EDITOR"], "nano", "vi"].compact)
28
+ end
29
+ module_function :find_editor
30
+
31
+ ##
32
+ # Finds a executable program in given names. When the executable
33
+ # was found, it stops searching then returns an absolute path of
34
+ # the executable.
35
+ #
36
+ # The actual searching is done in 2 cases. That is, a given name is:
37
+ #
38
+ # 1. an absolute path:
39
+ # returns the path itself if it exists and is executable.
40
+ # 2. just a program name:
41
+ # searchs the name in the search paths (ENV["PATH"]);
42
+ # if it is found in a path, construct an absolute path from
43
+ # the name and the path, then returns the path.
44
+ #
45
+ # :call-seq:
46
+ # ["nano", "vi"] -> "/usr/bin/nano"
47
+ # ["vi", "/usr/local/bin/emacs"] -> "/usr/bin/vi"
48
+ # ["/usr/local/bin/emacs", "vi"] -> "/usr/bin/vi" (if emacs doesn't exist)
49
+ # ["/usr/local/bin/emacs", "vi"] -> "/usr/local/bin/emacs" (if exists)
50
+ #
51
+ def find_program(names)
52
+ names.each { |name|
53
+ pathname = Pathname.new(name)
54
+ if pathname.absolute?
55
+ return pathname.to_path if pathname.exist? && pathname.executable?
56
+ else
57
+ abs = search_in_path(name)
58
+ return abs unless abs.nil?
59
+ end
60
+ }
61
+ nil
62
+ end
63
+ module_function :find_program
64
+
65
+ ##
66
+ # Executes the program with passing the given filename as argument.
67
+ # The file will be created into `Dir.tmpdir`.
68
+ #
69
+ # If initial_content is not nil, it must be an array of strings
70
+ # then it provides the initial content of a temporary file.
71
+ #
72
+ # :call-seq:
73
+ # "/usr/bin/nano", "20201021131300.md", nil -> "/somewhere/tmpdir/20201021131300.md"
74
+ # "/usr/bin/vi", "20201021131301.md", ["apple\n", "orange\n"] -> "/somewhere/tmpdir/20201021131301.md"
75
+ #
76
+ def run_with_tmpfile(prog, filename, initial_content = nil)
77
+ tmpfile = File.expand_path(add_extension(filename), Dir.tmpdir)
78
+
79
+ unless initial_content.nil?
80
+ File.open(tmpfile, "w") {|f| f.print(initial_content.join("\n"))}
81
+ end
82
+
83
+ rc = system(prog, tmpfile)
84
+ raise ProgramAbortError, [prog, tmpfile] unless rc
85
+ tmpfile
86
+ end
87
+ module_function :run_with_tmpfile
88
+
89
+ # :stopdoc:
90
+
91
+ private
92
+ def search_in_path(name)
93
+ search_paths = ENV["PATH"].split(":")
94
+ found = search_paths.map { |path|
95
+ abs = File.expand_path(name, path)
96
+ FileTest.exist?(abs) ? abs : nil
97
+ }
98
+ found.compact[0]
99
+ end
100
+ module_function :search_in_path
101
+
102
+ def add_extension(basename)
103
+ "#{basename}.md"
104
+ end
105
+ module_function :add_extension
106
+ end
107
+ end
@@ -0,0 +1,4 @@
1
+ module Rbnotes
2
+ VERSION = "0.2.1"
3
+ RELEASE = '2020-10-25'
4
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'lib/rbnotes/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rbnotes"
5
+ spec.version = Rbnotes::VERSION
6
+ spec.authors = ["mnbi"]
7
+ spec.email = ["mnbi@users.noreply.github.com"]
8
+
9
+ spec.summary = %q{A simple utility to write a note.}
10
+ spec.description = %q{Rbnotes allows you to write a note into a single repository.}
11
+ spec.homepage = "https://github.com/mnbi/rbnotes"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/mnbi/rbnotes"
17
+ spec.metadata["changelog_uri"] = "https://github.com/mnbi/rbnotes/blob/main/CHANGELOG.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "textrepo", "~> 0.4"
29
+ spec.add_dependency "unicode-display_width", "~> 1.7"
30
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbnotes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - mnbi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-10-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: textrepo
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: unicode-display_width
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ description: Rbnotes allows you to write a note into a single repository.
42
+ email:
43
+ - mnbi@users.noreply.github.com
44
+ executables:
45
+ - rbnotes
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".travis.yml"
51
+ - CHANGELOG.md
52
+ - Gemfile
53
+ - Gemfile.lock
54
+ - LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - bin/console
58
+ - bin/setup
59
+ - exe/rbnotes
60
+ - lib/rbnotes.rb
61
+ - lib/rbnotes/commands.rb
62
+ - lib/rbnotes/commands/add.rb
63
+ - lib/rbnotes/commands/delete.rb
64
+ - lib/rbnotes/commands/import.rb
65
+ - lib/rbnotes/commands/list.rb
66
+ - lib/rbnotes/commands/show.rb
67
+ - lib/rbnotes/commands/update.rb
68
+ - lib/rbnotes/conf.rb
69
+ - lib/rbnotes/error.rb
70
+ - lib/rbnotes/utils.rb
71
+ - lib/rbnotes/version.rb
72
+ - rbnotes.gemspec
73
+ homepage: https://github.com/mnbi/rbnotes
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ homepage_uri: https://github.com/mnbi/rbnotes
78
+ source_code_uri: https://github.com/mnbi/rbnotes
79
+ changelog_uri: https://github.com/mnbi/rbnotes/blob/main/CHANGELOG.md
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 2.7.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.1.4
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: A simple utility to write a note.
99
+ test_files: []