spectro 0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1bf7b456866166c224e87dca6b5526766412dd3f
4
+ data.tar.gz: 6e888e0846f3111e2af07f77b5d982c1e7fe6e20
5
+ SHA512:
6
+ metadata.gz: de13b0e41bb2bf2fc3a0aedb8600593e0d587789faf99633a189faa0182f4f60d45bb4dc3abcbd2fbd0cbff350681f6203d246246eae76fc6682b6defdc2ba98
7
+ data.tar.gz: fe5c65a3242a62ca08a4bfd9936cfe39662b7dc8d5bcb20c5df2c0edacfebb90c5f5d929c33d4ce81fa9be32f25b0627af67b347dd6c76e3b70f62aa4071c2b4
@@ -0,0 +1,33 @@
1
+ # Spectro
2
+
3
+ Specs driven social meta-programming
4
+
5
+ [![Build Status](https://api.travis-ci.org/robertodecurnex/spectro.png)](https://travis-ci.org/robertodecurnex/spectro)
6
+ [![Code Climate](https://codeclimate.com/github/robertodecurnex/spectro/badges/gpa.svg)](https://codeclimate.com/github/robertodecurnex/spectro)
7
+ [![Test Coverage](https://codeclimate.com/github/robertodecurnex/spectro/badges/coverage.svg)](https://codeclimate.com/github/robertodecurnex/spectro)
8
+ [![YARD Docs](https://img.shields.io/badge/YARD-Docs-blue.svg)](http://www.rubydoc.info/github/robertodecurnex/spectro/master)
9
+
10
+ ## Prototype
11
+
12
+ ```ruby
13
+ require 'spectro'
14
+
15
+ class Sample
16
+
17
+ include Spectro
18
+
19
+ implements \
20
+ hello: [:name]
21
+
22
+ end
23
+
24
+ __END__
25
+ spec_for hello String -> String
26
+ "Minion" -> "Say Hello to Minion"
27
+ "Roberto" -> "Say Hello to Roberto"
28
+ "Roland" -> "Say Hello to Roland"
29
+ ```
30
+
31
+ ```ruby
32
+ sample.hello 'Eddie' #=> 'Say Hello to Eddie'
33
+ ```
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'spectro/client'
4
+
5
+ Spectro::Client.start
@@ -0,0 +1,52 @@
1
+ require 'digest'
2
+ require 'forwardable'
3
+ require 'singleton'
4
+ require 'yaml'
5
+
6
+ require 'spectro/config'
7
+ require 'spectro/database'
8
+ require 'spectro/exception'
9
+ require 'spectro/mock'
10
+ require 'spectro/spec'
11
+
12
+ # Specs driven social meta-programming
13
+ module Spectro
14
+
15
+ # Extends the caller with the Spectro class methods on #include
16
+ def self.included klass
17
+ klass.extend(ClassMethods)
18
+ end
19
+
20
+ # Gives access to the Spectro::Config instance insde the given block
21
+ #
22
+ # Usage:
23
+ # Spectro.configure do |config|
24
+ # config.enable_mocks!
25
+ # end
26
+ def self.configure
27
+ yield Spectro::Config.instance
28
+ end
29
+
30
+ module ClassMethods
31
+
32
+ # Register the given method name supporting the given parameters.
33
+ #
34
+ # Whenever Spectro::Config.mocks_enabled? is true it will try to cover unfulfilled
35
+ # specs using the knwon rules as mocks.
36
+ #
37
+ # @param [{String, Symbol=><String, Symbol>}] interfaces hash of method names and required param names that the method supports
38
+ def implements interfaces
39
+ file_path = caller.first.match(/#{Dir.pwd}\/(.+):\d+:in .+/)[1]
40
+ interfaces.each do |method_name, required_params|
41
+ λ = Spectro::Database.fetch(file_path, method_name, *required_params) || Spectro::Mock.create(file_path, method_name)
42
+
43
+ raise Spectro::Exception::UndefinedMethodDefinition.new(file_path, method_name) if λ.nil?
44
+
45
+ self.send(:define_method, method_name, &λ)
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
@@ -0,0 +1,16 @@
1
+ require 'spectro'
2
+ require 'thor'
3
+
4
+ module Spectro
5
+
6
+ class Client < Thor
7
+
8
+ desc 'compile', 'Parses the current project looking for unfulfilled specs and looks for suitable lambdas in the repos. It then updates the cache with them.'
9
+ def compile
10
+ require 'spectro/compiler'
11
+ Spectro::Compiler.compile
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,65 @@
1
+ require 'spectro'
2
+ require 'spectro/spec/parser'
3
+ require 'yaml/store'
4
+
5
+ module Spectro
6
+
7
+ # Spectro:Compiler is in charge of spectroan the projects and parse its files,
8
+ # updating the project Spectro index and dumping information about the missing
9
+ # implementations (specs without an associated lambda)
10
+ class Compiler
11
+
12
+ include Singleton
13
+
14
+ class << self
15
+ extend Forwardable
16
+ def_delegators :instance, :compile
17
+ end
18
+
19
+ # Filters the project files keeping those making use of Spectro.
20
+ # It them parses those files, check for missing implementations
21
+ # and creates an .spectro/undefined.yml with the specs of them.
22
+ #
23
+ # @return [Spectro::Compiler] self
24
+ def compile
25
+ undefined_yaml = YAML::Store.new(".spectro/undefined.yml")
26
+ undefined_yaml.transaction do
27
+ targets().map do |path|
28
+ missing_specs = missing_specs_from_file(path)
29
+
30
+ next if missing_specs.empty?
31
+
32
+ undefined_yaml[path] = missing_specs
33
+ end
34
+ end
35
+
36
+ return self
37
+ end
38
+
39
+ private
40
+
41
+ # Parse the specs on the given file path and return those specs
42
+ # that have not been fulfilled or need to be updated.
43
+ #
44
+ # @param [String] path target file path
45
+ # @return [<Spectro::Spec>] collection of specs not fulfilled or out of date
46
+ def missing_specs_from_file(path)
47
+ Spectro::Spec::Parser.parse(path).select do |spec|
48
+ index_spec = Spectro::Database.index[path] && Spectro::Database.index[path][spec.signature.name]
49
+ index_spec.nil? || index_spec['spec_md5'] != spec.md5
50
+ end
51
+ end
52
+
53
+ # Filter project's rb files returning an Array of files
54
+ # containinig specs to be parsed.
55
+ #
56
+ # @return [<String>] array of files to be parsed
57
+ def targets
58
+ return %x[ grep -Pzrl --include="*.rb" "^__END__.*\\n.*spec_for" . ].split("\n").collect do |path|
59
+ path[2..-1]
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,31 @@
1
+ module Spectro
2
+
3
+ class Config
4
+
5
+ attr_accessor :mocks_enabled
6
+
7
+ include Singleton
8
+
9
+ class << self
10
+ extend Forwardable
11
+ def_delegators :instance, :enable_mocks!, :mocks_enabled?
12
+ end
13
+
14
+ # Sets mocks_enabled to true
15
+ #
16
+ # @return [Spectro::Config] self
17
+ def enable_mocks!
18
+ self.mocks_enabled = true
19
+ return self
20
+ end
21
+
22
+ # Returns the current mocks policy (enabled or disabled)
23
+ #
24
+ # @return [TrueClass, FalseClass] whether mocks are enabled or not
25
+ def mocks_enabled?
26
+ return !!self.mocks_enabled
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,45 @@
1
+ module Spectro
2
+
3
+ # Gives access to the current collection of
4
+ # algorithms (lambdas) providing several ways
5
+ # to fetch specific elements by different criterias.
6
+ class Database
7
+
8
+ include Singleton
9
+
10
+ class << self
11
+ extend Forwardable
12
+ def_delegators :instance, :fetch, :index
13
+ end
14
+
15
+ attr_accessor :cache
16
+
17
+ def initialize
18
+ self.cache = {}
19
+ end
20
+
21
+ # Lazy loads the index.yml and returns it
22
+ #
23
+ # @return [Hash] the parsed index.yml
24
+ def index
25
+ @index ||= YAML.load_file('./.spectro/index.yml')
26
+ end
27
+
28
+ # Fetches and return the target lambda based on the
29
+ # given class, method name and required aprameters.
30
+ #
31
+ # @param [String] file_path relative path of the file that requests the lambda
32
+ # @param [Symbol] method_name the method name that would be implemented
33
+ # @param [<Symbol>] required_params parameters that would be required by the lambda
34
+ # @return [Proc] the labda that would be implemented
35
+ def fetch file_path, method_name, *required_params
36
+ if self.index["#{file_path}"].nil? || self.index["#{file_path}"]["#{method_name}"].nil?
37
+ return nil
38
+ end
39
+ λ_id = self.index["#{file_path}"]["#{method_name}"]['lambda_id']
40
+ return self.cache[λ_id] ||= eval(File.read(".spectro/cache/#{λ_id}.rb"))
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,9 @@
1
+ require 'spectro/exception/undefined_method_definition'
2
+ require 'spectro/exception/unknown_mock_response'
3
+
4
+ module Spectro
5
+
6
+ module Exception
7
+ end
8
+
9
+ end
@@ -0,0 +1,22 @@
1
+ module Spectro
2
+
3
+ module Exception
4
+
5
+ class UndefinedMethodDefinition < ::Exception
6
+
7
+ attr_accessor :file_path, :method_name
8
+
9
+ def initialize file_path, method_name
10
+ self.file_path = file_path
11
+ self.method_name = method_name
12
+ end
13
+
14
+ def to_s
15
+ "[##{self.method_name}] has not been defiend for \"#{self.file_path}\". Verify the specs at \"#{self.file_path}\" and check the `spectro compile` output for more detailed information."
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,22 @@
1
+ module Spectro
2
+
3
+ module Exception
4
+
5
+ class UnknownMockResponse < ::Exception
6
+
7
+ attr_accessor :file_path, :method_name
8
+
9
+ def initialize file_path, method_name
10
+ self.file_path = file_path
11
+ self.method_name = method_name
12
+ end
13
+
14
+ def to_s
15
+ "[##{self.method_name}] mock has not response defined for the given params. Verify the specs at \"#{self.file_path}\"."
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,41 @@
1
+ module Spectro
2
+
3
+ class Mock
4
+
5
+ # Creates a mock of the given method for the given file based
6
+ # on the spec rules.
7
+ # If mocks are not enabled it defaults to nil
8
+ #
9
+ # @param [String] file_path relative path of the file that requests the lambda
10
+ # @param [Symbol] method_name the method name that would be implemented
11
+ # @return [NilClass, Proc] the mock as Proc or nil if mocks are disabled
12
+ def self.create file_path, method_name
13
+ return nil unless Spectro::Config.mocks_enabled?
14
+
15
+ spec = Spectro::Spec::Parser.parse(file_path).detect do |spec|
16
+ spec.signature.name == method_name.to_s
17
+ end
18
+
19
+ param_names = Array.new(spec.signature.params_types.count) do |index|
20
+ ('a'.ord+index).chr
21
+ end
22
+
23
+ responses = spec.rules.inject({}) do |memo, rule|
24
+ memo[rule.params] = rule.output
25
+ memo
26
+ end
27
+
28
+ return eval "
29
+ lambda do |#{param_names.join(',')}|
30
+ if !responses.has_key?([#{param_names.join(',')}])
31
+ raise Spectro::Exception::UnknownMockResponse.new(file_path, method_name)
32
+ end
33
+
34
+ return responses[[#{param_names.join(',')}]]
35
+ end
36
+ "
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,28 @@
1
+ require 'spectro/spec/parser'
2
+ require 'spectro/spec/rule'
3
+ require 'spectro/spec/signature'
4
+
5
+ module Spectro
6
+
7
+ class Spec
8
+
9
+ attr_accessor :md5, :rules, :signature
10
+
11
+ # @param [String] spec md5
12
+ # @param [Spectro::Spec::Signature] signature spec signature
13
+ # @param [<Spectro::Spec::Rule>] rules collection of spec rules
14
+ def initialize md5, signature, rules
15
+ self.md5 = md5
16
+ self.rules = rules
17
+ self.signature = signature
18
+ end
19
+
20
+ def == spec
21
+ return \
22
+ self.signature == spec.signature && \
23
+ self.rules == spec.rules
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,86 @@
1
+ require 'spectro'
2
+
3
+ module Spectro
4
+
5
+ class Spec
6
+
7
+ # Parser to get Spectro::Spec instances from the metadata on the program's files
8
+ class Parser
9
+
10
+ attr_accessor :file_path
11
+
12
+ # @param [String] file_path the path of the file to parse
13
+ def initialize file_path
14
+ self.file_path = file_path
15
+ end
16
+
17
+ # Create an instance of Spectro::Spec::Parser for the given file path
18
+ # and return the #parse response (the collection of Spectro::Spec instances
19
+ # for the given file)
20
+ #
21
+ # @param [String] file_path the path of the file to parse
22
+ # @return [<Spectro::Spec>] collection of specs found in the given file path
23
+ def self.parse(file_path)
24
+ Spectro::Spec::Parser.new(file_path).parse
25
+ end
26
+
27
+ # Look for specs on the given file and parse them as Spectro::Specs
28
+ #
29
+ # @return [<Spectro::Spec>] collection of specs found in the given file path
30
+ def parse
31
+ /.*^__END__$(?<raw_specs>.*)\Z/m =~ File.read(self.file_path)
32
+ return raw_specs.split('spec_for')[1..-1].map do |raw_spec|
33
+ self.parse_spec raw_spec
34
+ end
35
+ end
36
+
37
+ # Parses a raw spec and returns an Spectro::Spec instance
38
+ #
39
+ # @param [String] raw_spec raw spec
40
+ # @return [Spectro::Spec] the Spectro::Spec instance
41
+ def parse_spec raw_spec
42
+ spec_raw_signature, *spec_raw_rules = raw_spec.split("\n").reject(&:empty?)
43
+
44
+ spec_signature = self.parse_spec_signature(spec_raw_signature)
45
+
46
+ spec_rules = spec_raw_rules.map do |spec_raw_rule|
47
+ self.parse_spec_rule(spec_raw_rule)
48
+ end
49
+
50
+ spec_md5 = Digest::MD5.hexdigest(raw_spec)
51
+
52
+ return Spectro::Spec.new(spec_md5, spec_signature, spec_rules)
53
+ end
54
+
55
+ # Returns a Spectro::Spec::Rule instance from the raw spec rule
56
+ #
57
+ # @param [String] spec_raw_rule raw rule if the spec
58
+ # @return [Spectro::Spec::Rule]
59
+ def parse_spec_rule spec_raw_rule
60
+ # REGEX HERE PLEASE, F%#&!@* EASY
61
+ raw_params, raw_output = spec_raw_rule.split('->').map(&:strip)
62
+ output = eval(raw_output)
63
+ params = raw_params.split(/,\s+/).map do |raw_param|
64
+ eval(raw_param)
65
+ end
66
+
67
+ return Spectro::Spec::Rule.new(params, output)
68
+ end
69
+
70
+ # Returns a Spectro::Spec::Signature from the raw spec signature
71
+ #
72
+ # @param [String] spec_raw_signature raw signature of the spec
73
+ # @param [<Spectro::Spec::Signature]
74
+ def parse_spec_signature spec_raw_signature
75
+ # REGEX HERE PLEASE, F%#&!@* EASY
76
+ raw_name_and_params_types, output_type = spec_raw_signature.split('->').map(&:strip)
77
+ name, *params_types = raw_name_and_params_types.split(/,?\s+/).map(&:strip)
78
+
79
+ return Spectro::Spec::Signature.new(name, params_types, output_type)
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,28 @@
1
+ module Spectro
2
+
3
+ class Spec
4
+
5
+ # Assert representation based on input params and an expected output.
6
+ # Meant to be used against an algorith to test its behavior.
7
+ class Rule
8
+
9
+ attr_accessor :output, :params
10
+
11
+ # @param [<Object>] parmas set of input params
12
+ # @param [<Object>] output expected result
13
+ def initialize params, output
14
+ self.output = output
15
+ self.params = params
16
+ end
17
+
18
+ def == rule
19
+ return \
20
+ self.output == rule.output && \
21
+ self.params == rule.params
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,30 @@
1
+ module Spectro
2
+
3
+ class Spec
4
+
5
+ # Representation of the required input/output types
6
+ class Signature
7
+
8
+ attr_accessor :name, :output_type, :params_types
9
+
10
+ # @param [String] name local name of the algorith (not sure if needed)
11
+ # @param [<String>] param_types types of the expected input params
12
+ # @param [String] output_type type of the expected output
13
+ def initialize name, params_types, output_type
14
+ self.name = name
15
+ self.output_type = output_type
16
+ self.params_types = params_types
17
+ end
18
+
19
+ def == signature
20
+ return \
21
+ self.name == signature.name && \
22
+ self.output_type == signature.output_type && \
23
+ self.params_types == signature.params_types
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spectro
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Roberto Decurnex
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
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: codeclimate-test-reporter
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard
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: guard-rake
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: minitest
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: rake
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: yard
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
+ description:
112
+ email: decurnex.roberto@gmail.com
113
+ executables:
114
+ - spectro
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - README.md
118
+ files:
119
+ - README.md
120
+ - bin/spectro
121
+ - lib/spectro.rb
122
+ - lib/spectro/client.rb
123
+ - lib/spectro/compiler.rb
124
+ - lib/spectro/config.rb
125
+ - lib/spectro/database.rb
126
+ - lib/spectro/exception.rb
127
+ - lib/spectro/exception/undefined_method_definition.rb
128
+ - lib/spectro/exception/unknown_mock_response.rb
129
+ - lib/spectro/mock.rb
130
+ - lib/spectro/spec.rb
131
+ - lib/spectro/spec/parser.rb
132
+ - lib/spectro/spec/rule.rb
133
+ - lib/spectro/spec/signature.rb
134
+ homepage: http://github.com/robertodecurnex/spectro
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 2.0.0
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.4.5
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Specs driven social meta-programming
158
+ test_files: []
159
+ has_rdoc: