democritus 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baa238520da0320c4860a88897b7fd30a8310ea6
4
- data.tar.gz: 8aa32bdaee252ebb40aeb4823f14c818ebe4fa6f
3
+ metadata.gz: 8db17897f669a971aa5b22ba819978953a3eda10
4
+ data.tar.gz: d3f9514bb722c2dd6eb51a4beb06d0e48e606524
5
5
  SHA512:
6
- metadata.gz: 88e30d77f873c285a4d9c227cccc3b24aa7802cac41cf6bdd4b6682b01031fb0e5ddc98d7cecc8ea1f9bc79368c436b0e7d2cc87d4041f3c984ee9cf73e5712c
7
- data.tar.gz: d7810ebe84b3bcf7ae235c92e19a66527185b2fca28c9946144629aac3142b6a074050e4b3599c1d656df7bf511c645a28595308b48c508764cbecddee95d4aa
6
+ metadata.gz: 22a3ec1e45a1e3dce5cb8c36751723c88a37002d6a654e7ac7899a0dd926a5f3657f422592e5e94795df8cf1235e1bc6023a277dcef638445dbd99cbfeb1c3c3
7
+ data.tar.gz: d34e1f0f3288fa495664c7c63710185ee1a9eb7ae2a61591a3e50b85c8a2b561e67ac5f5184cefcf3bca1bb44a7508dbc3de949fec934943d1159c8be8b4810a
data/.hound.yml ADDED
@@ -0,0 +1,107 @@
1
+ require: rubocop-rspec
2
+ ################################################################################
3
+ ## Releasing the hounds in your local environment.
4
+ ##
5
+ ## Setup:
6
+ ## $ gem install rubocop
7
+ ##
8
+ ## Run:
9
+ ## $ rubocop ./path/to/file ./or/path/to/directory -c ./.hound.yml
10
+ ##
11
+ ################################################################################
12
+ AllCops:
13
+ Include:
14
+ - Rakefile
15
+ Exclude:
16
+ - app/data_generators/sipity/data_generators/**/*
17
+ - db/**/*
18
+ - bin/**/*
19
+ - config/**/*
20
+ - dragonfly/**/*
21
+ - 'spec/fixtures/**/*'
22
+ - 'vendor/**/*'
23
+ - 'scripts/**/*'
24
+ - 'tmp/**/*'
25
+ - 'spec/support/sipity/command_repository_interface.rb'
26
+ - 'spec/support/sipity/query_repository_interface.rb'
27
+ - 'app/validators/open_for_starting_submissions_validator.rb'
28
+ - 'app/forms/sipity/forms/form_builder.rb'
29
+ RunRailsCops: false
30
+
31
+ LineLength:
32
+ Description: 'Limit lines to 140 characters.'
33
+ Max: 140
34
+ Enabled: true
35
+
36
+ AlignParameters:
37
+ Description: >-
38
+ Align the parameters of a method call if they span more
39
+ than one line.
40
+ Enabled: true
41
+
42
+ CyclomaticComplexity:
43
+ Description: 'Avoid complex methods.'
44
+ Enabled: true
45
+ Exclude:
46
+
47
+ Documentation:
48
+ Description: 'Document classes and non-namespace modules.'
49
+ Enabled: true
50
+ Exclude:
51
+ - spec/**/*
52
+ - lib/**/version.rb
53
+
54
+ Metrics/PerceivedComplexity:
55
+ Enabled: true
56
+ Exclude:
57
+
58
+ Metrics/AbcSize:
59
+ Enabled: true
60
+ Max: 12
61
+ Exclude:
62
+
63
+ Delegate:
64
+ Description: 'Prefer delegate method for delegations.'
65
+ Enabled: false
66
+
67
+ EmptyLinesAroundBlockBody:
68
+ Enabled: false
69
+
70
+ DotPosition:
71
+ Description: 'Checks the position of the dot in multi-line method calls.'
72
+ EnforcedStyle: trailing
73
+ Enabled: true
74
+
75
+ Style/Encoding:
76
+ Description: 'Use UTF-8 as the source file encoding.'
77
+ Enabled: false
78
+
79
+ FileName:
80
+ Description: 'Use snake_case for source file names.'
81
+ Enabled: true
82
+
83
+ PercentLiteralDelimiters:
84
+ Description: 'Use `%`-literal delimiters consistently'
85
+ PreferredDelimiters:
86
+ '%': ()
87
+ '%i': ()
88
+ '%q': ()
89
+ '%Q': ()
90
+ '%r': '{}'
91
+ '%s': ()
92
+ '%w': ()
93
+ '%W': ()
94
+ '%x': ()
95
+ Enabled: true
96
+
97
+ RedundantReturn:
98
+ Description: "Don't use return where it's not required."
99
+ Enabled: false
100
+
101
+ StringLiterals:
102
+ Description: 'Checks if uses of quotes match the configured preference.'
103
+ Enabled: false
104
+
105
+ WordArray:
106
+ Description: 'Use %w or %W for arrays of words.'
107
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,3 +1,10 @@
1
1
  language: ruby
2
+ cache: bundler
3
+ sudo: false
2
4
  rvm:
5
+ - 2.1.0
3
6
  - 2.2.2
7
+ - 2.3.0
8
+ addons:
9
+ code_climate:
10
+ repo_token: 2b384ee17abc44f9436aadcb0d62e897747b4e3ad3804afbae4d3b74d8b1985f
data/README.md CHANGED
@@ -1,3 +1,61 @@
1
1
  # Democritus
2
2
 
3
- A placeholder for a great gem.
3
+ [![Build Status](https://travis-ci.org/jeremyf/democritus.png?branch=master)](https://travis-ci.org/jeremyf/democritus)
4
+ [![APACHE 2 License](http://img.shields.io/badge/APACHE2-license-blue.svg)](./LICENSE)
5
+ [![Code Climate](https://codeclimate.com/github/jeremyf/democritus.png)](https://codeclimate.com/github/jeremyf/democritus)
6
+ [![Test Coverage](https://codeclimate.com/github/jeremyf/democritus/badges/coverage.svg)](https://codeclimate.com/github/jeremyf/democritus)
7
+ [![Documentation Status](http://inch-ci.org/github/jeremyf/democritus.svg?branch=master)](http://inch-ci.org/github/jeremyf/democritus)
8
+
9
+ Democritus is a plugin for building class from reusable components.
10
+
11
+ Democritus is inspired as followup of a common pattern that I saw in the development of [Sipity's](https://github.com/ndlib/sipity/) form objects.
12
+ It also aims to address the needs of Sipity's yet to be developed sibling application; The dissemination of processed data.
13
+
14
+ I'm looking to apply the ideas put forward in Avdi Grimm's [Naught gem](https://github.com/avdi/naught).
15
+
16
+ ## Goal
17
+
18
+ I would like to be able to declare in Ruby the following:
19
+
20
+ ```ruby
21
+ ApprovalForm = Democritus.build(command_namespaces: ['Sipity::DemocritusCommands', 'Democritus::ClassBuilder::Commands']) do |builder|
22
+ builder.form do
23
+ attributes do
24
+ attribute(name: 'agree_to_terms_of_service', type: 'Boolean', validates: 'acceptance')
25
+ end
26
+ action_name(name: 'approval')
27
+ end
28
+ end
29
+ ```
30
+
31
+ With an `ApprovalForm`, I could `#submit` if `#valid?` (i.e. the `agree_to_terms_of_service` has been accepted).
32
+
33
+ From that point forward, I would like to be able to create the class based on a JSON description:
34
+
35
+ ```json
36
+ {
37
+ "#command_namespaces": ["Sipity::DemocritusCommands", "Democritus::ClassBuilder::Commands"],
38
+ "#form": {
39
+ "#attributes": {
40
+ "#attribute": [
41
+ { "name": "agree_to_terms_of_service", "type": "Boolean", "validates": "acceptance" }
42
+ ]
43
+ },
44
+ "#action_name": { "name": "approval" }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ```ruby
50
+ ApprovalForm = Demcritus.build_from_json(json)
51
+ ```
52
+
53
+ ## Roadmap
54
+
55
+ - [x] Rudimentary plugin command behavior
56
+ - [x] [Command::Attribute](./lib/democritus/class_builder/commands/attribute.rb)
57
+ - [x] [Command::Attirubtes](./lib/democritus/class_builder/commands/attributes.rb)
58
+ - [ ] Create the commands that build a Sipity processing form
59
+ - [X] Build class from JSON configuration
60
+ - [ ] Basic case for nested commands
61
+ - [ ] Allow for "constantization" of command_namespaces option.
data/Rakefile CHANGED
@@ -1 +1,32 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ unless Rake::Task.task_defined?('spec')
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.pattern = "./spec/**/*_spec.rb"
8
+ ENV['COVERAGE'] = 'true'
9
+ end
10
+ rescue LoadError
11
+ $stdout.puts "RSpec failed to load; You won't be able to run tests."
12
+ end
13
+ end
14
+
15
+ require 'rubocop/rake_task'
16
+ RuboCop::RakeTask.new do |task|
17
+ task.requires << 'rubocop-rspec'
18
+ task.options << "--config=.hound.yml"
19
+ end
20
+
21
+ require 'reek/rake/task'
22
+ Reek::Rake::Task.new do |task|
23
+ task.verbose = true
24
+ end
25
+
26
+ require 'flay_task'
27
+ FlayTask.new do |task|
28
+ task.verbose = true
29
+ task.threshold = 20
30
+ end
31
+
32
+ task(default: ['rubocop', 'reek', 'flay', 'spec'])
data/democritus.gemspec CHANGED
@@ -17,9 +17,17 @@ Gem::Specification.new do |spec|
17
17
  spec.bindir = "exe"
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
+ spec.required_ruby_version = '~> 2.1'
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.9"
22
+ spec.add_development_dependency "bundler", "~> 1.6"
22
23
  spec.add_development_dependency "rake", "~> 10.0"
23
24
  spec.add_development_dependency "rspec", "~> 3.2"
24
25
  spec.add_development_dependency "rspec-its", "~> 1.2"
26
+ spec.add_development_dependency "reek"
27
+ spec.add_development_dependency "flay"
28
+ spec.add_development_dependency "rubocop"
29
+ spec.add_development_dependency "rubocop-rspec"
30
+ spec.add_development_dependency "simplecov"
31
+ spec.add_development_dependency "codeclimate-test-reporter"
32
+ spec.add_development_dependency "byebug"
25
33
  end
data/lib/democritus.rb CHANGED
@@ -1,16 +1,34 @@
1
1
  require "democritus/version"
2
2
  require 'democritus/class_builder'
3
+ require 'democritus/class_builder/commands'
4
+ require 'democritus/from_json_class_builder'
3
5
 
6
+ # Compose objects by leveraging a DSL for class creation.
7
+ # Yes, we can write code that conforms to interfaces, but in my experience, as the Ruby object ecosystem has grown, so too has the needs
8
+ # for understanding the galaxy of objects.
4
9
  module Democritus
5
10
  # @api public
6
11
  #
7
12
  # Responsible for building a class based on atomic components.
13
+ #
14
+ # @yield [Democritus::ClassBuilder] Gives a builder to provide additional command style customizations
15
+ # @return Class
8
16
  def self.build(&configuration_block)
9
17
  builder = ClassBuilder.new
10
18
  builder.customize(&configuration_block)
11
19
  builder.generate_class
12
20
  end
13
21
 
22
+ # @api public
23
+ #
24
+ # Responsible for building a class based on the given JSON object.
25
+ #
26
+ # @return Class
27
+ def self.build_from_json(json)
28
+ builder = FromJsonClassBuilder.new(json)
29
+ builder.generate_class
30
+ end
31
+
14
32
  # An empty module intended to be exposed for is_a? comparisons (and ==)
15
33
  #
16
34
  # @example
@@ -1,16 +1,167 @@
1
1
  module Democritus
2
+ # Responsible for building a class based on the customization's applied
3
+ # through the #customize method.
4
+ #
5
+ # @see ./spec/lib/democritus/class_builder_spec.rb
6
+ #
7
+ # :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /(method_missing|respond_to_missing)/ ] }
2
8
  class ClassBuilder
3
- # @api public
4
- def generate_class
5
- Class.new
9
+ # @param command_namespaces [Array<Module>] the sequential list of namespaces you want to check for each registered command.
10
+ def initialize(command_namespaces: default_command_namespaces)
11
+ self.customization_module = Module.new
12
+ self.generation_module = Module.new
13
+ self.class_operations = []
14
+ self.instance_operations = []
15
+ self.command_namespaces = command_namespaces
16
+ end
17
+
18
+ private
19
+
20
+ # The module that receives customized method definitions.
21
+ #
22
+ # @example
23
+ # Democritus.build do |builder|
24
+ # builder.a_command
25
+ # def a_customization
26
+ # end
27
+ # end
28
+ #
29
+ # The above #a_customization method is captured in the customization_module and applied as an instance method
30
+ # to the generated class.
31
+ attr_accessor :customization_module
32
+ attr_accessor :generation_module
33
+
34
+ # Command operations to be applied as class methods of the generated_class.
35
+ attr_accessor :class_operations
36
+
37
+ def default_command_namespaces
38
+ [Democritus::ClassBuilder::Commands]
39
+ end
40
+
41
+ # The command namespaces that you want to use. Note, order is important
42
+ attr_reader :command_namespaces
43
+
44
+ def command_namespaces=(input)
45
+ @command_namespaces = Array(input)
6
46
  end
7
47
 
48
+ # Command operations to be applied as instance methods of the generated_class.
49
+ attr_accessor :instance_operations
50
+
51
+ public
52
+
8
53
  # @api public
9
54
  #
10
- # Responsible for capturing the customization block and applying executing
11
- # it for configuration of the given class.
55
+ # Responsible for executing the customization block against the
56
+ # customization module with the builder class as a parameter.
57
+ #
58
+ # @yield [Democritus::ClassBuilder] the means to build your custom class.
59
+ #
60
+ # @example
61
+ # ClassBuilder.new.customize do |builder|
62
+ # builder.command('paramter')
63
+ # def to_s; 'parameter'; end
64
+ # end
65
+ #
66
+ # @return nil
67
+ # @see ./spec/lib/democritus/class_builder_spec.rb
12
68
  def customize(&customization_block)
13
69
  return unless customization_block
70
+ customization_module.module_exec(self, &customization_block)
71
+ return nil
72
+ end
73
+
74
+ # @api public
75
+ #
76
+ # Responsible for generating a Class object based on the customizations
77
+ # applied via a customize block.
78
+ #
79
+ # @example
80
+ # dynamic_class = Democritus::ClassBuilder.new.generate_class
81
+ # an_instance_of_the_dynamic_class = dynamic_class.new
82
+ #
83
+ # @return Class object
84
+ #
85
+ # rubocop:disable MethodLength
86
+ # :reek:TooManyStatements: { exclude: [ 'Democritus::ClassBuilder#generate_class' ] }
87
+ def generate_class
88
+ generation_mod = generation_module # get a local binding
89
+ customization_mod = customization_module # get a local binding
90
+ apply_operations(instance_operations, generation_mod)
91
+ generated_class = Class.new do
92
+ const_set :GeneratedMethods, generation_mod
93
+ const_set :Customizations, customization_mod
94
+ include DemocritusObjectTag
95
+ include generation_mod
96
+ include customization_mod
97
+ end
98
+ generated_class
99
+ end
100
+ # rubocop:enable MethodLength
101
+
102
+ def defer(options = {}, &deferred_operation)
103
+ if options[:prepend]
104
+ instance_operations.unshift(deferred_operation)
105
+ else
106
+ instance_operations << deferred_operation
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ # :reek:UtilityFunction: { exclude: [ 'Democritus::ClassBuilder#apply_operations' ] }
113
+ def apply_operations(operations, module_or_class)
114
+ operations.each do |operation|
115
+ operation.call(module_or_class)
116
+ end
117
+ end
118
+
119
+ # @!group Method Missing
120
+
121
+ private
122
+
123
+ # @api public
124
+ def method_missing(method_name, *args, **kargs, &block)
125
+ command_name = self.class.command_name_for_method(method_name)
126
+ command_namespace = command_namespace_for(command_name)
127
+ if command_namespace
128
+ command_class = command_namespace.const_get(command_name)
129
+ command_class.new(*args, **kargs.merge(builder: self), &block).call
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ # @api public
136
+ def respond_to_missing?(method_name, *args)
137
+ respond_to_definition(method_name, :respond_to_missing?, *args)
138
+ end
139
+
140
+ # @api public
141
+ def respond_to_definition(method_name, *)
142
+ command_name = self.class.command_name_for_method(method_name)
143
+ command_namespace_for(command_name)
144
+ end
145
+
146
+ # @api private
147
+ def command_namespace_for(command_name)
148
+ command_namespaces.detect { |cs| cs.const_defined?(command_name) }
149
+ end
150
+ # @!endgroup
151
+
152
+ class << self
153
+ # @api public
154
+ #
155
+ # Convert the given :method_name into a "constantized" method name.
156
+ #
157
+ # @example
158
+ # Democritus::ClassBuilder.command_name_for_method(:test_command) == 'TestCommand'
159
+ #
160
+ # @param method_name [#to_s]
161
+ # @return String
162
+ def command_name_for_method(method_name)
163
+ method_name.to_s.gsub(/(?:^|_)([a-z])/) { Regexp.last_match[1].upcase }
164
+ end
14
165
  end
15
166
  end
16
167
  end
@@ -0,0 +1,36 @@
1
+ module Democritus
2
+ class ClassBuilder
3
+ # @api public
4
+ #
5
+ # An abstract class useful in composing additional Democritus::Commands
6
+ #
7
+ # The expected interface for a Democritus::Command is as follows:
8
+ #
9
+ # * Its #initialize method must accept a :builder keyword (i.e. `#initialize`)
10
+ # * It responds to #call and #call does not accept any parameters
11
+ class Command
12
+ # @api public
13
+ #
14
+ # @param builder [Democritus::ClassBuilder] The context in which we are leveraging this building command.
15
+ def initialize(*, builder:)
16
+ self.builder = builder
17
+ end
18
+
19
+ # @api public
20
+ #
21
+ # @abstract Subclass and override #call to implement
22
+ def call
23
+ fail(NotImplementedError, 'Method #call should be overriden in child classes')
24
+ end
25
+
26
+ # @api private
27
+ def defer(options = {}, &block)
28
+ builder.defer(options, &block)
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :builder
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ module Democritus
2
+ # A container namespace for Democritus commands that are leveraged for `Democritus.build`
3
+ #
4
+ # @see Democritus::Command for interface of each command.
5
+ module Commands
6
+ end
7
+ end
8
+
9
+ require 'democritus/class_builder/commands/attribute'
10
+ require 'democritus/class_builder/commands/attributes'
@@ -0,0 +1,33 @@
1
+ require 'democritus/class_builder/command'
2
+
3
+ module Democritus
4
+ class ClassBuilder
5
+ module Commands
6
+ # Command to assign an attribute to the given built class.
7
+ class Attribute < ::Democritus::ClassBuilder::Command
8
+ def initialize(name:, **options)
9
+ self.builder = options.fetch(:builder)
10
+ self.name = name
11
+ self.options = options
12
+ end
13
+
14
+ attr_reader :name, :options
15
+
16
+ # :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attribute#call' ] }
17
+ def call
18
+ defer do |subject|
19
+ subject.module_exec(@name) do |name|
20
+ attr_reader name
21
+ private
22
+ attr_writer name
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_writer :name, :options
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ require 'democritus/class_builder/command'
2
+
3
+ module Democritus
4
+ class ClassBuilder
5
+ module Commands
6
+ # Command to assign attributes as part of the initialize method.
7
+ #
8
+ # @example
9
+ # Democritus::ClassBuilder::Commands::Attributes.new(builder: a_builder) do
10
+ # attribute(:name)
11
+ # attribute(:coolness_factor)
12
+ # end
13
+ class Attributes < ::Democritus::ClassBuilder::Command
14
+ # @param builder [Democritus::ClassBuilder]
15
+ # @param additional_configuration [Proc] A means to nest additional configuration
16
+ def initialize(builder:, &additional_configuration)
17
+ self.builder = builder
18
+ self.additional_configuration = additional_configuration
19
+ self.attribute_names = []
20
+ end
21
+
22
+ # :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#call' ] }
23
+ # :reek:TooManyStatements: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#call' ] }
24
+ def call
25
+ # It may seem a little odd to yield self via an instance_exec, however in some cases I need a
26
+ # receiver for messages (i.e. FromJsonClassBuilder)
27
+ instance_exec(self, &additional_configuration) if additional_configuration.respond_to?(:call)
28
+ defer do |subject|
29
+ subject.module_exec(@attribute_names) do |attribute_names|
30
+ define_method(:initialize) do |**attributes|
31
+ attribute_names.each do |attribute_name|
32
+ send("#{attribute_name}=", attributes.fetch(attribute_name.to_sym, nil))
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def attribute(name:, **options)
40
+ name = name.to_sym
41
+ attribute_names << name
42
+ builder.attribute(name: name, **options)
43
+ end
44
+
45
+ private
46
+
47
+ attr_accessor :additional_configuration, :attribute_names
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,96 @@
1
+ require 'json'
2
+ module Democritus
3
+ # Responsible for building a class based on the given JSON document.
4
+ #
5
+ # Note the following structure:
6
+ #
7
+ # @example
8
+ # ```json
9
+ # { "#command_name": { "keyword_param_one": "param_value", "#nested_command_name": { "nested_keyword_param": "nested_param_value"} } }
10
+ # ```
11
+ #
12
+ # Commands that are called against the builder are Hash keys that start with '#'. Keywords are command parameters that
13
+ # do not start with '#'.
14
+ #
15
+ # @note This is a class with a greater "reek" than I would like. However, it
16
+ # is parsing JSON and loading that into ruby; Its complicated. So I'm
17
+ # willing to accept and assume responsibility for this code "reek".
18
+ #
19
+ # @see Democritus::ClassBuilder::Commands
20
+ # @see Democritus::FromJsonClassBuilder::KEY_IS_COMMAND_REGEXP
21
+ class FromJsonClassBuilder
22
+ # @api public
23
+ #
24
+ # @param json_document [String] A JSON document
25
+ def initialize(json_document)
26
+ self.data = json_document
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :data
32
+
33
+ def data=(json_document)
34
+ @data = JSON.parse(json_document)
35
+ end
36
+
37
+ public
38
+
39
+ # @api public
40
+ #
41
+ # A wrapper around the Democritus::ClassBuilder#generate_class. However instead of evaulating blocks, the builder must
42
+ # be called directly.
43
+ #
44
+ # @return Class object
45
+ def generate_class
46
+ keywords, nested_commands = extract_keywords_and_nested_commands(node: data)
47
+ class_builder = ClassBuilder.new(**keywords)
48
+ build(node: nested_commands, class_builder: class_builder)
49
+ class_builder.generate_class
50
+ end
51
+
52
+ KEY_IS_COMMAND_REGEXP = /\A\#(.+)$/.freeze
53
+
54
+ private
55
+
56
+ # rubocop:disable MethodLength
57
+ # :reek:TooManyStatements: { exclude: [ 'Democritus::FromJsonClassBuilder#extract_keywords_and_nested_commands' ] }
58
+ # :reek:UtilityFunction: { exclude: [ 'Democritus::FromJsonClassBuilder#extract_keywords_and_nested_commands' ] }
59
+ def extract_keywords_and_nested_commands(node:)
60
+ keywords = {}
61
+ options = {}
62
+ node.each_pair do |key, value|
63
+ match_data = KEY_IS_COMMAND_REGEXP.match(key)
64
+ if match_data
65
+ options[match_data[1].to_sym] = value
66
+ else
67
+ keywords[key.to_sym] = value
68
+ end
69
+ end
70
+ return [keywords, options]
71
+ end
72
+ # rubocop:enable MethodLength
73
+
74
+ # :reek:NestedIterators: { exclude: [ 'Democritus::FromJsonClassBuilder#build' ] }
75
+ def build(node:, class_builder:)
76
+ json_class_builder = self # establishing local binding
77
+ each_command(node: node) do |command_name, keywords, nested_commands|
78
+ class_builder.public_send(command_name, **keywords) do |nested_builder|
79
+ json_class_builder.send(:build, node: nested_commands, class_builder: nested_builder)
80
+ end
81
+ end
82
+ end
83
+
84
+ # :reek:NestedIterators: { exclude: [ 'Democritus::FromJsonClassBuilder#each_command' ] }
85
+ # :reek:FeatureEnvy: { exclude: [ 'Democritus::FromJsonClassBuilder#each_command' ] }
86
+ def each_command(node:)
87
+ node.each_pair do |command_name, nested_nodes|
88
+ nested_nodes = [nested_nodes] unless nested_nodes.is_a?(Array)
89
+ nested_nodes.each do |nested_node|
90
+ keywords, nested_commands = extract_keywords_and_nested_commands(node: nested_node)
91
+ yield(command_name, keywords, nested_commands)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,3 +1,6 @@
1
1
  module Democritus
2
- VERSION = "0.1.0"
2
+ # Democritus is striving to adhere to semantic versioning.
3
+ #
4
+ # @see semver.org
5
+ VERSION = "0.2.0"
3
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: democritus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Friesen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-12-16 00:00:00.000000000 Z
11
+ date: 2016-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.9'
19
+ version: '1.6'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.9'
26
+ version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,104 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: reek
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: flay
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
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: codeclimate-test-reporter
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: byebug
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
69
167
  description: A placeholder for an attribute declaration mechanism
70
168
  email:
71
169
  - jeremy.n.friesen@gmail.com
@@ -74,6 +172,7 @@ extensions: []
74
172
  extra_rdoc_files: []
75
173
  files:
76
174
  - ".gitignore"
175
+ - ".hound.yml"
77
176
  - ".travis.yml"
78
177
  - Gemfile
79
178
  - LICENSE
@@ -84,6 +183,11 @@ files:
84
183
  - democritus.gemspec
85
184
  - lib/democritus.rb
86
185
  - lib/democritus/class_builder.rb
186
+ - lib/democritus/class_builder/command.rb
187
+ - lib/democritus/class_builder/commands.rb
188
+ - lib/democritus/class_builder/commands/attribute.rb
189
+ - lib/democritus/class_builder/commands/attributes.rb
190
+ - lib/democritus/from_json_class_builder.rb
87
191
  - lib/democritus/version.rb
88
192
  homepage: https://github.com/jeremyf/democritus
89
193
  licenses: []
@@ -94,9 +198,9 @@ require_paths:
94
198
  - lib
95
199
  required_ruby_version: !ruby/object:Gem::Requirement
96
200
  requirements:
97
- - - ">="
201
+ - - "~>"
98
202
  - !ruby/object:Gem::Version
99
- version: '0'
203
+ version: '2.1'
100
204
  required_rubygems_version: !ruby/object:Gem::Requirement
101
205
  requirements:
102
206
  - - ">="