speculate_about 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 79f9d62e9eca9435df9fc7c9c5851be34190128cb5bf839035ae54623aabd623
4
+ data.tar.gz: 0ec1ab318bbb646c8fae2fbd40d1c5a0b4b809104559f75499d2edc274dc6862
5
+ SHA512:
6
+ metadata.gz: f88a8f63272fb0dfe276a00b454d527178ec798378270d62d5ea2d266e01bae8e1260a2edb261444eb0ab3c4582651b3c6f66c7abb0f333d93d0abc50979239e
7
+ data.tar.gz: ca9364804df625b912058277c01f2b20d526de1a2264c27b7f8d0ceba025074288425d3ed8ce0f9f22c4d31088aa1339daf6bc73f166342b4f18e6a8036bbe45
@@ -0,0 +1,29 @@
1
+ require 'rspec'
2
+
3
+ require 'speculations/parser'
4
+
5
+ module SpeculateAbout
6
+ def speculate_about file
7
+ path = _find_file(file)
8
+ code = _compile path
9
+ ENV["SPECULATE_ABOUT_DEBUG"] ? puts(code) : instance_eval(code)
10
+ end
11
+
12
+
13
+ private
14
+
15
+ def _compile path
16
+ ast = Speculations::Parser.new.parse_from_file(path)
17
+ ast.to_code
18
+ end
19
+ def _find_file file
20
+ return file if File.readable? file
21
+ path = File.join(File.dirname(location), file)
22
+ return path if File.readable? path
23
+ raise ArgumentError, "file #{file} neither found in your project dir, neither your spec dir"
24
+ end
25
+ end
26
+
27
+ RSpec.configure do |conf|
28
+ conf.extend SpeculateAbout
29
+ end
@@ -0,0 +1,2 @@
1
+ module Speculations
2
+ end
@@ -0,0 +1,39 @@
1
+ require_relative '../speculations'
2
+ class Speculations::Parser
3
+ require_relative './parser/context'
4
+ require_relative './parser/state'
5
+
6
+ attr_reader :filename, :input, :root, :state
7
+
8
+ def self.parsers
9
+ @__parsers__ ||= {
10
+ bef: State::Bef,
11
+ exa: State::Exa,
12
+ inc: State::Inc,
13
+ out: State::Out
14
+ }
15
+ end
16
+
17
+ def parse_from_file file
18
+ @filename = file
19
+ @input = File
20
+ .new(file)
21
+ .each_line(chomp: true)
22
+ .lazy
23
+ parse!
24
+ end
25
+
26
+ private
27
+
28
+ def initialize
29
+ @state = :out
30
+ end
31
+
32
+ def parse!
33
+ root = node = Context.new(name: "Speculations from #{@filename}", lnb: 0, filename: @filename, parent: nil)
34
+ input.each_with_index do |line, lnb|
35
+ @state, node = self.class.parsers.fetch(@state).parse(line, lnb.succ, node)
36
+ end
37
+ root
38
+ end
39
+ end
@@ -0,0 +1,80 @@
1
+ class Speculations::Parser::Context
2
+ require_relative './context/include'
3
+ require_relative './context/example'
4
+ require_relative './context/setup'
5
+
6
+ attr_reader :filename, :level, :lnb, :name, :parent, :setup
7
+
8
+ def add_child(name:, lnb:)
9
+ raise "Illegal nesting" if parent
10
+ children << self.class.new(name: name, lnb: lnb, parent: self)
11
+ children.last
12
+ end
13
+
14
+ def add_example(lnb:)
15
+ examples << Example.new(lnb: lnb, parent: self)
16
+ examples.last
17
+ end
18
+
19
+ def add_include(lnb:)
20
+ includes << Include.new(lnb: lnb, parent: self)
21
+ includes.last
22
+ end
23
+
24
+ def children
25
+ @__children__ ||= []
26
+ end
27
+
28
+ def examples
29
+ @__examples__ ||= []
30
+ end
31
+
32
+ def includes
33
+ @__includes__ ||= []
34
+ end
35
+
36
+ def map_lines(*lines, indent: 0)
37
+ prefix = " " * (level + indent)
38
+ lines.flatten.map{ |line| "#{prefix}#{line.strip}" }.join("\n")
39
+ end
40
+
41
+ def set_setup(lnb:)
42
+ @setup = Setup.new(lnb: lnb, parent: self)
43
+ end
44
+
45
+ def to_code
46
+ [
47
+ _header,
48
+ includes.map(&:to_code),
49
+ setup&.to_code,
50
+ examples.map(&:to_code),
51
+ children.map(&:to_code),
52
+ _footer
53
+ ].flatten.compact.join("\n")
54
+ end
55
+
56
+ private
57
+
58
+ def initialize(lnb:, name:, filename: nil, parent: nil)
59
+ _init_from_parent filename, parent
60
+ @level = parent ? parent.level.succ : 1
61
+ @lnb = lnb
62
+ @setup = nil
63
+ @name = name
64
+ @parent = parent
65
+ end
66
+
67
+ def _header
68
+ map_lines(%{context "#{name}" do}, indent: -1)
69
+ end
70
+
71
+ def _init_from_parent filename, parent
72
+ @filename = parent ? parent.filename : filename
73
+ raise ArgumentError, "no filename given in root context" unless @filename
74
+ end
75
+
76
+ def _footer
77
+ map_lines("end", indent: -1)
78
+ end
79
+
80
+ end
@@ -0,0 +1,34 @@
1
+ class Speculations::Parser::Context::Example
2
+
3
+ attr_reader :lnb, :name, :parent
4
+
5
+ def add_line line
6
+ lines << line
7
+ self
8
+ end
9
+
10
+ def lines
11
+ @__lines__ ||= []
12
+ end
13
+
14
+ def to_code
15
+ parent.map_lines(_example_head) +
16
+ "\n" +
17
+ parent.map_lines(lines, indent: 1) +
18
+ "\n" +
19
+ parent.map_lines("end")
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def initialize(lnb:, parent:)
26
+ @lnb = lnb
27
+ @name = "Example from #{parent.filename}:#{lnb.succ}"
28
+ @parent = parent
29
+ end
30
+
31
+ def _example_head
32
+ %{it "#{name}" do}
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ class Speculations::Parser::Context::Include
2
+
3
+ attr_reader :lnb, :parent
4
+
5
+ def add_line line
6
+ lines << line
7
+ self
8
+ end
9
+
10
+ def lines
11
+ @__lines__ ||= []
12
+ end
13
+
14
+ def to_code
15
+ parent.map_lines(lines)
16
+ end
17
+
18
+ private
19
+
20
+ def initialize(parent:, lnb:)
21
+ @parent = parent
22
+ @lnb = lnb
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ class Speculations::Parser::Context::Setup
2
+
3
+ attr_reader :lnb, :parent
4
+
5
+ def add_line line
6
+ lines << line
7
+ self
8
+ end
9
+
10
+ def lines
11
+ @__lines__ ||= []
12
+ end
13
+
14
+ def to_code
15
+ parent.map_lines("before do") +
16
+ "\n" +
17
+ parent.map_lines(lines, indent: 1) +
18
+ "\n" +
19
+ parent.map_lines("end")
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def initialize(lnb:, parent:)
26
+ @parent = parent
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Speculations::Parser::State extend self
2
+ require_relative './state/bef'
3
+ require_relative './state/exa'
4
+ require_relative './state/inc'
5
+ require_relative './state/out'
6
+
7
+ BEFORE_RGX = %r[\A\s{0,3}```.*\s:before]
8
+ CONTEXT_RGX = %r[\A\s{0,3}\#{1,7}\s+Context\s+(.*)]
9
+ EOBLOCK_RGX = %r[\A\s{0,3}```\s*\z]
10
+ EXAMPLE_RGX = %r[\A\s{0,3}```.*\s:example]
11
+ INCLUDE_RGX = %r[\A\s{0,3}```.*\s:include]
12
+
13
+ def before_match line
14
+ BEFORE_RGX =~ line
15
+ end
16
+
17
+ def context_match line
18
+ if match = CONTEXT_RGX.match(line)
19
+ match.captures.first
20
+ end
21
+ end
22
+
23
+ def eoblock_match line
24
+ EOBLOCK_RGX =~ line
25
+ end
26
+
27
+ def example_match line
28
+ EXAMPLE_RGX =~ line
29
+ end
30
+
31
+ def include_match line
32
+ INCLUDE_RGX =~ line
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module Speculations
2
+ class Parser
3
+ module State
4
+ module Bef extend self
5
+
6
+ def parse line, _lnb, node
7
+ case
8
+ when State.eoblock_match(line)
9
+ [:out, node.parent]
10
+ else
11
+ [:bef, node.add_line(line)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Speculations
2
+ class Parser
3
+ module State
4
+ module Exa extend self
5
+
6
+ def parse line, _lnb, node
7
+ case
8
+ when State.eoblock_match(line)
9
+ [:out, node.parent]
10
+ else
11
+ [:exa, node.add_line(line)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Speculations
2
+ class Parser
3
+ module State
4
+ module Inc extend self
5
+
6
+ def parse line, _lnb, node
7
+ case
8
+ when State.eoblock_match(line)
9
+ [:out, node.parent]
10
+ else
11
+ [:inc, node.add_line(line)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Speculations
2
+ class Parser
3
+ module State
4
+ module Out extend self
5
+
6
+ def parse line, lnb, node
7
+ case
8
+ when name = State.context_match(line)
9
+ node = node.parent if node.parent
10
+ new_node = node.add_child(name: name, lnb: lnb)
11
+ [:out, new_node]
12
+ when State.before_match(line)
13
+ node = node.set_setup(lnb: lnb)
14
+ [:bef, node]
15
+ when State.example_match(line)
16
+ node = node.add_example(lnb: lnb)
17
+ [:exa, node]
18
+ when State.include_match(line)
19
+ node = node.add_include(lnb: lnb)
20
+ [:inc, node]
21
+ else
22
+ [:out, node]
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: speculate_about
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.9'
27
+ description: Allows Markdown or other text files to be used as literal specs, a la
28
+ Elixr doctest, but from any file.
29
+ email: robert.dober@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/speculate_about.rb
35
+ - lib/speculations.rb
36
+ - lib/speculations/parser.rb
37
+ - lib/speculations/parser/context.rb
38
+ - lib/speculations/parser/context/example.rb
39
+ - lib/speculations/parser/context/include.rb
40
+ - lib/speculations/parser/context/setup.rb
41
+ - lib/speculations/parser/state.rb
42
+ - lib/speculations/parser/state/bef.rb
43
+ - lib/speculations/parser/state/exa.rb
44
+ - lib/speculations/parser/state/inc.rb
45
+ - lib/speculations/parser/state/out.rb
46
+ homepage: https://github.com/robertdober/speculate
47
+ licenses:
48
+ - Apache-2.0
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 2.7.0
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.1.2
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Extract RSpecs from Markdown
69
+ test_files: []