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
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ AllCops:
NewCops: enable
SuggestExtensions: false
TargetRubyVersion: 3.2

Metrics/BlockLength:
Exclude:
- "spec/**/*"
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ group :development, :test do
gem "rake", "~> 13.0"
gem "rspec", "~> 3.0"
gem "rubocop"
gem "simplecov", require: false
gem "webmock", "~> 3.0"
end
57 changes: 2 additions & 55 deletions bin/morph
Original file line number Diff line number Diff line change
@@ -1,62 +1,9 @@
#!/usr/bin/env ruby
# Commandline client for controlling morph and running scrapers and things

require "thor"
# TODO: Do compression on the tar file
# require 'zlib'
require 'morph-cli'

class MorphThor < Thor
class_option :dev, default: false, type: :boolean, desc: "Run against development Morph (for morph developers)"

desc "[execute]", "execute morph scraper"
option :directory, default: Dir.getwd

def execute
config = MorphCLI.load_config
env_config = if options[:dev]
config[:development]
else
config[:production]
end

config = ask_and_save_api_key(env_config, config) if env_config[:api_key].nil?

api_key_is_valid = false
until api_key_is_valid
begin
MorphCLI.execute(options[:directory], options[:dev], env_config)
api_key_is_valid = true
rescue Faraday::UnauthorizedError
puts "Your key isn't working. Let's try again."
config = ask_and_save_api_key(env_config, config)
rescue Faraday::ConnectionFailed => e
warn "Morph doesn't look to be running at #{env_config[:base_url]} (#{e})"
exit(1)
rescue Faraday::ServerError => e
warn "Uh oh. Something has gone wrong on the Morph server at #{env_config[:base_url]} (#{e})"
exit(1)
rescue Faraday::Error => e
warn "Request to #{env_config[:base_url]} failed (#{e})"
exit(1)
end
end
end

desc "version", "Show Morph version number and quit"
def version
puts "Morph CLI #{MorphCLI::VERSION}"
exit
end

no_commands do
def ask_and_save_api_key(env_config, config)
env_config[:api_key] = ask("What is your key? (Go to #{env_config[:base_url]}/settings)")
MorphCLI.save_config(config)
config
end
end
end
require "morph-cli/cli"

# If morph is run without any parameters it's the same as "morph execute"
MorphThor.start(ARGV.empty? ? ["execute"] : ARGV)
MorphCLI::CLI.start(ARGV.empty? ? ["execute"] : ARGV)
63 changes: 63 additions & 0 deletions lib/morph-cli/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'thor'
require 'morph-cli'

module MorphCLI
# Thor command line interface for running Morph scrapers
class CLI < Thor
def self.exit_on_failure?
true
end

class_option :dev, default: false, type: :boolean, desc: 'Run against development Morph (for morph developers)'

desc '[execute]', 'execute morph scraper'
option :directory, default: Dir.getwd

def execute
config = MorphCLI.load_config
env_config = if options[:dev]
config[:development]
else
config[:production]
end

config = ask_and_save_api_key(env_config, config) if env_config[:api_key].nil?

api_key_is_valid = false
until api_key_is_valid
begin
MorphCLI.execute(options[:directory], options[:dev], env_config)
api_key_is_valid = true
rescue Faraday::UnauthorizedError
puts "Your key isn't working. Let's try again."
config = ask_and_save_api_key(env_config, config)
rescue Faraday::ConnectionFailed => e
warn "Morph doesn't look to be running at #{env_config[:base_url]} (#{e})"
exit(1)
rescue Faraday::ServerError => e
warn "Uh oh. Something has gone wrong on the Morph server at #{env_config[:base_url]} (#{e})"
exit(1)
rescue Faraday::Error => e
warn "Request to #{env_config[:base_url]} failed (#{e})"
exit(1)
end
end
end

desc 'version', 'Show Morph version number and quit'
def version
puts "Morph CLI #{MorphCLI::VERSION}"
exit
end

no_commands do
def ask_and_save_api_key(env_config, config)
env_config[:api_key] = ask("What is your key? (Go to #{env_config[:base_url]}/settings)")
MorphCLI.save_config(config)
config
end
end
end
end
106 changes: 106 additions & 0 deletions spec/morph_cli/cli_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MorphCLI::CLI do
describe 'version' do
it 'prints the version and exits' do
expect do
expect { described_class.start(['version']) }.to raise_error(SystemExit)
end.to output("Morph CLI #{MorphCLI::VERSION}\n").to_stdout
end
end

describe 'execute' do
let(:config) do
{
development: { base_url: 'http://127.0.0.1:3000', api_key: 'dev-key' },
production: { base_url: 'https://morph.io', api_key: 'prod-key' }
}
end

before do
allow(MorphCLI).to receive(:load_config).and_return(config)
allow(MorphCLI).to receive(:save_config)
end

it 'runs the scraper with the production config by default' do
allow(MorphCLI).to receive(:execute)

described_class.start(['execute', '--directory', '/somewhere'])

expect(MorphCLI).to have_received(:execute)
.with('/somewhere', false, config[:production])
end

it 'runs the scraper with the development config when --dev is given' do
allow(MorphCLI).to receive(:execute)

described_class.start(['execute', '--dev'])

expect(MorphCLI).to have_received(:execute)
.with(anything, true, config[:development])
end

it 'asks for an API key and saves the config when none is set' do
config[:production].delete(:api_key)
allow(MorphCLI).to receive(:execute)
allow(Thor::LineEditor).to receive(:readline).and_return('shiny-new-key')

described_class.start(['execute'])

expect(Thor::LineEditor).to have_received(:readline)
.with(a_string_matching(/What is your key\?/), anything)
expect(config[:production][:api_key]).to eq('shiny-new-key')
expect(MorphCLI).to have_received(:save_config).with(config)
expect(MorphCLI).to have_received(:execute)
.with(anything, false, config[:production])
end

it 'asks for a new API key and retries when the server rejects it' do
attempts = 0
allow(MorphCLI).to receive(:execute) do
attempts += 1
raise Faraday::UnauthorizedError, '401' if attempts == 1
end
allow(Thor::LineEditor).to receive(:readline).and_return('fresh-key')

expect { described_class.start(['execute']) }
.to output(/Your key isn't working\. Let's try again\./).to_stdout

expect(attempts).to eq(2)
expect(config[:production][:api_key]).to eq('fresh-key')
expect(MorphCLI).to have_received(:save_config).with(config)
end

it 'exits with an error when morph is not reachable' do
allow(MorphCLI).to receive(:execute)
.and_raise(Faraday::ConnectionFailed, 'connection refused')

expect do
expect { described_class.start(['execute']) }
.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
end.to output(%r{Morph doesn't look to be running at https://morph\.io}).to_stderr
end

it 'exits with an error when the morph server fails' do
allow(MorphCLI).to receive(:execute)
.and_raise(Faraday::ServerError, '500')

expect do
expect { described_class.start(['execute']) }
.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
end.to output(/Something has gone wrong on the Morph server/).to_stderr
end

it 'exits with an error on any other request failure' do
allow(MorphCLI).to receive(:execute)
.and_raise(Faraday::BadRequestError, '400')

expect do
expect { described_class.start(['execute']) }
.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
end.to output(%r{Request to https://morph\.io failed}).to_stderr
end
end
end
Loading