Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions lib/completely/commands/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,25 @@ def script
end

def completions
@completions ||= Completions.load(config_path, function_name: args['--function'])
@completions ||= if config_path == '-'
raise Error, 'Nothing is piped on stdin' if $stdin.tty?

Completions.read $stdin, function_name: args['--function']
else
Completions.load config_path, function_name: args['--function']
end
end

def config_path
@config_path ||= args['CONFIG_PATH'] || ENV['COMPLETELY_CONFIG_PATH'] || 'completely.yaml'
end

def output_path
@output_path ||= args['OUTPUT_PATH'] || ENV['COMPLETELY_OUTPUT_PATH'] || "#{config_basename}.bash"
@output_path ||= args['OUTPUT_PATH'] || ENV['COMPLETELY_OUTPUT_PATH'] || stdout || "#{config_basename}.bash"
end

def stdout
@stdout ||= config_path == '-' ? '-' : nil
end

def config_basename
Expand Down
23 changes: 18 additions & 5 deletions lib/completely/commands/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Completely
module Commands
class Generate < Base
help 'Generate the bash completion script to a file'
help 'Generate the bash completion script to file or stdout'

usage 'completely generate [CONFIG_PATH OUTPUT_PATH --function NAME --wrap NAME]'
usage 'completely generate (-h|--help)'
Expand All @@ -12,10 +12,19 @@ class Generate < Base
option '-w --wrap NAME', 'Wrap the completion script inside a function that echos the ' \
'script. This is useful if you wish to embed it directly in your script.'

param_config_path
param 'CONFIG_PATH', <<~USAGE
Path to the YAML configuration file [default: completely.yaml].
Use '-' to read from stdin.

Can also be set by an environment variable.
USAGE

param 'OUTPUT_PATH', <<~USAGE
Path to the output bash script.
When not provided, the name of the input file will be used with a .bash extension.
Use '-' for stdout.

When not provided, the name of the input file will be used with a .bash extension, unless the input is stdin - in this case the default will be to output to stdout.

Can also be set by an environment variable.
USAGE

Expand All @@ -26,8 +35,12 @@ class Generate < Base
def run
wrap = args['--wrap']
output = wrap ? wrapper_function(wrap) : script
File.write output_path, output
say "Saved m`#{output_path}`"
if output_path == '-'
puts output
else
File.write output_path, output
say "Saved m`#{output_path}`"
end
syntax_warning unless completions.valid?
end

Expand Down
21 changes: 18 additions & 3 deletions lib/completely/commands/install.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'tempfile'
require 'completely/commands/base'

module Completely
Expand All @@ -17,9 +18,19 @@ class Install < Base
option '-d --dry', 'Show the installation command but do not run it'

param 'PROGRAM', 'Name of the program the completions are for.'
param 'SCRIPT_PATH', 'Path to the source bash script [default: completely.bash].'
param 'SCRIPT_PATH', <<~USAGE
Path to the source bash script [default: completely.bash].
Use '-' to provide the script via stdin.
USAGE

def run
if script_path == '-'
raise InstallError, "Nothing is piped on stdin" if $stdin.tty?

@script_path = tempfile.path
File.write script_path, $stdin.read
end

if args['--dry']
puts installer.install_command_string
return
Expand All @@ -32,14 +43,18 @@ def run
say 'You may need to restart your session to test it'
end

def tempfile
@tempfile ||= Tempfile.new('stdin-completely-')
end

private

def installer
Installer.new program: args['PROGRAM'], script_path: script_path
@installer ||= Installer.new(program: args['PROGRAM'], script_path: script_path)
end

def script_path
args['SCRIPT_PATH'] || 'completely.bash'
@script_path ||= args['SCRIPT_PATH'] || 'completely.bash'
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/completely/commands/preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Completely
module Commands
class Preview < Base
help 'Generate the bash completion script to STDOUT'
help 'Generate the bash completion script to stdout'

usage 'completely preview [CONFIG_PATH --function NAME]'
usage 'completely preview (-h|--help)'
Expand Down
11 changes: 7 additions & 4 deletions lib/completely/completions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ class Completions
attr_reader :config

class << self
def load(config_path, function_name: nil)
config = Config.load config_path
new config, function_name: function_name
def load(path, function_name: nil)
new Config.load(path), function_name: function_name
end

def read(io, function_name: nil)
new Config.read(io), function_name: function_name
end
end

Expand All @@ -26,7 +29,7 @@ def patterns
end

def valid?
pattern_prefixes.uniq.count == 1
pattern_prefixes.uniq.one?
end

def script
Expand Down
17 changes: 7 additions & 10 deletions lib/completely/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ class Config
attr_reader :config, :options

class << self
def load(config_path)
begin
config = YAML.load_file config_path, aliases: true
rescue ArgumentError
# :nocov:
config = YAML.load_file config_path
# :nocov:
end

new config
def parse(str)
new YAML.load(str, aliases: true)
rescue Psych::Exception => e
raise ParseError, "Invalid YAML: #{e.message}"
end

def load(path) = parse(File.read(path))
def read(io) = parse(io.read)
end

def initialize(config)
Expand Down
1 change: 1 addition & 0 deletions lib/completely/exceptions.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Completely
class Error < StandardError; end
class InstallError < Error; end
class ParseError < Error; end
end
12 changes: 5 additions & 7 deletions lib/completely/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,13 @@ def root_user?
end

def completions_path
@completions_path ||= completions_path!
end
@completions_path ||= begin
target_directories.each do |target|
return target if Dir.exist? target
end

def completions_path!
target_directories.each do |target|
return target if Dir.exist? target
nil
end

nil
end
end
end
4 changes: 2 additions & 2 deletions spec/approvals/cli/commands
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ Completely - Bash Completions Generator

Commands:
init Create a new sample YAML configuration file
preview Generate the bash completion script to STDOUT
generate Generate the bash completion script to a file
preview Generate the bash completion script to stdout
generate Generate the bash completion script to file or stdout
test Test completions
install Install a bash completion script
uninstall Uninstall a bash completion script
Expand Down
1 change: 1 addition & 0 deletions spec/approvals/cli/generate/custom-path-stdin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Saved spec/tmp/stdin-to-file.bash
10 changes: 8 additions & 2 deletions spec/approvals/cli/generate/help
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Generate the bash completion script to a file
Generate the bash completion script to file or stdout

Usage:
completely generate [CONFIG_PATH OUTPUT_PATH --function NAME --wrap NAME]
Expand All @@ -18,12 +18,18 @@ Options:
Parameters:
CONFIG_PATH
Path to the YAML configuration file [default: completely.yaml].
Use '-' to read from stdin.

Can also be set by an environment variable.

OUTPUT_PATH
Path to the output bash script.
Use '-' for stdout.

When not provided, the name of the input file will be used with a .bash
extension.
extension, unless the input is stdin - in this case the default will be to
output to stdout.

Can also be set by an environment variable.

Environment Variables:
Expand Down
1 change: 1 addition & 0 deletions spec/approvals/cli/install/help
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Parameters:

SCRIPT_PATH
Path to the source bash script [default: completely.bash].
Use '-' to provide the script via stdin.
1 change: 1 addition & 0 deletions spec/approvals/cli/install/stdin-dry
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sudo cp <tmpfile-path> /usr/share/bash-completion/completions/completely-test
2 changes: 2 additions & 0 deletions spec/approvals/cli/install/stdin-install
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Saved some-target-path
You may need to restart your session to test it
2 changes: 1 addition & 1 deletion spec/approvals/cli/preview/help
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Generate the bash completion script to STDOUT
Generate the bash completion script to stdout

Usage:
completely preview [CONFIG_PATH --function NAME]
Expand Down
30 changes: 28 additions & 2 deletions spec/completely/commands/generate_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
describe Commands::Generate do
subject { described_class.new }

before { system 'cp lib/completely/templates/sample.yaml completely.yaml' }
after { system 'rm -f completely.yaml' }
before do
reset_tmp_dir
system 'cp lib/completely/templates/sample.yaml completely.yaml'
end

after do
system 'rm -f completely.yaml'
end

context 'with --help' do
it 'shows long usage' do
Expand Down Expand Up @@ -81,6 +87,26 @@
end
end

context 'with stdin and stdout' do
it 'reads config from stdin and writes to stdout' do
allow($stdin).to receive_messages(tty?: false, read: File.read('completely.yaml'))

expect { subject.execute %w[generate -] }
.to output_approval('cli/generated-script')
end
end

context 'with stdin and output path' do
let(:outfile) { 'spec/tmp/stdin-to-file.bash' }

it 'reads config from stdin and writes to file' do
allow($stdin).to receive_messages(tty?: false, read: File.read('completely.yaml'))

expect { subject.execute %W[generate - #{outfile}] }.to output_approval('cli/generate/custom-path-stdin')
expect(File.read outfile).to match_approval('cli/generated-script')
end
end

context 'with --function NAME' do
after { system 'rm -f completely.bash' }

Expand Down
37 changes: 37 additions & 0 deletions spec/completely/commands/install_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@
end
end

context 'with PROGRAM - (stdin)' do
it 'invokes the Installer using a temp file' do
allow(subject).to receive(:installer).and_return(mock_installer)
allow($stdin).to receive_messages(tty?: false, read: 'dummy data')

expect(mock_installer).to receive(:install)

expect { subject.execute %w[install completely-test -] }
.to output_approval('cli/install/stdin-install')

expect(File.read subject.tempfile.path).to eq 'dummy data'
end
end

context 'with PROGRAM --dry' do
it 'shows the command and does not install anything' do
expect(mock_installer).not_to receive(:install)
Expand All @@ -43,6 +57,29 @@
end
end

context 'with PROGRAM - --dry (stdin)' do
it 'shows the command and does not install anything' do
allow($stdin).to receive_messages(tty?: false, read: 'dummy data')

expect(mock_installer).not_to receive(:install)

expect { subject.execute %w[install completely-test - --dry] }
.to output_approval('cli/install/stdin-dry')
.except(/[^\s]*stdin-completely-[^\s]*/, '<tmpfile-path>')
end

context 'when stdin is empty' do
it 'raises InstallError' do
allow($stdin).to receive_messages(tty?: true, read: nil)
expect(mock_installer).not_to receive(:install)

expect { subject.execute %w[install completely-test - --dry] }
.to raise_error(InstallError, 'Nothing is piped on stdin')
end
end
end


context 'when the installer fails' do
it 'raises an error' do
allow(subject).to receive(:installer).and_return(mock_installer)
Expand Down
7 changes: 7 additions & 0 deletions spec/completely/completions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
let(:path) { "spec/fixtures/#{file}.yaml" }
let(:file) { 'basic' }

describe '::read' do
it 'reads from io' do
io = double :io, read: "cli: [--help, --version]"
expect(described_class.read(io).config.config).to eq({ 'cli' => %w[--help --version] })
end
end

describe '#valid?' do
context 'when all patterns start with the same word' do
it 'returns true' do
Expand Down
21 changes: 21 additions & 0 deletions spec/completely/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@

let(:path) { "spec/fixtures/#{file}.yaml" }
let(:file) { 'nested' }
let(:config_string) { "cli: [--help, --version]" }
let(:config_hash) { { 'cli' => %w[--help --version] } }

describe '::parse' do
it 'loads config from string' do
expect(described_class.parse(config_string).config).to eq config_hash
end

context 'when the string is not a valid YAML' do
it 'raises ParseError' do
expect { described_class.parse("not: a: yaml") }.to raise_error(Completely::ParseError)
end
end
end

describe '::read' do
it 'loads config from io' do
io = double :io, read: config_string
expect(described_class.read(io).config).to eq config_hash
end
end

describe '#flat_config' do
it 'returns a flat pattern => completions hash' do
Expand Down
Loading