commit 43f2382ec0a81bccfdf72230185ecc20f552d8ec
parent b66469e741508043d4ea4b43b68b9153ee9ca947
Author: Daniel Barlow <dan@telent.net>
Date: Thu, 12 Aug 2021 23:07:59 +0100
replace CLI class with the new tested version
Diffstat:
| M | moss.rb | | | 213 | ++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------- |
| M | spec/cli_spec.rb | | | 92 | ++++++------------------------------------------------------------------------- |
2 files changed, 140 insertions(+), 165 deletions(-)
diff --git a/moss.rb b/moss.rb
@@ -166,110 +166,165 @@ def random_alnum(length)
bytes.pack("C*")
end
-
class CLI
- @@commands = {}
-
class << self
- def command(name, docstring, &blk)
- @@commands[name] = { name: name, doc: docstring }
- # we use methods here instead of blocks, because blocks
- # don't have required parameters
- define_method name, &blk
+ @@commands = {}
+ def command(command_name, docstring, &blk)
+ define_method command_name, &blk
+ @@commands[command_name] = { doc: docstring }
end
+ end
- def dispatch(name, *parameters)
- command = @@commands.fetch(name.to_sym)
- instance = new
- instance.public_send(command[:name], *parameters)
- rescue KeyError => e
- raise "Command not recognized, try \"moss help\""
- rescue ArgumentError => e
- meth = instance.method(command[:name])
- params = meth.parameters.map {|p| "<#{p[1]}>" }.join(" ")
- raise "usage: moss #{name} #{params}"
- end
+ def parse_arguments(argv)
+ name = argv.first.to_sym
+ args = argv.drop(1)
+ raise NoMethodError unless @@commands.key?(name)
+ arg_signature = method(name).parameters
- def aka(command, command_alias)
- @@commands[command_alias] = @@commands[command]
- end
+ return [ name, [] ] if arg_signature.empty?
+
+ flags = argv.reduce({}) {|m, flag|
+ key, val= flag.match(/\A--(\w+)(?:=(.+))?/)&.captures
+ key ?
+ m.merge(key.to_sym => val ? val : :key_present) :
+ m
+ }
- def usage(instance)
- puts "Store and retrieve encrypted secrets\n\n"
- puts "Usage: moss [command] [parameters]...\n\n"
- @@commands.each do |name, command|
- next unless name == command[:name] # skip aliases
- meth = instance.method(command[:name])
- params = meth.parameters
- printf " %-40s - %s \n",
- ([command[:name]] + params.map {|p| p[1].to_s }).join(" "),
- command[:doc]
+ payload = arg_signature.reduce({}) { |m, (type, name)|
+ case type
+ when :rest
+ args
+ when :keyreq
+ args.first ? m.merge(name => args.shift) : m
+ when :key
+ m
end
- end
+ }
+ [ name,
+ payload.respond_to?(:merge) ? flags.merge(payload) : payload ]
end
- command :generate, "generate a random secret" do |filename, length|
- secret = random_alnum(length.to_i)
- print secret
- MOSS.write_secret(filename, secret)
+ def params_to_s(meth)
+ meth.parameters.map {|(type, name)|
+ case type
+ when :req, :keyreq
+ "<#{name}>"
+ when :rest
+ "<#{name}...>"
+ when :key
+ "[--#{name}]"
+ end
+ }.join(" ")
end
- command :add, "add a secret to the store" do |filename|
- secret = STDIN.read
- MOSS.write_secret(filename, secret)
+ def describe_usage(method_name)
+ param_string = params_to_s(method(method_name))
+ "#{method_name} #{param_string}"
end
- aka :add, :insert
- command :show, "display a secret" do |file|
- STDOUT.write(MOSS.read_secret(file))
+ UsageError = Class.new(RuntimeError)
+
+ def dispatch(argv)
+ name, payload = parse_arguments(argv)
+ begin
+ if payload.respond_to?(:keys)
+ self.public_send(name, **payload)
+ else
+ self.public_send(name, *payload)
+ end
+ rescue ArgumentError => e
+ raise UsageError, "usage: moss #{describe_usage(name)}"
+ end
end
- aka :show, :cat
- command :search, "search secrets with names matching term" do |*term|
- files = MOSS.secrets.filter {|f|
- f.match(Regexp.new(term.join(" ")))
+ def usage
+ command_texts = @@commands.map { |name, command|
+ # unless name == command[:name] # skip aliases
+ sprintf " %-40s - %s",
+ describe_usage(name),
+ command[:doc]
}
- files.each do |n|
- puts n
- end
+
+ usage_header + command_texts.join("\n")
end
- aka :search, :list
- command :edit, "edit a secret" do |file|
- content = MOSS.read_secret(file)
- Tempfile.create(file.gsub(/[\W]/,"")) do |f|
- f.write(content)
- f.flush
- if Kernel.system(ENV['EDITOR'], f.path)
- f.rewind
- secret = f.read
- MOSS.write_secret(file, secret)
- else
- raise "#{$!}"
+end
+
+def cli
+ cli = CLI.new
+ class << cli
+ def usage_header
+ "Store and retrieve encrypted secrets\n\n" +
+ "Usage: moss [command] [parameters]...\n\n"
+ end
+ command :generate, "generate a random secret" do |filename:, length:|
+ secret = random_alnum(length.to_i)
+ print secret
+ MOSS.write_secret(filename, secret)
+ end
+
+ command :add, "add a secret to the store" do |filename:, force: false|
+ secret = STDIN.read
+ MOSS.write_secret(filename, secret)
+ end
+# aka :add, :insert
+
+ command :show, "display a secret" do |file:|
+ STDOUT.write(MOSS.read_secret(file))
+ end
+# aka :show, :cat
+
+ command :search, "search secrets with names matching term" do |*term|
+ files = MOSS.secrets.filter {|f|
+ f.match(Regexp.new(term.join(" ")))
+ }
+ files.each do |n|
+ puts n
+ end
+ end
+# aka :search, :list
+
+ command :edit, "edit a secret" do |file:|
+ content = MOSS.read_secret(file)
+ Tempfile.create(file.gsub(/[\W]/,"")) do |f|
+ f.write(content)
+ f.flush
+ if Kernel.system(ENV['EDITOR'], f.path)
+ f.rewind
+ secret = f.read
+ MOSS.write_secret(file, secret)
+ else
+ raise "#{$?}"
+ end
end
end
- end
- command :config, "show configuration" do
- puts JSON.generate(MOSS.config)
- end
+ command :config, "show configuration" do
+ puts JSON.generate(MOSS.config)
+ end
- command :init, "create new moss repository" do |keyfile|
- MOSS.create(Pathname.new(keyfile))
- end
+ command :init, "create new moss repository" do |keyfile:|
+ MOSS.create(Pathname.new(keyfile))
+ end
- command :git, "perform git operation in store" do |* parameters|
- MOSS.git_operation(parameters)
- end
+ command :git, "perform git operation in store" do |* git_command|
+ MOSS.git_operation(parameters)
+ end
- command :help, "display this help text" do
- self.class.usage(self)
+ command :help, "display this help text" do
+ puts self.usage
+ end
end
+ cli
end
-begin
- CLI.dispatch(* ARGV)
-rescue StandardError => e
- warn e.message
- exit 1
+if $0==__FILE__
+ # running as a script
+ begin
+ File.umask(077)
+ cli.dispatch(ARGV)
+ rescue StandardError => e
+ warn e.message
+ exit 1
+ end
end
diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb
@@ -1,92 +1,12 @@
require "moss"
-class CLI1
- class << self
- @@commands = {}
- def command(command_name, docstring, &blk)
- define_method command_name, &blk
- @@commands[command_name] = { doc: docstring }
- end
- end
-
- def parse_arguments(argv)
- name = argv.first.to_sym
- args = argv.drop(1)
- raise NoMethodError unless @@commands.key?(name)
- arg_signature = method(name).parameters
-
- return [ name, [] ] if arg_signature.empty?
-
- flags = argv.reduce({}) {|m, flag|
- key, val= flag.match(/\A--(\w+)(?:=(.+))?/)&.captures
- key ?
- m.merge(key.to_sym => val ? val : :key_present) :
- m
- }
-
- payload = arg_signature.reduce({}) { |m, (type, name)|
- case type
- when :rest
- args
- when :keyreq
- args.first ? m.merge(name => args.shift) : m
- when :key
- m
- end
- }
- [ name,
- payload.respond_to?(:merge) ? flags.merge(payload) : payload ]
- end
-
- def params_to_s(meth)
- meth.parameters.map {|(type, name)|
- case type
- when :req, :keyreq
- "<#{name}>"
- when :rest
- "<#{name}...>"
- when :key
- "[--#{name}]"
- end
- }.join(" ")
- end
-
- def describe_usage(method_name)
- param_string = params_to_s(method(method_name))
- "#{method_name} #{param_string}"
- end
-
- UsageError = Class.new(RuntimeError)
-
- def dispatch(argv)
- name, payload = parse_arguments(argv)
- begin
- self.public_send(name,payload)
- rescue ArgumentError => e
- raise UsageError, "usage: moss #{describe_usage(name)}"
- end
- end
-
- def usage
- command_texts = @@commands.map { |name, command|
- # unless name == command[:name] # skip aliases
- sprintf " %-40s - %s",
- "#{name} #{describe_usage(name)}",
- command[:doc]
- }
-
- usage_header+ command_texts.join("\n")
- end
-
-end
-
-cli = CLI1.new
+cli = CLI.new
class <<cli
- def usage_header
+ def usage_header
"Store and retrieve encrypted secrets\n\n" +
- "Usage: moss [command] [parameters]...\n\n"
+ "Usage: moss [command] [parameters]...\n\n"
end
-
+
command :greet, "say hello" do |firstname:, lastname:|
"hello, #{firstname} #{lastname}"
end
@@ -107,7 +27,7 @@ class <<cli
end
-RSpec.describe CLI1 do
+RSpec.describe CLI do
describe "parameter parsing" do
it "parses positional parameters" do
@@ -160,7 +80,7 @@ RSpec.describe CLI1 do
it "complains if wrong params" do
expect { cli.dispatch ["mail"] }
- .to raise_exception(CLI1::UsageError, /usage: moss mail <rcpt> \[--subject\]/)
+ .to raise_exception(CLI::UsageError, /usage: moss mail <rcpt> \[--subject\]/)
end
end