From e583c32700e3976d1828a3deb3fc7d58a3dbde3a Mon Sep 17 00:00:00 2001 From: Austin Kabiru Date: Fri, 10 Aug 2018 12:58:14 +0300 Subject: [PATCH 1/4] chore(list): Make bot more high level `FakerBot::Bot` knew too much about `search`; it should only perform "bot ops" related to Faker Reflections and expose methods that commands can access to retrieve what they need --- lib/fakerbot/bot.rb | 26 +++++++++++++----- lib/fakerbot/cli.rb | 16 ++++++++++- lib/fakerbot/command.rb | 41 ++++++++++++++++++++++++++++ lib/fakerbot/commands/list.rb | 17 ++++++++++++ lib/fakerbot/commands/search.rb | 26 ++---------------- lib/fakerbot/templates/list/.gitkeep | 1 + spec/integration/list_spec.rb | 16 +++++++++++ spec/unit/list_spec.rb | 13 +++++++++ 8 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 lib/fakerbot/command.rb create mode 100644 lib/fakerbot/commands/list.rb create mode 100644 lib/fakerbot/templates/list/.gitkeep create mode 100644 spec/integration/list_spec.rb create mode 100644 spec/unit/list_spec.rb diff --git a/lib/fakerbot/bot.rb b/lib/fakerbot/bot.rb index 94105f8..3a25e15 100644 --- a/lib/fakerbot/bot.rb +++ b/lib/fakerbot/bot.rb @@ -20,10 +20,10 @@ def self.my_singleton_methods end end - attr_reader :matching_descendants, :query + attr_reader :descendants_with_methods, :query - def initialize(query) - @matching_descendants = Hash.new { |h, k| h[k] = [] } + def initialize(query = nil) + @descendants_with_methods = Hash.new { |h, k| h[k] = [] } @query = query end @@ -31,11 +31,23 @@ class << self def find(query) new(query).find end + + def list(verbose: false) + new.list(verbose) + end end def find search_descendants_matching_query - matching_descendants + descendants_with_methods + end + + def list(verbose) + faker_descendants.each do |faker| + methods = verbose ? faker.my_singleton_methods : [] + store(faker, methods) + end + descendants_with_methods end private @@ -44,13 +56,13 @@ def search_descendants_matching_query faker_descendants.each do |faker| methods = faker.my_singleton_methods matching = methods.select { |m| m.match?(/#{query}/i) } - save_matching(faker, matching) + store(faker, matching) end end - def save_matching(descendant, matching) + def store(descendant, matching) return if matching.empty? - matching_descendants[descendant].concat(matching) + descendants_with_methods[descendant].concat(matching) end def faker_descendants diff --git a/lib/fakerbot/cli.rb b/lib/fakerbot/cli.rb index 39860a1..16d8408 100644 --- a/lib/fakerbot/cli.rb +++ b/lib/fakerbot/cli.rb @@ -3,6 +3,7 @@ require 'thor' require 'fakerbot/cli' require 'fakerbot/version' +require 'fakerbot/commands/list' require 'fakerbot/commands/search' module FakerBot @@ -16,11 +17,24 @@ def version end map %w[--version -v] => :version + desc 'list', 'List all Faker constants' + method_option :help, aliases: '-h', type: :boolean, + desc: 'Display usage information' + method_option :verbose, aliases: '-v', type: :boolean, + desc: 'Display Faker constants with methods' + def list(*) + if options[:help] + invoke :help, ['list'] + else + Fakerbot::Commands::List.new(options).execute + end + end + desc 'search [Faker]', 'Search Faker method(s)' method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information' method_option :verbose, aliases: '-v', type: :boolean, - desc: 'Display Faker classes with methods' + desc: 'Display Faker constants methods with examples' def search(query) if options[:help] invoke :help, ['search'] diff --git a/lib/fakerbot/command.rb b/lib/fakerbot/command.rb new file mode 100644 index 0000000..835a113 --- /dev/null +++ b/lib/fakerbot/command.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'forwardable' +require 'pastel' +require 'tty/pager' +require 'tty/tree' + +module FakerBot + class Command + extend Forwardable + + def_delegators :command, :run + + def pager + TTY::Pager.new(command: 'less -R') + end + + def screen + TTY::Screen + end + + def tree(input) + TTY::Tree.new do + input.each do |faker, methods| + node Pastel.new.green(faker.to_s) do + methods&.each { |m| leaf Pastel.new.cyan(m.to_s) } + end + end + end + end + + def render(result) + output = tree(result) + if screen.height < output.nodes.size + pager.page output.render + else + puts output.render + end + end + end +end diff --git a/lib/fakerbot/commands/list.rb b/lib/fakerbot/commands/list.rb new file mode 100644 index 0000000..2f41c2c --- /dev/null +++ b/lib/fakerbot/commands/list.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative '../command' + +module Fakerbot + module Commands + class List < FakerBot::Command + def initialize(options) + @options = options + end + + def execute + render FakerBot::Bot.list(verbose: @options[:verbose]) + end + end + end +end diff --git a/lib/fakerbot/commands/search.rb b/lib/fakerbot/commands/search.rb index 3e754ee..a36222c 100644 --- a/lib/fakerbot/commands/search.rb +++ b/lib/fakerbot/commands/search.rb @@ -1,17 +1,12 @@ # frozen_string_literal: true -require 'pastel' -require 'tty/pager' -require 'tty/tree' require 'fakerbot/bot' module FakerBot module Commands - class Search + class Search < FakerBot::Command def initialize(options) @options = options - @pager = TTY::Pager.new(command: 'less -R') - @screen = TTY::Screen end def execute(input) @@ -20,26 +15,9 @@ def execute(input) private - attr_reader :screen, :pager - def render(result) return not_found if result.empty? - output = tree(result) - if screen.height < output.nodes.size - pager.page output.render - else - puts output.render - end - end - - def tree(input) - TTY::Tree.new do - input.each do |faker, methods| - node Pastel.new.green(faker.to_s) do - methods.each { |m| leaf Pastel.new.cyan(m.to_s) } - end - end - end + super end def not_found diff --git a/lib/fakerbot/templates/list/.gitkeep b/lib/fakerbot/templates/list/.gitkeep new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/lib/fakerbot/templates/list/.gitkeep @@ -0,0 +1 @@ +# diff --git a/spec/integration/list_spec.rb b/spec/integration/list_spec.rb new file mode 100644 index 0000000..067783a --- /dev/null +++ b/spec/integration/list_spec.rb @@ -0,0 +1,16 @@ +RSpec.describe "`fakerbot list` command", type: :cli do + it "executes `fakerbot help list` command successfully" do + output = `fakerbot help list` + expected_output = <<-OUT +Usage: + fakerbot list + +Options: + -h, [--help], [--no-help] # Display usage information + +Command description... + OUT + + expect(output).to eq(expected_output) + end +end diff --git a/spec/unit/list_spec.rb b/spec/unit/list_spec.rb new file mode 100644 index 0000000..874e5ef --- /dev/null +++ b/spec/unit/list_spec.rb @@ -0,0 +1,13 @@ +require 'fakerbot/commands/list' + +RSpec.describe Fakerbot::Commands::List do + it "executes `list` command successfully" do + output = StringIO.new + options = {} + command = Fakerbot::Commands::List.new(options) + + command.execute(output: output) + + expect(output.string).to eq("OK\n") + end +end From 4efd14abc5586a7c67354d327cde48455373a46a Mon Sep 17 00:00:00 2001 From: Austin Kabiru Date: Fri, 10 Aug 2018 13:18:02 +0300 Subject: [PATCH 2/4] =?UTF-8?q?feat(list):=20Add=20list=20command=20?= =?UTF-8?q?=F0=9F=98=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 2 +- lib/fakerbot/bot.rb | 17 ++++++++++------- lib/fakerbot/version.rb | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ac300ad..d7f2c54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - fakerbot (0.2.4) + fakerbot (0.3.0) faker pastel (~> 0.7.2) thor (~> 0.20.0) diff --git a/lib/fakerbot/bot.rb b/lib/fakerbot/bot.rb index 3a25e15..a608700 100644 --- a/lib/fakerbot/bot.rb +++ b/lib/fakerbot/bot.rb @@ -43,15 +43,18 @@ def find end def list(verbose) + verbose ? all_descendants_with_methods : faker_descendants + end + + private + + def all_descendants_with_methods faker_descendants.each do |faker| - methods = verbose ? faker.my_singleton_methods : [] - store(faker, methods) + store(faker, faker.my_singleton_methods) end descendants_with_methods end - private - def search_descendants_matching_query faker_descendants.each do |faker| methods = faker.my_singleton_methods @@ -60,9 +63,9 @@ def search_descendants_matching_query end end - def store(descendant, matching) - return if matching.empty? - descendants_with_methods[descendant].concat(matching) + def store(descendant, methods) + return if methods.empty? + descendants_with_methods[descendant].concat(methods) end def faker_descendants diff --git a/lib/fakerbot/version.rb b/lib/fakerbot/version.rb index f074552..0f4eb0c 100644 --- a/lib/fakerbot/version.rb +++ b/lib/fakerbot/version.rb @@ -1,3 +1,3 @@ module FakerBot - VERSION = '0.2.4'.freeze + VERSION = '0.3.0'.freeze end From cfc9ef49dc2edbfa760a3347fbb0e6501c2cca70 Mon Sep 17 00:00:00 2001 From: Austin Kabiru Date: Fri, 10 Aug 2018 14:01:30 +0300 Subject: [PATCH 3/4] chore(list): Add unit and integration specs --- lib/fakerbot/command.rb | 12 +++++++----- lib/fakerbot/commands/list.rb | 4 ++-- spec/integration/list_spec.rb | 32 +++++++++++++++++++++++--------- spec/unit/list_spec.rb | 28 +++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/lib/fakerbot/command.rb b/lib/fakerbot/command.rb index 835a113..6cd92c6 100644 --- a/lib/fakerbot/command.rb +++ b/lib/fakerbot/command.rb @@ -29,12 +29,14 @@ def tree(input) end end - def render(result) - output = tree(result) - if screen.height < output.nodes.size - pager.page output.render + def render(result, output = $stdout) + result_tree = tree(result) + view = result_tree.render + if screen.height < result_tree.nodes.size + # paginate when attached to terminal + output.tty? ? pager.page(view) : output.puts(view) else - puts output.render + output.puts view end end end diff --git a/lib/fakerbot/commands/list.rb b/lib/fakerbot/commands/list.rb index 2f41c2c..d79f8dd 100644 --- a/lib/fakerbot/commands/list.rb +++ b/lib/fakerbot/commands/list.rb @@ -9,8 +9,8 @@ def initialize(options) @options = options end - def execute - render FakerBot::Bot.list(verbose: @options[:verbose]) + def execute(output: $stdout) + render FakerBot::Bot.list(verbose: @options[:verbose]), output end end end diff --git a/spec/integration/list_spec.rb b/spec/integration/list_spec.rb index 067783a..bbd249d 100644 --- a/spec/integration/list_spec.rb +++ b/spec/integration/list_spec.rb @@ -1,16 +1,30 @@ -RSpec.describe "`fakerbot list` command", type: :cli do - it "executes `fakerbot help list` command successfully" do +# frozen_string_literal: true + +RSpec.describe '`fakerbot list` command', type: :cli do + it 'executes `fakerbot help list` command successfully' do output = `fakerbot help list` - expected_output = <<-OUT -Usage: - fakerbot list + expected_output = <<~OUT + Usage: + fakerbot list -Options: - -h, [--help], [--no-help] # Display usage information + Options: + -h, [--help], [--no-help] # Display usage information + -v, [--verbose], [--no-verbose] # Display Faker constants with methods -Command description... - OUT + List all Faker constants + OUT expect(output).to eq(expected_output) end + + it 'executes `fakerbot list` command successfully' do + output = `fakerbot list` + expect(output).to match(/Faker::/) + end + + it 'executes `fakerbot list -v` command successfully' do + output = `fakerbot list -v` + expect(output).to match(/Faker::/) + expect(output).to match(/└──/) + end end diff --git a/spec/unit/list_spec.rb b/spec/unit/list_spec.rb index 874e5ef..b019e65 100644 --- a/spec/unit/list_spec.rb +++ b/spec/unit/list_spec.rb @@ -1,13 +1,31 @@ require 'fakerbot/commands/list' RSpec.describe Fakerbot::Commands::List do - it "executes `list` command successfully" do - output = StringIO.new - options = {} - command = Fakerbot::Commands::List.new(options) + let(:output) { StringIO.new } + let(:options) { {} } + let(:command) { Fakerbot::Commands::List.new(options) } + before do command.execute(output: output) + end + + context 'when single `list` command' do + it 'executes successfully' do + expect(output.string).to match(/Faker/) + expect(output.string.lines.size).to be_positive + end + end + + context 'when `list -v` verbose command' do + let(:options) { { verbose: true } } + + it 'executes successfully' do + constant = output.string.lines[0] + method = output.string.lines[1] - expect(output.string).to eq("OK\n") + expect(constant).to match(/Faker::/) + expect(method).not_to match(/Faker::/) + expect(method).to match(/└──/) + end end end From f9c9840e297b44d02ec784211b3b3c082544fe0a Mon Sep 17 00:00:00 2001 From: Austin Kabiru Date: Fri, 10 Aug 2018 14:24:19 +0300 Subject: [PATCH 4/4] chore(ad lid): More tests! --- lib/fakerbot/cli.rb | 2 +- lib/fakerbot/commands/list.rb | 2 +- lib/fakerbot/commands/search.rb | 9 +++++---- spec/integration/search_spec.rb | 34 +++++++++++++++++++++++++++++++++ spec/unit/list_spec.rb | 4 ++-- spec/unit/search_spec.rb | 28 +++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 spec/integration/search_spec.rb create mode 100644 spec/unit/search_spec.rb diff --git a/lib/fakerbot/cli.rb b/lib/fakerbot/cli.rb index 16d8408..2efebdd 100644 --- a/lib/fakerbot/cli.rb +++ b/lib/fakerbot/cli.rb @@ -26,7 +26,7 @@ def list(*) if options[:help] invoke :help, ['list'] else - Fakerbot::Commands::List.new(options).execute + FakerBot::Commands::List.new(options).execute end end diff --git a/lib/fakerbot/commands/list.rb b/lib/fakerbot/commands/list.rb index d79f8dd..994cf74 100644 --- a/lib/fakerbot/commands/list.rb +++ b/lib/fakerbot/commands/list.rb @@ -2,7 +2,7 @@ require_relative '../command' -module Fakerbot +module FakerBot module Commands class List < FakerBot::Command def initialize(options) diff --git a/lib/fakerbot/commands/search.rb b/lib/fakerbot/commands/search.rb index a36222c..d959a56 100644 --- a/lib/fakerbot/commands/search.rb +++ b/lib/fakerbot/commands/search.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fakerbot/bot' +require_relative '../command' module FakerBot module Commands @@ -9,15 +10,15 @@ def initialize(options) @options = options end - def execute(input) - render FakerBot::Bot.find(input) + def execute(input, output: $stdout) + render FakerBot::Bot.find(input), output end private - def render(result) + def render(result, output) return not_found if result.empty? - super + super(result, output) end def not_found diff --git a/spec/integration/search_spec.rb b/spec/integration/search_spec.rb new file mode 100644 index 0000000..c9c8aac --- /dev/null +++ b/spec/integration/search_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +RSpec.describe '`fakerbot search` command', type: :cli do + it 'executes `fakerbot help search` command successfully' do + output = `fakerbot help search` + expected_output = <<~OUT + Usage: + fakerbot search [Faker] + + Options: + -h, [--help], [--no-help] # Display usage information + -v, [--verbose], [--no-verbose] # Display Faker constants methods with examples + + Search Faker method(s) + OUT + + expect(output).to eq(expected_output) + end + + context 'when search query exists' do + it 'returns results' do + output = `fakerbot search name` + expect(output).to match(/Faker::/) + expect(output).to match(/└──/) + end + end + + context 'when search query does not exist' do + it 'returns a not found message' do + output = `fakerbot search asdasdhk` + expect(output).to match(/Sorry, we couldn't find a match/) + end + end +end diff --git a/spec/unit/list_spec.rb b/spec/unit/list_spec.rb index b019e65..363bfd6 100644 --- a/spec/unit/list_spec.rb +++ b/spec/unit/list_spec.rb @@ -1,9 +1,9 @@ require 'fakerbot/commands/list' -RSpec.describe Fakerbot::Commands::List do +RSpec.describe FakerBot::Commands::List do let(:output) { StringIO.new } let(:options) { {} } - let(:command) { Fakerbot::Commands::List.new(options) } + let(:command) { FakerBot::Commands::List.new(options) } before do command.execute(output: output) diff --git a/spec/unit/search_spec.rb b/spec/unit/search_spec.rb new file mode 100644 index 0000000..a86e936 --- /dev/null +++ b/spec/unit/search_spec.rb @@ -0,0 +1,28 @@ +require 'fakerbot/commands/search' + +RSpec.describe FakerBot::Commands::Search do + let(:output) { StringIO.new } + let(:options) { {} } + let(:command) { described_class.new(options) } + + context 'when query object exists' do + before do + command.execute('image', output: output) + end + + it 'returns results' do + expect(output.string).to match(/Faker/) + expect(output.string.lines.size).to be_positive + end + end + + context 'when query object does not exist' do + before do + command.execute('hasjdhaksjd', output: output) + end + + it 'returns nil' do + expect(output.string).to be_empty + end + end +end