Skip to content
This repository has been archived by the owner on Apr 6, 2021. It is now read-only.

Commit

Permalink
Merge pull request #35 from shantytown/29-envs-as-ags
Browse files Browse the repository at this point in the history
Remove all global state, turn Env into a monad.
  • Loading branch information
janstenpickle committed Oct 1, 2015
2 parents 14d6a3e + bf1edd8 commit aa266ac
Show file tree
Hide file tree
Showing 50 changed files with 1,165 additions and 1,396 deletions.
15 changes: 0 additions & 15 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,3 @@ LineLength:

Metrics/ModuleLength:
Max: 150

# This is a stupid lint. Yes, module_function is more explanatory than extend self for most use cases. However, the
# former does not work if you want the methods to be able to call private methods; the latter does. See
# https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along for a detailed explanation of the
# problem.
ModuleFunction:
Enabled: False

# FIXME: Only enabling because we are using the singleton pattern still for the
# Env and TaskEnv classes and they store memoized values that need to be
# preserved even after mixing in. Without class vars, this does not work. We
# plan to refactor these singletons out and into class instances that are passed
# around instead as needed.
ClassVars:
Enabled: False
Empty file removed .shanty.yml
Empty file.
5 changes: 5 additions & 0 deletions Shantyconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'shanty/plugins/bundler_plugin'
require 'shanty/plugins/cucumber_plugin'
require 'shanty/plugins/rspec_plugin'
require 'shanty/plugins/rubocop_plugin'
require 'shanty/plugins/rubygem_plugin'
24 changes: 16 additions & 8 deletions lib/shanty.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
require 'i18n'
require 'pathname'

require 'shanty/cli'
require 'shanty/env'
require 'shanty/graph'

# Require all plugins and task sets.
Dir[File.join(__dir__, 'shanty', '{plugins,task_sets}', '*.rb')].each { |f| require f }
require 'shanty/graph_factory'

module Shanty
# Main shanty class
class Shanty
include Env

# This is the root directory where the Shanty gem is located. Do not confuse this with the root of the repository
# in which Shanty is operating, which is available via the TaskEnv class.
GEM_ROOT = File.expand_path(File.join(__dir__, '..'))

def initialize
@env = Env.new
end

def start!
setup_i18n
require!
Cli.new(TaskSet.task_sets).run
execute_shantyconfig!
Cli.new(@env.task_sets, @env, graph).run
end

private

def execute_shantyconfig!
config_path = File.join(@env.root, Env::CONFIG_FILE)
@env.instance_eval(File.read(config_path), config_path)
end

def graph
GraphFactory.new(@env).graph
end

def setup_i18n
I18n.enforce_available_locales = true
I18n.load_path = Dir[File.join(GEM_ROOT, 'translations', '*.yml')]
Expand Down
12 changes: 6 additions & 6 deletions lib/shanty/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
require 'shanty/info'
require 'shanty/task_set'
require 'shanty/env'
require 'shenanigans/hash/to_ostruct'
require 'deep_merge'

module Shanty
# Public: Handle the CLI interface between the user and the registered tasks
Expand All @@ -16,8 +14,10 @@ class Cli

CONFIG_FORMAT = '[plugin]:[key] [value]'

def initialize(task_sets)
def initialize(task_sets, env, graph)
@task_sets = task_sets
@env = env
@graph = graph
end

def tasks
Expand All @@ -42,7 +42,7 @@ def run
def global_config
global_option('-c', '--config [CONFIG]', "Add config via command line in the format #{CONFIG_FORMAT}") do |config|
if (match = config.match(/(?<plugin>\S+):(?<key>\S+)\s+(?<value>\S+)/))
Env.config.merge!(match[:plugin] => { match[:key] => match[:value] })
@env.config[match[:plugin].to_sym][match[:key].to_sym] = match[:value]
else
fail(I18n.t('cli.invalid_config_param', actual: config, expected: CONFIG_FORMAT))
end
Expand Down Expand Up @@ -73,14 +73,14 @@ def add_options_to_command(task, command)
def add_action_to_command(name, task, command)
command.action do |_, options|
task = tasks[name]
options.default(Hash[defaults_for_options(task)])
options.default(defaults_for_options(task).to_h)
enforce_required_options(task, options)
execute_task(name, task, options)
end
end

def execute_task(name, task, options)
klass = task[:klass].new
klass = task[:klass].new(@env, @graph)
arity = klass.method(name).arity
args = (arity >= 1 ? [options] : [])
klass.send(name, *args)
Expand Down
47 changes: 0 additions & 47 deletions lib/shanty/config.rb

This file was deleted.

90 changes: 33 additions & 57 deletions lib/shanty/env.rb
Original file line number Diff line number Diff line change
@@ -1,84 +1,60 @@
require 'i18n'
require 'logger'
require 'pathname'
require 'shanty/file_tree'
require 'shanty/plugin'
require 'shanty/plugins/shantyfile_plugin'
require 'shanty/task_set'
require 'shanty/task_sets/basic_task_set'
require 'yaml'
require 'shenanigans/hash/to_ostruct'

require 'shanty/config'
require 'shanty/project_tree'

module Shanty
#
module Env
# Idiom to allow singletons that can be mixed in: http://ozmm.org/posts/singin_singletons.html
extend self

CONFIG_FILE = '.shanty.yml'
# RWS Monad pattern.
class Env
CONFIG_FILE = 'Shantyconfig'

# This must be defined first due to being a class var that isn't first
# first accessed with ||=.
@@config = nil

def clear!
@@logger = nil
@@environment = nil
@@build_number = nil
@@root = nil
@@config = nil
@@project_tree = nil
def root
@root ||= find_root
end

def require!
Dir.chdir(root) do
(config['require'] || []).each do |path|
requires_in_path(path).each { |f| require File.join(root, f) }
end
end
def file_tree
@file_tree ||= FileTree.new(root)
end

def logger
@@logger ||= Logger.new($stdout).tap do |logger|
logger.formatter = proc do |_, datetime, _, msg|
"#{datetime}: #{msg}\n"
end
end
def plugins
@plugins ||= [Plugins::ShantyfilePlugin]
end

def environment
@@environment ||= ENV['SHANTY_ENV'] || 'local'
def task_sets
@task_sets ||= [TaskSets::BasicTaskSet]
end

def build_number
@@build_number ||= (ENV['SHANTY_BUILD_NUMBER'] || 1).to_i
def projects
@projects ||= {}
end

def root
@@root ||= find_root
def config
@config ||= Hash.new { |h, k| h[k] = {} }
end

def project_tree
@@project_tree ||= ProjectTree.new(root)
end
def require(*args)
new_plugins = []
new_task_sets = []

def config
@@config ||= Config.new(root, environment)
rescue RuntimeError
# Create config object without .shanty.yml if the project root cannot be resolved
@@config ||= Config.new(nil, environment)
end
Plugin.send(:define_singleton_method, :inherited) { |klass| new_plugins << klass }
TaskSet.send(:define_singleton_method, :inherited) { |klass| new_task_sets << klass }

private
super(*args)

def requires_in_path(path)
if File.directory?(path)
Dir[File.join(path, '**', '*.rb')]
elsif File.exist?(path)
[path]
else
Dir[path]
end
Plugin.singleton_class.send(:remove_method, :inherited)
TaskSet.singleton_class.send(:remove_method, :inherited)

plugins.concat(new_plugins)
task_sets.concat(new_task_sets)
end

private

def find_root
fail I18n.t('missing_root', config_file: CONFIG_FILE) if root_dir.nil?
root_dir
Expand Down
2 changes: 1 addition & 1 deletion lib/shanty/project_tree.rb → lib/shanty/file_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Shanty
#
# FIXME: The ignores are not implemented yet, this work has been recorded in
# issue #9 (https://github.com/shantytown/shanty/issues/9).
class ProjectTree
class FileTree
# Allow double globbing, and matching hidden files.
GLOB_FLAGS = File::FNM_EXTGLOB | File::FNM_DOTMATCH
# FIXME: Basic ignores until the .gitignore file can be loaded instead.
Expand Down
30 changes: 18 additions & 12 deletions lib/shanty/project_sorter.rb → lib/shanty/graph_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,45 @@

module Shanty
# Public: Sorts projects using Tarjan's strongly connected components algorithm.
class ProjectSorter
class GraphFactory
include TSort

# Public: Initialise a ProjectLinker.
#
# projects - An array of projects to use. These will be mutated and sorted when the linking takes place.
def initialize(projects)
@projects = projects
# env - ...
def initialize(env)
@env = env
end

# Private: Given a list of projects, sort them and construct a graph.
#
# The sorting uses Tarjan's strongly connected components algorithm.
#
# Returns a Graph with the projects sorted.
def sort
def graph
Graph.new(project_path_trie, tsort)
end

private

def project_path_trie
@project_path_trie ||= Containers::Trie.new.tap do |trie|
projects.map { |project| trie[project.path] = project }
end
end

def projects
@projects ||= @env.plugins.each_with_object(Set.new) do |plugin, s|
s.merge(plugin.projects(@env))
end
end

def tsort_each_node
@projects.each { |p| yield p }
projects.each { |p| yield p }
end

def tsort_each_child(project)
project_path_trie.get(project.path).parents.each { |p| yield p }
end

def project_path_trie
@project_path_trie ||= Containers::Trie.new.tap do |trie|
@projects.map { |project| trie[project.path] = project }
end
end
end
end
15 changes: 15 additions & 0 deletions lib/shanty/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Shanty
# Small mixin that gives the receiver class a logger method. This method simply wraps the Ruby Logger class with a
# nice consistent logging format.
module Logger
module_function

def logger
@logger ||= ::Logger.new($stdout).tap do |logger|
logger.formatter = proc do |_, datetime, _, msg|
"#{datetime}: #{msg}\n"
end
end
end
end
end

0 comments on commit aa266ac

Please sign in to comment.