flo 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b84be555cacd1fd27392d83cd39e89c19e9dafd4
4
+ data.tar.gz: 9cd073f4e4c9ab0da1ea2b816b2cb1e90a579441
5
+ SHA512:
6
+ metadata.gz: 43b7d50133307857e6a656d5ef99b0c9b8c0e99030541b7c890086d3903e4ca6b4387ea624d060e17d02384b6d6b1e9c0dbad5c22d44809b4f324b920881fd43
7
+ data.tar.gz: e8ad9ceec8b76344fa641774919a41d38298f2c50d122668e64935175a0c4e61b657a95233746ee8c36cb8f491cd07919a5ec1fc7536f434981396a43664f31b
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg
5
+ tmp
6
+ .yardoc/
7
+ coverage
8
+ doc
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ source 'https://rubygems.org'
7
+
8
+ # Specify your gem's dependencies in flo.gemspec
9
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2017, Salesforce.com, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Flo
2
+ [![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/codeclimate/codeclimate) [![Build Status](https://semaphoreci.com/api/v1/justinpowers/flo/branches/master/shields_badge.svg)](https://semaphoreci.com/justinpowers/flo)
3
+
4
+
5
+ Flo is a local workflow automation tool that helps you get things done. This gem contains the core functionality for Flo, plugins for interacting with various systems can be found in separate provider gems.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```shell
12
+ gem 'flo'
13
+ ```
14
+ And then execute:
15
+
16
+ ```shell
17
+ $ bundle
18
+ ```
19
+ Or install it yourself as:
20
+
21
+ ```shell
22
+ $ gem install flo
23
+ ```
24
+
25
+ ## Documentation
26
+ https://www.rubydoc.info/github/salesforce/flo/
27
+
28
+ ## Usage
29
+
30
+ ### Command line usage
31
+
32
+ COMING SOON!
33
+ A command line parser has not yet been added. If you require a script that can be invoked from the command line, you can create a ruby script that creates and runs a `Flo::Runner` instance. See [Ruby script usage](#Ruby_script_usage) for more details.
34
+
35
+ ### Ruby script usage
36
+
37
+ Flo has been built with the intention of being easy to use within ruby scripts. `Flo::Runner` is responsible for parsing your custom configuration and invoking the commands. See the following example:
38
+
39
+ ```ruby
40
+ require 'flo'
41
+
42
+ runner = Flo::Runner.new
43
+
44
+ # Load your custom command configurations (see .flo file section for more details)
45
+ runner.load_config_file(File.join(__dir__,'.flo'))
46
+
47
+ # Run the something:useful command defined in the .flo file
48
+ response = runner.execute([:something, :useful], id: '123')
49
+ ```
50
+
51
+
52
+ ## .flo configuration file
53
+
54
+ Flo makes very few assumptions about what you want to use it for, or how you want to use it. In order for you to use it for anything useful, you will need to provide it some configuration. This is accomplished by loading one or more .flo configuration files. See [Ruby script usage](#Ruby_script_usage) for an example of how to load the file. The .flo file is evaluated in ruby in a cleanroom environment (see the [cleanroom gem](https://github.com/sethvargo/cleanroom) for more information), so you can require any gems or modules needed to accomplish the functionality you are looking for. There are two required sections:
55
+ * configuration
56
+ * command registration
57
+
58
+ ### Configuration
59
+
60
+ Before you can do anything useful in your .flo file, you have to declare providers. You can do so using a config block, for example:
61
+
62
+ ```ruby
63
+ config do |cfg|
64
+ cfg.provider :developer, {configuration_option: 'value' }
65
+ end
66
+ ```
67
+
68
+ ### Command registration
69
+
70
+ You can register any number of commands. Commands are namespaced to make it easier to group together similar commands. Within a command declaration you declare a set of tasks that will be executed in order when the command is invoked. Here is an overly simple example for starting a feature branch that only uses a single provider. In typical usage you would likely utilize multiple providers within a single command.
71
+
72
+ ```ruby
73
+ # Registers a command for starting a feature - feature:start. This command has
74
+ # one required argument: 'feature_name'. Note that in order for this to work,
75
+ # you will need to declare the git_flo provider in the config section.
76
+ register_command([:feature, :start]) do |feature_name: nil|
77
+
78
+ # During command execution, perform the :check_out_or_create_branch method on
79
+ # the git_flo provider, passing in the :from and :name arguments
80
+ perform :git_flo, :check_out_or_create_branch, { from: 'master', name: feature_name }
81
+ end
82
+ ```
83
+
84
+ ## Contributing
85
+
86
+ 1. Fork it (http://github.com/your-github-username/flo/fork )
87
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
89
+ 4. Push to the branch (`git push origin my-new-feature`)
90
+ 5. Create new Pull Request
91
+
92
+ ## License
93
+
94
+ >Copyright (c) 2017, Salesforce.com, Inc.
95
+ >All rights reserved.
96
+ >
97
+ >Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
98
+ >
99
+ >* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
100
+ >
101
+ >* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
102
+ >
103
+ >* Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
104
+ >
105
+ >THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require "bundler/gem_tasks"
7
+ require "rake/testtask"
8
+
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.pattern = "test/**/*_test.rb"
11
+ end
12
+
13
+ task :default => :test
data/flo.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ # Copyright © 2017, Salesforce.com, Inc.
3
+ # All Rights Reserved.
4
+ # Licensed under the BSD 3-Clause license.
5
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+
7
+ lib = File.expand_path('../lib', __FILE__)
8
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
9
+ require 'flo/version'
10
+
11
+ Gem::Specification.new do |spec|
12
+ spec.name = "flo"
13
+ spec.version = Flo::VERSION
14
+ spec.authors = ["Justin Powers"]
15
+ spec.email = ["justinspowers@gmail.com"]
16
+ spec.summary = %q{Simple developer workflow automation}
17
+ spec.description = %q{Flo is a local workflow automation tool that helps you get things done. This gem contains the core functionality for Flo, plugins for interacting with various systems can be found in separate provider gems.}
18
+ spec.homepage = "https://github.com/salesforce/flo"
19
+ spec.license = "BSD-3-Clause"
20
+
21
+ spec.files = `git ls-files -z`.split("\x0")
22
+ spec.test_files = spec.files.grep(%r{^test/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "cleanroom"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.5"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "minitest"
30
+ spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "simplecov"
32
+ end
@@ -0,0 +1,115 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require 'cleanroom'
7
+
8
+ module Flo
9
+
10
+ # Definition of a single command. In general you should not call {#initialize Command.new}
11
+ # directly, but instead define the command using {Runner#register_command}.
12
+ #
13
+ # When a command is generated using {Runner#register_command} (typically in a
14
+ # flo configuration file), only the DSL methods will be available
15
+ #
16
+ class Command
17
+
18
+ include Cleanroom
19
+
20
+ # Creates a new command instance
21
+ # @option opts [Hash] providers ({}) Providers that the command will need
22
+ # access to
23
+ #
24
+ # @yield [args*]The block containing the definition for the command.
25
+ # Arguments passed into {#call} are available within the block
26
+ #
27
+ def initialize(opts={}, &blk)
28
+ raise ArgumentError.new('.new must be called with a block defining the command') unless blk
29
+ @state_class = opts[:state_class] || Flo::State
30
+ @task_class = opts[:task_class] || Flo::Task
31
+ @providers = opts[:providers] || {}
32
+ @tasks = []
33
+ @definition_lambda = convert_block_to_lambda(blk)
34
+ end
35
+
36
+ # @api dsl
37
+ # DSL method: Define a task that will be performed during the execution stage when
38
+ # {#call} is invoked.
39
+ # @param provider_sym [Symbol] The provider to send the message to
40
+ # @param method_sym [Symbol] The method you wish to call on the provider
41
+ # @param provider_options [Hash] A hash of options to be passed when
42
+ # invoking the method on the provider. Any lambda values will be called
43
+ # during the execution stage when #{#call} is invoked
44
+ #
45
+ def perform(provider_sym, method_sym, provider_options={})
46
+ tasks << @task_class.new(providers[provider_sym], method_sym, provider_options)
47
+ end
48
+ expose :perform
49
+
50
+ alias :validate :perform
51
+ expose :validate
52
+
53
+ # @api dsl
54
+ # DSL method: Returns an object representing the current state of the provider during
55
+ # the execution stage when {#call} is invoked. Any methods called on the
56
+ # {State} instance will return a lambda. This is intended to be used in the
57
+ # parameters passed to the {#perform} method, as you often want these
58
+ # parameters lazily evaluated during the execution stage, not when the
59
+ # definition is parsed.
60
+ # @param provider_sym [Symbol] The provider that you wish to query
61
+ #
62
+ # @return [State] An object that will return a lambda, delegating the method
63
+ # call to the provider specified
64
+ #
65
+ def state(provider_sym)
66
+ state_class.new(providers[provider_sym])
67
+ end
68
+ expose :state
69
+
70
+ # Invoke the command that has already been defined.
71
+ #
72
+ # This will run the command, processing any tasks defined by {#perform} and
73
+ # {#validate} in order, stopping execution if any of the tasks fails.
74
+ # Arguments passed in here will be merged with the provider options defined
75
+ # in each task.
76
+ # @param args [Hash] arguments to be passed to each task
77
+ #
78
+ def call(args={})
79
+ evaluate_command_definition(args)
80
+ response = tasks.map do |task|
81
+
82
+ response = task.call(args)
83
+
84
+ # bail early if the task failed
85
+ return response unless response.success?
86
+ response
87
+ end.last
88
+ end
89
+
90
+ private
91
+
92
+ attr_reader :tasks, :definition_lambda, :providers, :state_class
93
+
94
+ def evaluate_command_definition(*args)
95
+ cleanroom.instance_exec(*args, &definition_lambda)
96
+ end
97
+
98
+ def convert_block_to_lambda(blk)
99
+ # jruby and rubinius can convert a proc directly into a lambda
100
+ if (converted_block = lambda(&blk)).lambda?
101
+ converted_block
102
+ else
103
+ # Otherwise, hacky method to take advantage of #define_method's automatic lambda conversion
104
+ cleanroom.define_singleton_method(:_command_definition, &blk)
105
+ cleanroom.method(:_command_definition).to_proc
106
+ end
107
+ end
108
+
109
+ def cleanroom
110
+ @cleanroom ||= self.class.send(:cleanroom).new(self)
111
+ end
112
+
113
+
114
+ end
115
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ module Flo
7
+ CommandNotDefinedError = Class.new(StandardError)
8
+ class CommandCollection
9
+
10
+ def initialize
11
+ @commands = {}
12
+ end
13
+
14
+ def [](*key)
15
+ raise CommandNotDefinedError.new("#{key} command is not defined") unless @commands.has_key?(key)
16
+ @commands[key]
17
+ end
18
+
19
+ def []=(*key, command)
20
+ @commands[key] = command
21
+ end
22
+
23
+ end
24
+ end
data/lib/flo/config.rb ADDED
@@ -0,0 +1,45 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ module Flo
7
+ MissingRequireError = Class.new(StandardError)
8
+
9
+ # Instantiates and stores providers for use in command definitions
10
+ #
11
+ # @attr_reader providers [Hash] Hash of provider instances
12
+ #
13
+ class Config
14
+ attr_reader :providers
15
+
16
+ def initialize
17
+ @providers = {}
18
+ end
19
+
20
+ # Instantiate a provider and add it to the {#providers} collection
21
+ # @param provider_sym [Symbol]
22
+ # @param options [Hash] Options to be passed to provider initialization
23
+ # @yield Yields the block to the provider initializer, in case the provider
24
+ # accepts a block
25
+ #
26
+ def provider(provider_sym, options={}, &blk)
27
+ @providers[provider_sym] = provider_class(provider_sym).new(options, &blk)
28
+ end
29
+
30
+ private
31
+
32
+ def provider_class(provider_sym)
33
+ klass = camel_case(provider_sym.to_s)
34
+ klass_name = "Flo::Provider::#{klass}"
35
+ Object.const_get(klass_name)
36
+ rescue NameError => e
37
+ raise MissingRequireError.new("#{klass_name} is not loaded. Please require the library before use")
38
+ end
39
+
40
+ def camel_case(str)
41
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
42
+ str.split('_').map{|e| e.capitalize}.join
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require 'ostruct'
7
+
8
+ module Flo
9
+ module Provider
10
+ Response = Struct.new(:success?)
11
+ class Developer
12
+
13
+ def initialize(options={})
14
+
15
+ end
16
+
17
+ def is_successful(opts={})
18
+ success = opts[:success].nil? ? true : opts[:success]
19
+ Flo::Provider::Response.new(success)
20
+ end
21
+
22
+ def return_true
23
+ true
24
+ end
25
+
26
+ def echo(opts={})
27
+ puts opts.inspect
28
+ Flo::Provider::Response.new(true)
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/flo/runner.rb ADDED
@@ -0,0 +1,94 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require 'flo'
7
+ require 'flo/command'
8
+ require 'flo/command_collection'
9
+ require 'flo/state'
10
+ require 'flo/config'
11
+ require 'flo/task'
12
+ require 'cleanroom'
13
+
14
+ module Flo
15
+
16
+ # This is the main class for instantiating and performing Flo commands. If
17
+ # you are wanting to interact with Flo via a ruby script, this is the class
18
+ # you want. Utilizing Flo through the command line interface will invoke this
19
+ # class after all of the argument parsing is complete.
20
+ #
21
+ # @example
22
+ # runner = Runner.new
23
+ # runner.load_config_file('path/to/config/file')
24
+ # runner.execute([:issue, :submit], id: '1234', submitter: 'John Doe')
25
+ #
26
+ # @attr_reader commands [CommandCollection] List of commands currently defined
27
+ #
28
+ class Runner
29
+ include Cleanroom
30
+
31
+ attr_reader :commands
32
+
33
+ # Creates a new runner. This object is generally useless until you load
34
+ # some configuration into it, typically using {#load_config_file}
35
+ #
36
+ def initialize(opts={})
37
+ @config = opts[:config] || Flo::Config.new
38
+ @command_class = opts[:command_class] || Flo::Command
39
+ @commands = opts[:command_collection] || Flo::CommandCollection.new
40
+ end
41
+
42
+ # Open and parse a file containing flo configuration. This file is
43
+ # evaluated within a cleanroom. See the {https://github.com/sethvargo/cleanroom cleanroom gem}
44
+ # for more information.
45
+ # @param config_file [String] path to the flo configuration file
46
+ #
47
+ def load_config_file(config_file)
48
+ evaluate_file(config_file)
49
+ end
50
+
51
+ # Executes the command specified, with the arguments specified
52
+ # @param command_namespace [Array<Symbol>] An array containing the name of
53
+ # the command as a symbol, including the namespace. For example, the
54
+ # command "issue submit" would become [:issue, :submit]
55
+ # @param args={} [Hash] Options that will get passed to the command
56
+ #
57
+ def execute(command_namespace, args={})
58
+ commands[command_namespace].call(args)
59
+ end
60
+
61
+ # @api dsl
62
+ # DSL method: Returns the instance of {Config} associated with this runner. Exposes the
63
+ # {Config} instance if a block is used
64
+ #
65
+ # @yield [Config]
66
+ #
67
+ # @return [Config]
68
+ #
69
+ def config
70
+ yield(@config) if block_given?
71
+ @config
72
+ end
73
+ expose :config
74
+
75
+ # @api dsl
76
+ # DSL method: Creates and defines a {Command}, adding it to the command collection.
77
+ # Definition for the command should happen inside of the required block.
78
+ # See {Command} for methods available within the block.
79
+ # @param command_namespace [Array<Symbol>] Array of symbols representing the
80
+ # command including the namespace
81
+ # @yield [args*]The block containing the definition for the command.
82
+ # Arguments passed into {#execute} are available within the block
83
+ #
84
+ def register_command(command_namespace, &blk)
85
+ commands[command_namespace] = command_class.new(providers: config.providers, &blk)
86
+ end
87
+ expose :register_command
88
+
89
+ private
90
+
91
+ attr_reader :command_class
92
+
93
+ end
94
+ end
data/lib/flo/state.rb ADDED
@@ -0,0 +1,23 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ module Flo
7
+ class State
8
+
9
+ def initialize(provider)
10
+ @provider = provider
11
+ end
12
+
13
+ def method_missing(meth_sym, *args)
14
+ lambda do
15
+ provider.method(meth_sym).call(*args)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :provider
22
+ end
23
+ end
data/lib/flo/task.rb ADDED
@@ -0,0 +1,53 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ module Flo
7
+
8
+ # Definition of a task performed by a {Command}.
9
+ #
10
+ class Task
11
+
12
+ # Creates a new Task instance
13
+ #
14
+ # @param [Provider] provider The provider to send the message to
15
+ # @param [Symbol] The method you wish to call on the provider
16
+ # @param [Hash] provider_options={} A hash of options to be passed when
17
+ # invoking the method on the provider. Any lambda values will be called
18
+ # during the execution stage when {#call} is invoked
19
+ #
20
+ def initialize(provider, method_sym, provider_options={})
21
+ @provider = provider
22
+ @method_sym = method_sym
23
+
24
+ raise ArgumentError.new("Expected provider_options to be a hash") unless provider_options.is_a? Hash
25
+ @provider_options = provider_options
26
+ end
27
+
28
+ # Call invokes the task on the provider instance. Additional parameters can be
29
+ # passed in that are merged into the parameters that were provided in {initialize}.
30
+ # Proc values will be evaluated before being passed to the provider.
31
+ #
32
+ # @param [Hash] args={} Additional arguments to pass to the provider method
33
+ # @return [#success?] Response of the provider's method
34
+ def call(args={})
35
+ @provider.public_send(method_sym, merged_evaluated_args(args))
36
+ end
37
+
38
+ private
39
+ attr_reader :provider, :method_sym, :provider_options
40
+
41
+ def merged_evaluated_args(args)
42
+ evaluate_proc_values(provider_options.merge args)
43
+ end
44
+
45
+ def evaluate_proc_values(args={})
46
+ hsh = {}
47
+ args.each do |k, v|
48
+ hsh[k] = v.is_a?(Proc) ? v.call : v
49
+ end
50
+ hsh
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ module Flo
7
+ VERSION = "0.0.2"
8
+ end
data/lib/flo.rb ADDED
@@ -0,0 +1,14 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require "flo/version"
7
+ require "flo/runner"
8
+
9
+ # Parent module for all Flo related functionality. Should not be used directly.
10
+ # See {file:README.md} or {Runner} for information on overall usage.
11
+ #
12
+ module Flo
13
+
14
+ end
@@ -0,0 +1,14 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require 'flo/provider/developer'
7
+
8
+ config do |cfg|
9
+ cfg.provider :developer
10
+ end
11
+
12
+ register_command([:task, :start]) do |success: true|
13
+ perform :developer, :is_successful, { success: state(:developer).return_true }
14
+ end
@@ -0,0 +1,8 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ config do |cfg|
7
+ cfg.provider :developer
8
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/command_collection'
8
+
9
+ module Flo
10
+ class CommandCollectionTest < Flo::UnitTest
11
+
12
+ def subject
13
+ @subject ||= Flo::CommandCollection.new
14
+ end
15
+
16
+ def test_a_single_symbol_can_be_used_as_a_storage_key
17
+ command = Object.new
18
+ subject[:command_name] = command
19
+ assert_same command, subject[:command_name]
20
+ end
21
+
22
+ def test_an_array_of_symbols_can_be_used_as_a_storage_key
23
+ command = Object.new
24
+ subject[:name, :spaced, :command] = command
25
+ assert_same command, subject[:name, :spaced, :command]
26
+ end
27
+
28
+ def test_properly_selects_correct_command
29
+ command1 = Object.new
30
+ command2 = Object.new
31
+ subject[:name, :spaced, :command1] = command1
32
+ subject[:name, :spaced, :command2] = command2
33
+
34
+ assert_same command1, subject[:name, :spaced, :command1]
35
+ assert_same command2, subject[:name, :spaced, :command2]
36
+ end
37
+
38
+ def test_selecting_a_command_that_doesnt_exist_raises_error
39
+ assert_raises(Flo::CommandNotDefinedError) do
40
+ subject[:undefined_command]
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,95 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/command'
8
+ require 'ostruct'
9
+
10
+ module Flo
11
+ class CommandTest < Flo::UnitTest
12
+
13
+ def subject
14
+ @subject_block ||= lambda { }
15
+ @providers ||= { mocked_provider: mock }
16
+ @state_class_mock ||= Object
17
+ @subject ||= Flo::Command.new(
18
+ providers: @providers,
19
+ state_class: @state_class_mock,
20
+ task_class: MockTask,
21
+ &@subject_block
22
+ )
23
+ end
24
+
25
+ def mock
26
+ @mock ||= MockProvider.new
27
+ end
28
+
29
+ def performer_class_mock
30
+ @performer_class_mock ||= begin
31
+ mock = Minitest::Mock.new
32
+ mock.expect(:new, performer_obj, [Symbol, Symbol, {}, Hash])
33
+ end
34
+ end
35
+
36
+ def performer_obj
37
+ @performer_obj ||= lambda { }
38
+ end
39
+
40
+ def test_new_raises_if_no_block_provided
41
+ assert_raises(ArgumentError) do
42
+ Flo::Command.new(:foo)
43
+ end
44
+ end
45
+
46
+ def test_task_called_with_args
47
+ @subject_block = lambda do |*args|
48
+ perform :mocked_provider, :provider_method, baz: 2
49
+ end
50
+
51
+ result = subject.call(bar: 1)
52
+
53
+ assert_equal([{ bar: 1 }], result.args_passed)
54
+ assert_equal({ baz: 2 }, result.args_initialized.last)
55
+ end
56
+
57
+ def test_call_stops_and_returns_failure_early_if_task_is_not_successful
58
+ @subject_block = lambda do |*args|
59
+ perform :mocked_provider, :failed_provider_method, {success: false}
60
+ perform :mocked_provider, :provider_method, {fail: true}
61
+ end
62
+
63
+ refute subject.call.success?
64
+ end
65
+
66
+ def test_state_is_not_invoked_during_configuration_phase
67
+ @state_class_mock = Minitest::Mock.new
68
+ state_mock = Minitest::Mock.new
69
+ @state_class_mock.expect(:new, state_mock, [mock])
70
+
71
+ state_mock.expect(:state_method, lambda { raise "This should never be evaluated" }, [] )
72
+ subject.perform :mocked_provider, :provider_method, {evaluated_later: subject.state(:mocked_provider).state_method }
73
+ end
74
+
75
+ class MockProvider
76
+ attr_reader :args, :state_method_called
77
+
78
+ def state_method
79
+ @state_method_called = true
80
+ end
81
+ end
82
+
83
+ class MockTask
84
+ def initialize(*args)
85
+ @initial_args = args
86
+ end
87
+
88
+ def call(*args)
89
+ fail("This task should never have been called") if @initial_args.last.fetch(:fail, false)
90
+ success = @initial_args.last.fetch(:success, true)
91
+ OpenStruct.new(success?: success, args_passed: args, args_initialized: @initial_args)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/config'
8
+
9
+ module Flo
10
+ class ConfigTest < Flo::UnitTest
11
+
12
+ def subject
13
+ @subject ||= Flo::Config.new
14
+ end
15
+
16
+ def test_provider_instantiates_new_provider
17
+ assert_equal({}, subject.providers)
18
+ subject.provider(:mock_provider)
19
+ assert_kind_of Flo::Provider::MockProvider, subject.providers[:mock_provider]
20
+ end
21
+
22
+ def test_raises_helpful_error_if_provider_not_required
23
+ assert_raises(Flo::MissingRequireError) do
24
+ subject.provider :doesnt_exist
25
+ end
26
+ end
27
+
28
+ end
29
+ module Provider
30
+ class MockProvider
31
+ def initialize(args={})
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo'
8
+ require 'flo/provider/developer'
9
+
10
+ class FloTest < Flo::UnitTest
11
+
12
+ def subject
13
+ @subject = Flo
14
+ end
15
+
16
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/runner'
8
+ require 'flo/config'
9
+
10
+ module Flo
11
+ class RunnerIntegrationTest < Flo::UnitTest
12
+
13
+ def subject
14
+ @subject ||= begin
15
+ subj = Flo::Runner.new
16
+ subj.load_config_file(File.join(FIXTURES_ROOT, 'basic_setup.rb'))
17
+ subj
18
+ end
19
+ end
20
+
21
+ def test_execute_returns_success
22
+ response = subject.execute([:task, :start])
23
+
24
+ assert_equal true, response.success?
25
+ end
26
+
27
+ def test_execute_success_is_false_when_perform_fails
28
+ response = subject.execute([:task, :start], success: false)
29
+
30
+ assert_equal false, response.success?
31
+ end
32
+
33
+ end
34
+ end
35
+
@@ -0,0 +1,100 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/runner'
8
+
9
+ module Flo
10
+ class RunnerTest < Flo::UnitTest
11
+
12
+ def subject
13
+ @subject ||= Flo::Runner.new(
14
+ config: config_mock,
15
+ command_class: command_class_mock,
16
+ command_collection: command_collection_mock
17
+ )
18
+ end
19
+
20
+ def config_mock
21
+ @config_mock ||= Minitest::Mock.new
22
+ end
23
+
24
+ def command_class_mock
25
+ @command_class_mock ||= Minitest::Mock.new
26
+ end
27
+
28
+ def command_collection_mock
29
+ @command_collection_mock ||= {}
30
+ end
31
+
32
+ def config_file_fixture
33
+ @config_file_fixture ||= File.expand_path(File.join(FIXTURES_ROOT, 'basic_setup.rb'))
34
+ end
35
+
36
+ def test_config_returns_config_instance
37
+ # Minitest::Mock undefines #equal?, which causes #assert_same to raise
38
+ assert_equal config_mock.object_id, subject.config.object_id
39
+ end
40
+
41
+ def test_passing_block_to_config_yields_config_to_block
42
+ config_mock.expect(:foo, true)
43
+
44
+ subject.config do |cfg|
45
+ cfg.foo
46
+ end
47
+
48
+ config_mock.verify
49
+ end
50
+
51
+ def test_register_command_adds_new_command_to_collection
52
+ new_command = Object.new
53
+ command_class_mock.expect(:new, new_command, [{ providers: {} }])
54
+
55
+ config_mock.expect(:providers, {})
56
+
57
+ subject.register_command(:foo) { }
58
+
59
+ assert_equal new_command, subject.commands[:foo]
60
+
61
+ command_class_mock.verify
62
+ end
63
+
64
+ def test_register_command_namespaced_adds_new_command_to_collection
65
+ config_mock.expect(:providers, {})
66
+ new_command = Object.new
67
+ command_class_mock.expect(:new, new_command, [{ providers: {} }])
68
+
69
+ @command_collection_mock = {}
70
+
71
+ subject.register_command([:foo, :bar]) { }
72
+
73
+ assert_equal(new_command, subject.commands[[:foo, :bar]])
74
+
75
+ command_class_mock.verify
76
+ end
77
+
78
+ def test_execute_calls_command_with_args
79
+ args = {foo: :bar}
80
+ providers_hash = Object.new
81
+ new_command = lambda { |args| true }
82
+
83
+ command_class_mock.expect(:new, new_command, [{ providers: providers_hash }])
84
+ config_mock.expect(:providers, providers_hash)
85
+
86
+ subject.register_command(:foo)
87
+
88
+ subject.execute(:foo, args)
89
+ end
90
+
91
+ def test_load_config_file_evals_file
92
+ config_mock.expect(:provider, true, [:developer])
93
+ subject.load_config_file(File.join(FIXTURES_ROOT, 'one_config_call.rb'))
94
+
95
+ config_mock.verify
96
+ end
97
+
98
+
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/state'
8
+
9
+ module Flo
10
+ class StateTest < Flo::UnitTest
11
+
12
+ def subject
13
+ @mock_provider ||= MockProvider.new
14
+ @subject ||= Flo::State.new(@mock_provider)
15
+ end
16
+
17
+ def test_meth_missing_returns_lambda
18
+ assert_kind_of Proc, subject.existing_method
19
+ end
20
+
21
+ def test_arguments_are_passed_into_returned_lambda
22
+ subject.existing_method(:method_arguments).call
23
+
24
+ assert_equal([:method_arguments], @mock_provider.args)
25
+ end
26
+
27
+ def test_provider_method_is_called_when_lambda_is_invoked
28
+ subject.existing_method.call
29
+
30
+ assert @mock_provider.existing_method_called
31
+ end
32
+
33
+ class MockProvider
34
+ attr_reader :args, :existing_method_called
35
+ def existing_method(*args)
36
+ @existing_method_called = true
37
+ @args = args
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require_relative '../minitest_helper'
7
+ require 'flo/task'
8
+ require 'ostruct'
9
+
10
+ module Flo
11
+ class TaskTest < Flo::UnitTest
12
+ def subject
13
+ @method_sym ||= :foo
14
+ @provider_options ||= {}
15
+ @subject ||= Flo::Task.new(
16
+ provider,
17
+ @method_sym,
18
+ @provider_options
19
+ )
20
+ end
21
+
22
+ def provider
23
+ @provider ||= Minitest::Mock.new
24
+ end
25
+
26
+ def test_calls_method_on_provider
27
+ provider.expect(:foo, true, [{}])
28
+ subject.call()
29
+ provider.verify
30
+ end
31
+
32
+ def test_passes_on_args
33
+ provider.expect(:foo, true, [{ bar: 1 }])
34
+ subject.call(bar: 1)
35
+ provider.verify
36
+ end
37
+
38
+ def test_merges_args_with_provider_options
39
+ @provider_options = { baz: 2 }
40
+ provider.expect(:foo, true, [{ bar: 1, baz: 2 }])
41
+ subject.call(bar: 1)
42
+ provider.verify
43
+ end
44
+
45
+ def test_should_raise_if_provider_options_is_not_a_hash
46
+ @provider_options = :bar
47
+ assert_raises(ArgumentError) { subject.call(baz: 1) }
48
+ end
49
+
50
+ def test_called_args_override_provider_options
51
+ @provider_options = { bar: 2 }
52
+ provider.expect(:foo, true, [{ bar: 1 }])
53
+ subject.call(bar: 1)
54
+ provider.verify
55
+ end
56
+
57
+ def test_procs_in_options_are_evaluated_before_provider_method_is_called
58
+ @provider_options = { baz: Proc.new { 2 }}
59
+ provider.expect(:foo, true, [{ bar: 1, baz: 2 }])
60
+ subject.call(bar: Proc.new { 1 })
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright © 2017, Salesforce.com, Inc.
2
+ # All Rights Reserved.
3
+ # Licensed under the BSD 3-Clause license.
4
+ # For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
5
+
6
+ require 'simplecov'
7
+ SimpleCov.start
8
+
9
+ SimpleCov.configure do
10
+ add_filter "/test/"
11
+ end
12
+
13
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
14
+ TEST_ROOT = File.dirname(__FILE__)
15
+ FIXTURES_ROOT = File.expand_path('../fixtures', __FILE__)
16
+ ENV["MT_NO_SKIP_MSG"] = "true"
17
+
18
+ require 'minitest/autorun'
19
+ require 'pry'
20
+
21
+ module Flo
22
+ class UnitTest < Minitest::Test
23
+ self.parallelize_me!
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Justin Powers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cleanroom
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Flo is a local workflow automation tool that helps you get things done. This
98
+ gem contains the core functionality for Flo, plugins for interacting with various
99
+ systems can be found in separate provider gems.
100
+ email:
101
+ - justinspowers@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - flo.gemspec
112
+ - lib/flo.rb
113
+ - lib/flo/command.rb
114
+ - lib/flo/command_collection.rb
115
+ - lib/flo/config.rb
116
+ - lib/flo/provider/developer.rb
117
+ - lib/flo/runner.rb
118
+ - lib/flo/state.rb
119
+ - lib/flo/task.rb
120
+ - lib/flo/version.rb
121
+ - test/fixtures/basic_setup.rb
122
+ - test/fixtures/one_config_call.rb
123
+ - test/flo/command_collection_test.rb
124
+ - test/flo/command_test.rb
125
+ - test/flo/config_test.rb
126
+ - test/flo/flo_test.rb
127
+ - test/flo/runner_integration_test.rb
128
+ - test/flo/runner_test.rb
129
+ - test/flo/state_test.rb
130
+ - test/flo/task_test.rb
131
+ - test/minitest_helper.rb
132
+ homepage: https://github.com/salesforce/flo
133
+ licenses:
134
+ - BSD-3-Clause
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.5.2
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Simple developer workflow automation
156
+ test_files:
157
+ - test/fixtures/basic_setup.rb
158
+ - test/fixtures/one_config_call.rb
159
+ - test/flo/command_collection_test.rb
160
+ - test/flo/command_test.rb
161
+ - test/flo/config_test.rb
162
+ - test/flo/flo_test.rb
163
+ - test/flo/runner_integration_test.rb
164
+ - test/flo/runner_test.rb
165
+ - test/flo/state_test.rb
166
+ - test/flo/task_test.rb
167
+ - test/minitest_helper.rb