Skip to content

Commit

Permalink
Merge f28de6e into 4544c0b
Browse files Browse the repository at this point in the history
  • Loading branch information
matthutchinson committed Jan 9, 2018
2 parents 4544c0b + f28de6e commit 43d21ab
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Expand Up @@ -48,7 +48,7 @@ Metrics/CyclomaticComplexity:
# Offense count: 23
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 23
Max: 24

# Offense count: 5
Metrics/PerceivedComplexity:
Expand Down
6 changes: 3 additions & 3 deletions .travis.yml
Expand Up @@ -4,9 +4,9 @@ cache: bundler
rvm:
- 2.0.0
- 2.1.10
- 2.2.8
- 2.3.5
- 2.4.2
- 2.2.9
- 2.3.6
- 2.4.3
- ruby-head

before_install:
Expand Down
2 changes: 1 addition & 1 deletion features/lolcommits.feature
Expand Up @@ -123,7 +123,7 @@ Feature: Basic UI functionality
Then the output should contain "Disabling plugin: loltext - answer with 'true' to enable & configure"
And a file named "~/.lolcommits/test/config.yml" should exist
When I successfully run `lolcommits --test --show-config`
Then the output should match /loltext:\s+enabled: false/
Then the output should match /loltext:\s+:enabled: false/

Scenario: test capture should work regardless of whether in a lolrepo
Given I am in a directory named "nothingtoseehere"
Expand Down
3 changes: 2 additions & 1 deletion features/step_definitions/lolcommits_steps.rb
Expand Up @@ -160,7 +160,8 @@ def default_loldir
end

Then(/^the output should contain a list of plugins$/) do
step %(the output should contain "Available plugins: ")
step %(the output should contain "Installed plugins: (* enabled)")
step %(the output should contain "[*] loltext")
end

When(/^I do a git commit with commit message "(.*?)"$/) do |commit_msg|
Expand Down
21 changes: 21 additions & 0 deletions lib/core_ext/hash/hash_dig.rb
@@ -0,0 +1,21 @@
# Backport Hash#dig to Ruby < 2.3
# inspired by https://github.com/Invoca/ruby_dig

module HashDig
def dig(key, *rest)
value = self[key]
if value.nil? || rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
else
raise TypeError, "#{value.class} does not have #dig method"
end
end
end

if RUBY_VERSION < '2.3'
class Hash
include HashDig
end
end
2 changes: 2 additions & 0 deletions lib/lolcommits.rb
@@ -1,5 +1,7 @@
$LOAD_PATH.unshift File.expand_path('.')

require 'core_ext/hash/hash_dig' # backport Hash#dig for Ruby < 2.3

require 'mini_magick'
require 'core_ext/mini_magick/utilities'
require 'fileutils'
Expand Down
68 changes: 40 additions & 28 deletions lib/lolcommits/configuration.rb
Expand Up @@ -11,11 +11,13 @@ def initialize(plugin_manager, test_mode: false)
@loldir = Configuration.loldir_for('test') if test_mode
end

def read_configuration
return {} unless File.exist?(configuration_file)
# TODO: change to safe_load when Ruby 2.0.0 support drops
# YAML.safe_load(File.open(configuration_file), [Symbol])
YAML.load(File.open(configuration_file)) || {}
def yaml
@_yaml ||= begin
return Hash.new({}) unless File.exist?(configuration_file)
# TODO: change to safe_load when Ruby 2.0.0 support drops
# YAML.safe_load(File.open(configuration_file), [Symbol])
YAML.load(File.open(configuration_file)) || Hash.new({})
end
end

def configuration_file
Expand Down Expand Up @@ -65,19 +67,23 @@ def frames_loc
end

def list_plugins
puts "Available plugins: \n * #{plugin_manager.plugin_names.join("\n * ")}"
puts "Installed plugins: (* enabled)\n"

plugin_manager.plugins.each do |gem_plugin|
plugin = gem_plugin.plugin_klass.new(config: yaml[gem_plugin.name])
puts " [#{plugin.enabled? ? '*' : '-'}] #{gem_plugin.name}"
end
end

def find_plugin(plugin_name_option)
plugin_name = plugin_name_option.empty? ? ask_for_plugin_name : plugin_name_option
return if plugin_name.empty?
def find_plugin(name)
plugin_name = name.empty? ? ask_for_plugin_name : name
plugin = plugin_manager.find_by_name(plugin_name)

plugin_klass = plugin_manager.find_by_name(plugin_name)
return plugin_klass.new(config: self) if plugin_klass
return plugin if plugin

puts "Unable to find plugin: '#{plugin_name}'"
return if plugin_name_option.empty?
list_plugins
list_plugins unless name.empty?
nil
end

def ask_for_plugin_name
Expand All @@ -91,37 +97,43 @@ def do_configure!(plugin_name)
plugin = find_plugin(plugin_name.to_s.strip)
return unless plugin

config = read_configuration
plugin_name = plugin.class.name
plugin_config = plugin.configure_options!
puts "Configuring plugin: #{plugin.name}\n"
plugin_config = plugin.plugin_klass.new(config: yaml[plugin_name]).configure_options! || {}

unless plugin_config[:enabled]
puts "Disabling plugin: #{plugin.name} - answer with 'true' to enable & configure"
end
rescue Interrupt
# e.g. user Ctrl+c or aborted by plugin configure_options!
puts "\nDisabling plugin: #{plugin_name} - configuration aborted"
plugin_config ||= {}
plugin_config['enabled'] = false
if plugin
puts "\nConfiguration aborted: #{plugin.name} has been disabled"
plugin_config ||= {}
plugin_config[:enabled] = false
else
puts "\n"
end
ensure
if plugin
# set and save plugin config
config[plugin_name] = plugin_config
save(config)
# save plugin config
save(plugin.name, plugin_config)

# print config if plugin was enabled
if plugin_config && plugin_config['enabled']
puts "\nSuccessfully configured plugin: #{plugin_name} - at path '#{configuration_file}'"
puts read_configuration[plugin_name].to_yaml.to_s
if plugin_config[:enabled]
puts "\nSuccessfully configured plugin: #{plugin.name} - at path '#{configuration_file}'"
puts plugin_config.to_yaml.to_s
end
end
end

def save(config)
config_file_contents = config.to_yaml
def save(plugin_name, plugin_config)
config_file_contents = yaml.merge(plugin_name => plugin_config).to_yaml
File.open(configuration_file, 'w') do |f|
f.write(config_file_contents)
end
end

def to_s
read_configuration.to_yaml.to_s
yaml.to_yaml.to_s
end

# class methods
Expand Down
11 changes: 10 additions & 1 deletion lib/lolcommits/gem_plugin.rb
Expand Up @@ -44,6 +44,10 @@ def plugin_klass
warn "failed to load constant from plugin gem '#{plugin_klass_name}: #{e}'"
end

def plugin_instance(runner)
plugin_klass.new(runner: runner, config: runner.config.yaml[name])
end

def gem_name
gem_spec.name
end
Expand All @@ -55,7 +59,12 @@ def gem_path
end

def plugin_klass_name
gem_path.split('/').map(&:capitalize).join('::')
# convert gem paths to plugin classes e.g.
# lolcommits/loltext --> Lolcommits::Plugin::Loltext
# lolcommits/term_output --> Lolcommits::Plugin::TermOutput
gem_path.split('/').insert(1, 'plugin').collect do |c|
c.split('_').collect(&:capitalize).join
end.join('::')
end
end
end
138 changes: 71 additions & 67 deletions lib/lolcommits/plugin/base.rb
@@ -1,30 +1,16 @@
require 'lolcommits/plugin/configuration_helper'

module Lolcommits
module Plugin
class Base
attr_accessor :runner, :config, :options

def initialize(runner: nil, config: nil)
self.runner = runner
self.config = config || runner.config
self.options = ['enabled']
end
include Lolcommits::Plugin::ConfigurationHelper

def execute_pre_capture
return unless configured_and_enabled?
debug 'I am enabled, about to run pre capture'
run_pre_capture
end

def execute_post_capture
return unless configured_and_enabled?
debug 'I am enabled, about to run post capture'
run_post_capture
end
attr_accessor :runner, :configuration, :options

def execute_capture_ready
return unless configured_and_enabled?
debug 'I am enabled, about to run capture ready'
run_capture_ready
def initialize(runner: nil, config: {})
self.runner = runner
self.configuration = config || {}
self.options = [:enabled]
end

def run_pre_capture; end
Expand All @@ -33,51 +19,51 @@ def run_post_capture; end

def run_capture_ready; end

def configuration
saved_config = config.read_configuration
return {} unless saved_config
saved_config[self.class.name] || {}
end

# ask for plugin options
##
# Prompts the user to configure all plugin options.
#
# Available options can be defined in an Array (@options instance var)
# and/or a Hash (by overriding the `default_options` method).
#
# By default (on initialize), `@options` is set with `[:enabled]`. This is
# mandatory since `enabled?` checks this option is true before running any
# capture hooks.
#
# Using a Hash to define default options allows you to:
#
# - including default values
# - define nested options, user is prompted for each nested option key
#
# `configure_option_hash` will iterate over all options prompting the user
# for input and building the configuration Hash.
#
# Lolcommits will save this Hash to a YAML file. During the capture
# process the configuration is loaded, parsed and available in the plugin
# class as `@configuration`. Or if you want to fall back to default
# values, you should use `config_option` to fetch option values.
#
# Alternatively you can override this method entirely to customise the
# process. A helpful `parse_user_input` method is available to help parse
# strings from STDIN (into boolean, integer or nil values).
#
# @return [Hash] the configured plugin options
def configure_options!
puts "Configuring plugin: #{self.class.name}\n"
options.reduce({}) do |acc, option|
print "#{option}: "
val = parse_user_input(gets.strip)
# check enabled option value, disable and abort config if not true
if option == 'enabled'
unless val == true
puts "Disabling plugin: #{self.class.name} - answer with 'true' to enable & configure"
break { option => false }
end
end

acc.merge(option => val)
end
configure_option_hash(
Hash[options.map { |key, _value| [key, nil] }].merge(default_options)
)
end

def parse_user_input(str)
# cater for bools, strings, ints and blanks
if 'true'.casecmp(str).zero?
true
elsif 'false'.casecmp(str).zero?
false
elsif str =~ /^[0-9]+$/
str.to_i
elsif str.strip.empty?
nil
else
str
end
def default_options
{}
end

def configured_and_enabled?
valid_configuration? && enabled?
def config_option(*keys)
configuration.dig(*keys) || default_options.dig(*keys)
end

def enabled?
configuration['enabled'] == true
# legacy configs (< 0.9.9) used a string key
configuration[:enabled] || configuration['enabled']
end

# check config is valid
Expand All @@ -94,12 +80,12 @@ def configured?
# dont puts or print if the runner wants to be silent (stealth mode)
def puts(*args)
return if runner && runner.capture_stealth
super(args)
super(*args)
end

def print(args)
def print(*args)
return if runner && runner.capture_stealth
super(args)
super(*args)
end

# helper to log errors with a message via debug
Expand All @@ -113,11 +99,6 @@ def debug(msg)
super("#{self.class}: " + msg)
end

# identifying plugin name (for config, listing)
def self.name
'plugin'
end

# Returns position(s) of when a plugin should run during the capture
# process.
#
Expand All @@ -132,6 +113,29 @@ def self.name
def self.runner_order
[]
end

private

def configure_option_hash(option_hash, spacing_count = 0)
option_hash.keys.reduce({}) do |acc, option|
option_value = option_hash[option]
prefix = ' ' * spacing_count
if option_value.is_a?(Hash)
puts "#{prefix}#{option}:\n"
acc.merge(option => configure_option_hash(option_value, (spacing_count + 1)))
else
print "#{prefix}#{option.to_s.tr('_', ' ')}#{" (#{option_value})" unless option_value.nil?}: "
user_value = parse_user_input(gets.chomp.strip)

# if not enabled, disable and return without setting more options
# useful with nested hash configs, place enabled as first sub-option
# if answer is !true, no further sub-options will be prompted for
return { option => false } if option == :enabled && user_value != true

acc.merge(option => user_value)
end
end
end
end
end
end

0 comments on commit 43d21ab

Please sign in to comment.