diff --git a/bin/uhwm b/bin/uhwm index 539577c..e03193c 100755 --- a/bin/uhwm +++ b/bin/uhwm @@ -2,4 +2,5 @@ require 'uh/wm' -Uh::WM::CLI.run!(ARGV) +Uh::WM::CLI.run(ARGV) +sleep 8 diff --git a/features/cli/usage.feature b/features/cli/usage.feature new file mode 100644 index 0000000..a96c1d3 --- /dev/null +++ b/features/cli/usage.feature @@ -0,0 +1,11 @@ +Feature: CLI usage + + Scenario: prints the usage when unknown option switch is given + When I run uhwm with option --unknown-option + Then the exit status must be 64 + And the output must contain exactly the usage + + Scenario: prints the help when -h option is given + When I run uhwm with option -h + Then the exit status must be 0 + And the output must contain exactly the usage diff --git a/features/steps/output_steps.rb b/features/steps/output_steps.rb new file mode 100644 index 0000000..9bf2ea2 --- /dev/null +++ b/features/steps/output_steps.rb @@ -0,0 +1,8 @@ +Then /^the output must contain exactly the usage$/ do + assert_exact_output <<-eoh, all_output +Usage: uhwm [options] + +options: + -h, --help print this message + eoh +end diff --git a/features/steps/run_steps.rb b/features/steps/run_steps.rb index 2a58281..e617e27 100644 --- a/features/steps/run_steps.rb +++ b/features/steps/run_steps.rb @@ -1,4 +1,17 @@ -When /^I start uhwm$/ do - @process = run 'uhwm' - @interactive = @process +def uhwm_run options = nil + command = %w[uhwm] + command << options if options + @interactive = @process = run command.join ' ' +end + +When /^I start uhwm$/ do + uhwm_run +end + +When /^I run uhwm with options? (-.+)$/ do |options| + uhwm_run options +end + +Then /^the exit status must be (\d+)$/ do |exit_status| + assert_exit_status exit_status.to_i end diff --git a/lib/uh/wm/cli.rb b/lib/uh/wm/cli.rb index 734ddf2..1656d88 100644 --- a/lib/uh/wm/cli.rb +++ b/lib/uh/wm/cli.rb @@ -1,13 +1,56 @@ module Uh module WM class CLI + ArgumentError = Class.new(ArgumentError) + + USAGE = "Usage: #{File.basename $0} [options]".freeze + + EX_USAGE = 64 + class << self - def run! arguments - $stdout.sync = true - @display = Display.new - @display.open - puts "Connected to X server on `#{display}'" - sleep 8 + def run arguments, **options + cli = new arguments, **options + cli.parse_arguments! + cli.run + rescue ArgumentError => e + $stderr.puts e + exit EX_USAGE + end + end + + def initialize args, stdin: $stdin, stdout: $stdout, stderr: $stderr + @arguments = args + @stdin = stdin + @stdout = stdout + @stderr = stderr + end + + def parse_arguments! + option_parser.parse! @arguments + rescue OptionParser::InvalidOption => e + fail ArgumentError, option_parser + end + + def run + @stdout.sync = true + @display = Display.new + @display.open + @stdout.puts "Connected to X server on `#{@display}'" + end + + + private + + def option_parser + OptionParser.new do |opts| + opts.banner = USAGE + opts.separator '' + opts.separator 'options:' + + opts.on '-h', '--help', 'print this message' do + @stdout.print opts + exit + end end end end diff --git a/spec/uh/wm/cli_spec.rb b/spec/uh/wm/cli_spec.rb new file mode 100644 index 0000000..0b00cda --- /dev/null +++ b/spec/uh/wm/cli_spec.rb @@ -0,0 +1,98 @@ +require 'support/exit_helpers' + +module Uh + module WM + RSpec.describe CLI do + include ExitHelpers + + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + let(:arguments) { [] } + let(:options) { { stdout: stdout, stderr: stderr } } + + subject(:cli) { described_class.new arguments, **options } + + describe '.run' do + subject(:run) { described_class.run arguments, **options } + + it 'builds a new CLI with given arguments' do + expect(described_class) + .to receive(:new).with(arguments, options).and_call_original + run + end + + it 'parses new CLI arguments' do + cli = instance_spy CLI + allow(described_class).to receive(:new) { cli } + expect(cli).to receive :parse_arguments! + run + end + + it 'runs new CLI' do + cli = instance_spy CLI + allow(described_class).to receive(:new) { cli } + expect(cli).to receive :run + run + end + + context 'with invalid arguments' do + let(:arguments) { %w[--unknown-option] } + + it 'prints the usage' do + expect { trap_exit { run } }.to output(/\AUsage: .+/).to_stderr + end + + it 'exits with a return status of 64' do + stderr_original = $stderr + $stderr = stderr + expect { run }.to raise_error(SystemExit) do |e| + expect(e.status).to eq 64 + end + $stderr = stderr_original + end + end + end + + describe '#run' do + let(:display) { instance_spy Display } + + before { allow(Display).to receive(:new) { display } } + + it 'opens a new X display' do + expect(display).to receive :open + cli.run + end + + it 'prints a message on standard output when connected' do + cli.run + expect(stdout.string).to match /connected/i + end + end + + describe '#parse_arguments!' do + context 'with help option' do + let(:arguments) { %w[-h] } + + it 'prints the usage banner on standard output' do + trap_exit { cli.parse_arguments! } + expect(stdout.string).to match /\AUsage: .+/ + end + + it 'prints options usage on standard output' do + trap_exit { cli.parse_arguments! } + expect(stdout.string).to match /\n^options:\n\s+-/ + end + end + + context 'with invalid option' do + let(:arguments) { %w[--unknown-option] } + + it 'raises a CLI::ArgumentError' do + expect { cli.parse_arguments! } + .to raise_error CLI::ArgumentError + end + end + end + end + end +end