democritus 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  - - ">="