Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI - Integrate fakerbot 🤖 #1507

Merged
merged 17 commits into from
Mar 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat(cli): Pull in fakerbot gem logic
  • Loading branch information
akabiru committed Jan 5, 2019
commit e34a04bc7b0c0446623f13a0179975ba10843354
51 changes: 51 additions & 0 deletions lib/faker/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'thor'
require 'faker/version'
require 'faker/cli/commands/list'
require 'faker/cli/commands/search'

module Faker
module CLI
class Base < Thor
Error = Class.new(StandardError)

desc 'version', 'Faker version'
def version
require_relative 'version'
puts "v#{Faker::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 :show_methods, aliases: '-m', type: :boolean, default: true,
desc: 'Display Faker constants with methods'
method_option :verbose, aliases: '-v', type: :boolean,
desc: 'Include sample Faker output'
def list(*)
if options[:help]
invoke :help, ['list']
else
Faker::CLI::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 :show_methods, aliases: '-m', type: :boolean, default: true,
desc: 'Display Faker constants with methods'
method_option :verbose, aliases: '-v', type: :boolean,
desc: 'Include sample Faker output'
def search(query)
if options[:help]
invoke :help, ['search']
else
Faker::CLI::Commands::Search.new(options).execute(query)
end
end
end
end
end
19 changes: 19 additions & 0 deletions lib/faker/cli/command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'forwardable'

require_relative 'reflector'
require_relative 'renderer'

module Faker
module CLI
class Command
extend Forwardable

def_delegators :command, :run
attr_reader :options

def render(result, output)
Renderer.call(result, options, output)
end
end
end
end
20 changes: 20 additions & 0 deletions lib/faker/cli/commands/list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require_relative '../command'

module Faker
module CLI
module Commands
class List < Faker::CLI::Command
def initialize(options)
@options = options
end

def execute(output: $stdout)
result = Faker::CLI::Reflector.list(
show_methods: options[:show_methods]
)
render(result, output)
end
end
end
end
end
30 changes: 30 additions & 0 deletions lib/faker/cli/commands/search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require_relative '../command'

module Faker
module CLI
module Commands
class Search < Faker::CLI::Command
def initialize(options)
@options = options
end

def execute(input, output: $stdout)
result = Faker::CLI::Reflector.find(input)
render(result, output)
end

private

def render(result, output)
return not_found if result.empty?

super(result, output)
end

def not_found
puts "\nSorry, we couldn't find a match 😢", "\n"
end
end
end
end
end
81 changes: 81 additions & 0 deletions lib/faker/cli/reflector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Faker
module CLI
# Exposes `Faker` reflection methods
# @api private
class Reflector
Faker::Base.class_eval do
# Select `Faker` subclasses
# @return [Array] `Faker::Base` sub classes
def self.descendants
@descendants ||= ObjectSpace.each_object(Class).select do |klass|
klass < self
end
end

# Select public class methods
# @return [Array] public methods
def self.my_singleton_methods
singleton_methods(false).select { |m| respond_to?(m) }
end
end

attr_reader :descendants_with_methods, :query

def initialize(query = nil)
@descendants_with_methods = Hash.new { |h, k| h[k] = [] }
@query = query
end

class << self
def find(query)
new(query).find
end

def list(show_methods: false)
new.list(show_methods)
end
end

def find
search_descendants_matching_query
descendants_with_methods
end

def list(show_methods)
show_methods ? all_descendants_with_methods : faker_descendants
end

private

def all_descendants_with_methods
faker_descendants.each do |faker|
store(faker, faker.my_singleton_methods)
end
descendants_with_methods
end

def search_descendants_matching_query
faker_descendants.each do |faker|
methods = faker.my_singleton_methods
matching = methods.select { |m| query_matches?(m.to_s) }
store(faker, matching)
end
end

def query_matches?(method_name)
method_name_parts = method_name.split(/_/).reject(&:empty?)
query.match(/#{method_name_parts.join('|')}/)
end

def store(descendant, methods)
return if methods.empty?

descendants_with_methods[descendant].concat(methods)
end

def faker_descendants
@faker_descendants ||= Faker::Base.descendants
end
end
end
end
91 changes: 91 additions & 0 deletions lib/faker/cli/renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require 'pastel'
require 'tty/pager'
require 'tty/screen'
require 'tty/tree'

module Faker
module CLI
class Renderer
attr_reader :crayon, :hash, :options, :output, :pager

def self.call(*args)
new(*args).call
end

def initialize(hash, options, output)
@hash = hash
@options = options.deep_symbolize_keys
@output = output
@crayon = Pastel.new(enabled: output.tty?)
@pager = TTY::Pager.new(command: 'less -R')
end

def call
if paginable?
pager.page(render)
else
output.puts(render)
end
end

def render
tree.render
end

def tree
@tree ||= TTY::Tree.new(build_tree)
end

def paginable?
gt_screen_height? && output.tty?
end

def gt_screen_height?
tree.nodes.size > TTY::Screen.height
end

private

def build_tree
result = hash.reduce({}) do |h, (faker, methods)|
h.merge! node(faker, methods&.sort)
end

result.sort_by(&:to_s).to_h
end

def node(const, methods)
{
crayon.green(const.to_s) => leaf(const, methods)
}
end

def leaf(const, methods)
(methods || []).map { |m| crayon.cyan(*leaf_args(m, const)) }
end

def leaf_args(method, const)
[method.to_s].tap { |arr| verbose_output(method, const, arr) if verbose? }
end

def verbose?
options[:verbose] == true
end

def verbose_output(method, const, arr)
fake, message = faker_method(method, const)
arr << crayon.dim.white("=> #{fake}") << crayon.dim.magenta.bold(message.to_s)
end

def faker_method(method, const)
[const.public_send(method), ensure_method_is_supported(method, const)]
rescue ArgumentError => _exception
['N/A', '']
end

def ensure_method_is_supported(method, const)
const.respond_to?(:"_deprecated_#{method}") ? ' ( WILL BE DEPRECATED )' : ''
end
end
end
end