speculate_about 0.4.2 → 0.6.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 +4 -4
- data/bin/speculate +4 -0
- data/lib/speculate_about.rb +8 -26
- data/lib/speculations/cli.rb +49 -0
- data/lib/speculations/parser/context/example.rb +6 -17
- data/lib/speculations/parser/context/include.rb +1 -1
- data/lib/speculations/parser/context.rb +118 -107
- data/lib/speculations/parser/state/candidate.rb +32 -0
- data/lib/speculations/parser/state/{bef.rb → in.rb} +3 -3
- data/lib/speculations/parser/state/out.rb +14 -59
- data/lib/speculations/parser/state/triggers.rb +35 -0
- data/lib/speculations/parser/state.rb +4 -55
- data/lib/speculations/parser.rb +30 -32
- data/lib/speculations/version.rb +2 -2
- data/lib/speculations.rb +43 -1
- metadata +12 -24
- data/lib/speculations/parser/context/setup.rb +0 -28
- data/lib/speculations/parser/state/exa.rb +0 -17
- data/lib/speculations/parser/state/inc.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddf1c9fa5d8bb2d18f142e4fcc18aad6ca71d0694829b7bd0f7d291b0e3c97d2
|
4
|
+
data.tar.gz: '06689091cee69a4f3df817efc5ba8be2ed754d85fe98ed5cb9d27c28b58be506'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c382d674930d2e61a8494de4331a4b4f3afef2cc6b77193fbb09af4610f3c8944f65edb09ff5ff550dfea5f56114692c1d93f37f04a88d30e91d014deba7b7f0
|
7
|
+
data.tar.gz: b29b76a402e49063919eeded1724c7ea2d87c557fba1ea6f4e526d9e9a322da746669d460d3effbc4146090f57559a4c77523464e9cc44f324172c0363e783f1
|
data/bin/speculate
ADDED
data/lib/speculate_about.rb
CHANGED
@@ -1,34 +1,15 @@
|
|
1
1
|
require 'rspec'
|
2
|
-
|
3
|
-
require 'speculations/parser'
|
4
|
-
|
2
|
+
require_relative 'speculations/parser'
|
5
3
|
module SpeculateAbout
|
6
|
-
def speculate_about file, alternate_syntax: false
|
7
|
-
paths = _find_files(file, File.dirname( caller.first ))
|
8
|
-
raise ArgumentError, "no files found for pattern #{file}" if paths.empty?
|
9
|
-
paths.each do |path|
|
10
|
-
code = _compile path, _readable(path), alternate_syntax: alternate_syntax
|
11
|
-
ENV["SPECULATE_ABOUT_DEBUG"] ? _show(code, path) : instance_eval(code, path)
|
12
|
-
end
|
13
|
-
end
|
14
4
|
|
5
|
+
def speculate_about(infile)
|
6
|
+
raise ArgumentError, "#{infile} not found" unless File.readable? infile
|
7
|
+
ast = Speculations::Parser.new.parse_from_file(infile)
|
8
|
+
code = ast.to_code.join("\n")
|
9
|
+
ENV["SPECULATE_ABOUT_DEBUG"] ? _show(code, infile) : instance_eval(code, infile)
|
10
|
+
end
|
15
11
|
|
16
12
|
private
|
17
|
-
|
18
|
-
def _compile path, file, alternate_syntax: false
|
19
|
-
ast = Speculations::Parser.new.parse_from_file(path, file, alternate_syntax: alternate_syntax)
|
20
|
-
ast.to_code
|
21
|
-
end
|
22
|
-
def _readable(path)
|
23
|
-
d = File.dirname(path)
|
24
|
-
File.join(File.basename(d), File.basename(path))
|
25
|
-
end
|
26
|
-
def _find_files file, local_path
|
27
|
-
[Dir.pwd, local_path]
|
28
|
-
.flat_map do |dir|
|
29
|
-
Dir.glob(File.join(dir, file))
|
30
|
-
end
|
31
|
-
end
|
32
13
|
def _show(code, path)
|
33
14
|
message = "Generated code for #{path}"
|
34
15
|
_underline(message)
|
@@ -38,6 +19,7 @@ module SpeculateAbout
|
|
38
19
|
puts message
|
39
20
|
puts message.gsub(/./, ul)
|
40
21
|
end
|
22
|
+
|
41
23
|
end
|
42
24
|
|
43
25
|
RSpec.configure do |conf|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
module Speculations
|
3
|
+
module CLI extend self
|
4
|
+
def run args
|
5
|
+
loop do
|
6
|
+
case args.first
|
7
|
+
when "-h", "--help"
|
8
|
+
_usage
|
9
|
+
when "-v", "--version"
|
10
|
+
_version
|
11
|
+
else
|
12
|
+
return _compile_and_maybe_run args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def _compile_and_maybe_run args
|
20
|
+
require_relative "../speculations"
|
21
|
+
args = Dir.glob(["*.md", "speculations/**/*.md"]) if args.empty?
|
22
|
+
args.each do |input_file|
|
23
|
+
Speculations.compile(input_file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def _usage
|
28
|
+
puts <<-EOF
|
29
|
+
usage:
|
30
|
+
#{$0} [options] [filenames]
|
31
|
+
|
32
|
+
options:
|
33
|
+
-h | --help display this exit with -1
|
34
|
+
-v | --version display version of the speculate_about gem exit with -2
|
35
|
+
|
36
|
+
filenames (default to all markdown files in the project directory and its speculations subdirectories)
|
37
|
+
|
38
|
+
recreate outdated speculations in `spec/speculations/`
|
39
|
+
EOF
|
40
|
+
exit -1
|
41
|
+
end
|
42
|
+
|
43
|
+
def _version
|
44
|
+
require_relative 'version'
|
45
|
+
puts VERSION
|
46
|
+
exit -2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
class Speculations::Parser::Context::Example
|
2
2
|
|
3
|
-
attr_reader :lnb, :
|
3
|
+
attr_reader :lnb, :title, :parent
|
4
4
|
|
5
|
-
NAMED_EXAMPLE = %r{\A[`~]{3,}ruby\s+:example\s+(.*)}
|
6
5
|
|
7
6
|
def add_line line
|
8
7
|
lines << line
|
@@ -15,34 +14,24 @@ class Speculations::Parser::Context::Example
|
|
15
14
|
|
16
15
|
def to_code
|
17
16
|
parent.map_lines(_example_head) +
|
18
|
-
"\n" +
|
19
17
|
parent.map_lines(lines, indent: 1) +
|
20
|
-
"\n" +
|
21
18
|
parent.map_lines("end")
|
22
19
|
end
|
23
20
|
|
24
21
|
|
25
22
|
private
|
26
23
|
|
27
|
-
def initialize(lnb:,
|
24
|
+
def initialize(lnb:, parent:, title:)
|
28
25
|
@lnb = lnb
|
29
26
|
@parent = parent
|
30
|
-
@
|
27
|
+
@title = _compute_title(title)
|
31
28
|
end
|
32
29
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
if name1&.empty? == false # SIC
|
37
|
-
"#{name1} (#{parent.orig_filename}:#{lnb.succ})"
|
38
|
-
elsif name
|
39
|
-
"#{name} (#{parent.orig_filename}:#{lnb.succ})"
|
40
|
-
else
|
41
|
-
"Example from #{parent.orig_filename}:#{lnb.succ}"
|
42
|
-
end
|
30
|
+
def _compute_title(title)
|
31
|
+
"#{title} (#{parent.root.filename}:#{lnb})"
|
43
32
|
end
|
44
33
|
|
45
34
|
def _example_head
|
46
|
-
%{it "#{
|
35
|
+
%{it "#{title}" do}
|
47
36
|
end
|
48
37
|
end
|
@@ -1,109 +1,120 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
1
|
+
module Speculations
|
2
|
+
class Parser
|
3
|
+
class Context
|
4
|
+
require_relative './context/include'
|
5
|
+
require_relative './context/example'
|
6
|
+
|
7
|
+
attr_reader :filename, :level, :lnb, :title, :parent, :root, :tree_level
|
8
|
+
|
9
|
+
def new_context(title:, lnb:, level: )
|
10
|
+
new_child = self.class.new(title: title, lnb: lnb, parent: self, level: level)
|
11
|
+
_realign_levels(new_child)
|
12
|
+
end
|
13
|
+
|
14
|
+
def new_example(lnb:, title:)
|
15
|
+
examples << Example.new(lnb: lnb, parent: self, title: title)
|
16
|
+
examples.last
|
17
|
+
end
|
18
|
+
|
19
|
+
def new_include(lnb:)
|
20
|
+
includes << Include.new(lnb: lnb, parent: self)
|
21
|
+
includes.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def parent_of_level needed_min_level
|
25
|
+
# I would love to write
|
26
|
+
# self.enum_by(:parent).find{ |ctxt| ctxt.level <= needed_min_level }
|
27
|
+
current = self
|
28
|
+
while current.level > needed_min_level
|
29
|
+
current = current.parent
|
30
|
+
end
|
31
|
+
current
|
32
|
+
end
|
33
|
+
|
34
|
+
def children
|
35
|
+
@__children__ ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def examples
|
39
|
+
@__examples__ ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def includes
|
43
|
+
@__includes__ ||= []
|
44
|
+
end
|
45
|
+
|
46
|
+
def map_lines(*lines, indent: 0)
|
47
|
+
prefix = " " * (tree_level + indent).succ
|
48
|
+
lines.flatten.map{ |line| "#{prefix}#{line.strip}" }
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_code
|
52
|
+
[
|
53
|
+
_header,
|
54
|
+
includes.map(&:to_code),
|
55
|
+
examples.map(&:to_code),
|
56
|
+
children.map(&:to_code),
|
57
|
+
_footer
|
58
|
+
].flatten.compact
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_new_parent new_parent
|
62
|
+
@parent = new_parent
|
63
|
+
@tree_level += 1
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def initialize(lnb: 0, title: nil, filename: nil, parent: nil, level: 0)
|
71
|
+
@filename = filename
|
72
|
+
@level = level
|
73
|
+
@lnb = lnb
|
74
|
+
@title = title
|
75
|
+
@parent = parent
|
76
|
+
if parent
|
77
|
+
_init_from_parent
|
78
|
+
else
|
79
|
+
@root = self
|
80
|
+
@tree_level = 0
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def _header
|
85
|
+
if parent
|
86
|
+
map_lines(%{# #{filename}:#{lnb}}, %{context "#{title}" do}, indent: -1)
|
87
|
+
else
|
88
|
+
[]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def _init_from_parent
|
93
|
+
@root = parent.root
|
94
|
+
@filename = parent.filename
|
95
|
+
@tree_level = parent.tree_level.succ
|
96
|
+
end
|
97
|
+
|
98
|
+
def _footer
|
99
|
+
if parent
|
100
|
+
map_lines("end", indent: -1)
|
101
|
+
else
|
102
|
+
[]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def _realign_levels new_parent
|
107
|
+
if children.empty? || children.first.level == new_parent.level
|
108
|
+
children << new_parent
|
109
|
+
return new_parent
|
110
|
+
end
|
111
|
+
children.each do |child|
|
112
|
+
new_parent.children << child.with_new_parent(new_parent)
|
113
|
+
end
|
114
|
+
@__children__ = [new_parent]
|
115
|
+
new_parent
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
51
119
|
end
|
52
|
-
|
53
|
-
def set_given given
|
54
|
-
@given = given
|
55
|
-
self
|
56
|
-
end
|
57
|
-
|
58
|
-
def set_name(potential_name)
|
59
|
-
@potential_name = potential_name
|
60
|
-
self
|
61
|
-
end
|
62
|
-
|
63
|
-
def set_setup(lnb:)
|
64
|
-
@setup = Setup.new(lnb: lnb, parent: self)
|
65
|
-
end
|
66
|
-
|
67
|
-
def to_code
|
68
|
-
[
|
69
|
-
_header,
|
70
|
-
includes.map(&:to_code),
|
71
|
-
setup&.to_code,
|
72
|
-
examples.map(&:to_code),
|
73
|
-
children.map(&:to_code),
|
74
|
-
_footer
|
75
|
-
].flatten.compact.join("\n")
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def initialize(alternate_syntax: false, lnb:, name:, filename: nil, orig_filename: nil, parent: nil)
|
81
|
-
_init_from_parent filename, orig_filename, parent
|
82
|
-
@alternate_syntax = alternate_syntax
|
83
|
-
@given = false
|
84
|
-
@level = parent ? parent.level.succ : 1
|
85
|
-
@lnb = lnb
|
86
|
-
@setup = nil
|
87
|
-
@name = name
|
88
|
-
@parent = parent
|
89
|
-
end
|
90
|
-
|
91
|
-
def _header
|
92
|
-
map_lines(%{context "#{name}" do}, indent: -1)
|
93
|
-
end
|
94
|
-
|
95
|
-
def _init_from_parent filename, orig_filename, parent
|
96
|
-
_set_filename filename, orig_filename, parent
|
97
|
-
@orig_filename = parent ? parent.orig_filename : orig_filename
|
98
|
-
end
|
99
|
-
|
100
|
-
def _set_filename filename, orig_filename, parent
|
101
|
-
@filename = parent ? parent.filename : filename
|
102
|
-
raise ArgumentError, "no filename given in root context" unless @filename
|
103
|
-
end
|
104
|
-
|
105
|
-
def _footer
|
106
|
-
map_lines("end", indent: -1)
|
107
|
-
end
|
108
|
-
|
109
120
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Speculations
|
2
|
+
class Parser
|
3
|
+
module State
|
4
|
+
module Candidate extend self
|
5
|
+
def parse line, lnb, node, ctxt
|
6
|
+
case
|
7
|
+
when State.blank_line(line)
|
8
|
+
[:candidate, node, ctxt]
|
9
|
+
when match = State.context_match(line)
|
10
|
+
level = match[1].size
|
11
|
+
new_parent = node.parent_of_level(level.pred)
|
12
|
+
node = new_parent.new_context(title: match[2], lnb: lnb, level: level)
|
13
|
+
[:out, node]
|
14
|
+
when match = State.maybe_example(line)
|
15
|
+
[:candidate, node, match[:title]]
|
16
|
+
when match = State.maybe_include(line)
|
17
|
+
[:candidate, node, :inc]
|
18
|
+
when match = State.ruby_code_block(line)
|
19
|
+
if ctxt == :inc
|
20
|
+
node = node.new_include(lnb: lnb)
|
21
|
+
else
|
22
|
+
node = node.new_example(title: ctxt, lnb: lnb)
|
23
|
+
end
|
24
|
+
[:in, node]
|
25
|
+
else
|
26
|
+
[:out, node]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Speculations
|
2
2
|
class Parser
|
3
3
|
module State
|
4
|
-
module
|
4
|
+
module In extend self
|
5
5
|
|
6
|
-
def parse line, _lnb, node
|
6
|
+
def parse line, _lnb, node, ctxt
|
7
7
|
case
|
8
8
|
when State.eoblock_match(line)
|
9
9
|
[:out, node.parent]
|
10
10
|
else
|
11
|
-
[:
|
11
|
+
[:in, node.add_line(line)]
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -3,71 +3,26 @@ module Speculations
|
|
3
3
|
module State
|
4
4
|
module Out extend self
|
5
5
|
|
6
|
-
def parse line, lnb, node
|
7
|
-
|
8
|
-
|
6
|
+
def parse line, lnb, node, _ctxt
|
7
|
+
case
|
8
|
+
when match = State.context_match(line)
|
9
|
+
make_new_context(lnb: lnb, node: node, match: match)
|
10
|
+
when match = State.maybe_example(line)
|
11
|
+
[:candidate, node, match[:title]]
|
12
|
+
when match = State.maybe_include(line)
|
13
|
+
[:candidate, node, :inc]
|
9
14
|
else
|
10
|
-
|
15
|
+
[:out, node]
|
11
16
|
end
|
12
17
|
end
|
13
18
|
|
14
19
|
private
|
15
20
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
[:out, new_node.reset_context]
|
22
|
-
when State.ws_match(line)
|
23
|
-
[:out, node]
|
24
|
-
when name = State.potential_name(line)
|
25
|
-
node.set_name(name[1])
|
26
|
-
node.set_given(false)
|
27
|
-
[:out, node]
|
28
|
-
when name = State.then_match(line)
|
29
|
-
node.set_name(name[1])
|
30
|
-
node.set_given(false)
|
31
|
-
[:out, node]
|
32
|
-
when State.given_match(line)
|
33
|
-
node.set_name(nil)
|
34
|
-
[:out, node.set_given(true)]
|
35
|
-
when State.ruby_match(line)
|
36
|
-
if node.potential_name
|
37
|
-
node = node.add_example(lnb: lnb, line: line)
|
38
|
-
[:exa, node]
|
39
|
-
elsif node.given?
|
40
|
-
node = node.add_include(lnb: lnb)
|
41
|
-
[:inc, node]
|
42
|
-
else
|
43
|
-
[:out, node]
|
44
|
-
end
|
45
|
-
else
|
46
|
-
[:out, node.reset_context]
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def _parse_classical line, lnb, node
|
51
|
-
case
|
52
|
-
when name = State.context_match(line)
|
53
|
-
node = node.parent if node.parent
|
54
|
-
new_node = node.add_child(name: name, lnb: lnb)
|
55
|
-
[:out, new_node]
|
56
|
-
when State.before_match(line)
|
57
|
-
node = node.set_setup(lnb: lnb)
|
58
|
-
[:bef, node]
|
59
|
-
when State.example_match(line)
|
60
|
-
node = node.add_example(lnb: lnb, line: line)
|
61
|
-
[:exa, node]
|
62
|
-
when State.include_match(line)
|
63
|
-
node = node.add_include(lnb: lnb)
|
64
|
-
[:inc, node]
|
65
|
-
when name = State.potential_name(line)
|
66
|
-
node.set_name(name[1])
|
67
|
-
[:out, node]
|
68
|
-
else
|
69
|
-
[:out, node]
|
70
|
-
end
|
21
|
+
def make_new_context(lnb:, match:, node:)
|
22
|
+
level = match[:level].size
|
23
|
+
new_parent = node.parent_of_level(level.pred)
|
24
|
+
node = new_parent.new_context(title: match[:title], lnb: lnb, level: level)
|
25
|
+
[:out, node]
|
71
26
|
end
|
72
27
|
|
73
28
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Speculations::Parser::State::Triggers
|
2
|
+
|
3
|
+
BLANK_RGX = %r{\A\s*\z}
|
4
|
+
CONTEXT_RGX = %r[\A\s{0,3}(?<level>\#{1,7})\s+Context:?\s+(?<title>.*)]
|
5
|
+
EOBLOCK_RGX = %r[\A\s{0,3}```\s*\z]
|
6
|
+
GIVEN_RGX = %r[\A\s{0,3}(?:Given|When)\b]
|
7
|
+
EXAMPLE_RGX = %r[\A\s{0,3}Example:?\s+(<?title>.*)]
|
8
|
+
RUBY_RGX = %r[\A\s{0,3}```ruby]
|
9
|
+
THEN_RGX = %r[\A\s{0,3}(?:Then|But|And|Also)\b\s*(?<title>.*)]
|
10
|
+
|
11
|
+
def blank_line line
|
12
|
+
BLANK_RGX.match(line)
|
13
|
+
end
|
14
|
+
|
15
|
+
def context_match line
|
16
|
+
CONTEXT_RGX.match(line)
|
17
|
+
end
|
18
|
+
|
19
|
+
def eoblock_match line
|
20
|
+
EOBLOCK_RGX.match(line)
|
21
|
+
end
|
22
|
+
|
23
|
+
def maybe_example line
|
24
|
+
EXAMPLE_RGX.match(line) ||
|
25
|
+
THEN_RGX.match(line)
|
26
|
+
end
|
27
|
+
|
28
|
+
def maybe_include line
|
29
|
+
GIVEN_RGX.match(line)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ruby_code_block line
|
33
|
+
RUBY_RGX.match(line)
|
34
|
+
end
|
35
|
+
end
|
@@ -1,59 +1,8 @@
|
|
1
1
|
module Speculations::Parser::State extend self
|
2
|
-
require_relative './state/
|
3
|
-
require_relative './state/
|
4
|
-
require_relative './state/inc'
|
2
|
+
require_relative './state/candidate'
|
3
|
+
require_relative './state/in'
|
5
4
|
require_relative './state/out'
|
5
|
+
require_relative './state/triggers'
|
6
6
|
|
7
|
-
|
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
|
-
GIVEN_RGX = %r[\A\s{0,3}(?:Given|When)\b]
|
12
|
-
INCLUDE_RGX = %r[\A\s{0,3}```.*\s:include]
|
13
|
-
NAME_RGX = %r[\A\s{0,3}Example:?\s+(.*)]i
|
14
|
-
RUBY_RGX = %r[\A\s{0,3}```ruby]
|
15
|
-
THEN_RGX = %r[\A\s{0,3}(?:Then|But|And)\s+(.*)]
|
16
|
-
WS_RGX = %r[\A\s*\z]
|
17
|
-
|
18
|
-
def before_match line
|
19
|
-
BEFORE_RGX =~ line
|
20
|
-
end
|
21
|
-
|
22
|
-
def context_match line
|
23
|
-
if match = CONTEXT_RGX.match(line)
|
24
|
-
match.captures.first
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def eoblock_match line
|
29
|
-
EOBLOCK_RGX =~ line
|
30
|
-
end
|
31
|
-
|
32
|
-
def example_match line
|
33
|
-
EXAMPLE_RGX =~ line
|
34
|
-
end
|
35
|
-
|
36
|
-
def given_match line
|
37
|
-
GIVEN_RGX =~ line
|
38
|
-
end
|
39
|
-
|
40
|
-
def include_match line
|
41
|
-
INCLUDE_RGX =~ line
|
42
|
-
end
|
43
|
-
|
44
|
-
def potential_name line
|
45
|
-
NAME_RGX.match(line)
|
46
|
-
end
|
47
|
-
|
48
|
-
def ruby_match line
|
49
|
-
RUBY_RGX =~ line
|
50
|
-
end
|
51
|
-
|
52
|
-
def then_match line
|
53
|
-
THEN_RGX.match(line)
|
54
|
-
end
|
55
|
-
|
56
|
-
def ws_match line
|
57
|
-
WS_RGX =~ line
|
58
|
-
end
|
7
|
+
extend Triggers
|
59
8
|
end
|
data/lib/speculations/parser.rb
CHANGED
@@ -1,41 +1,39 @@
|
|
1
|
-
|
2
|
-
class
|
3
|
-
|
4
|
-
|
1
|
+
module Speculations
|
2
|
+
class Parser
|
3
|
+
require_relative './parser/context'
|
4
|
+
require_relative './parser/state'
|
5
5
|
|
6
|
-
attr_reader :alternate_syntax, :filename, :input, :orig_filename, :root, :state
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
7
|
+
def self.parsers
|
8
|
+
@__parsers__ ||= {
|
9
|
+
candidate: State::Candidate,
|
10
|
+
in: State::In,
|
11
|
+
out: State::Out
|
12
|
+
}
|
13
|
+
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
parse!
|
26
|
-
end
|
15
|
+
def parse_from_file file
|
16
|
+
@filename = file
|
17
|
+
@input = File
|
18
|
+
.new(file)
|
19
|
+
.each_line(chomp: true)
|
20
|
+
.lazy
|
21
|
+
parse!
|
22
|
+
end
|
27
23
|
|
28
|
-
|
24
|
+
private
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
def initialize
|
27
|
+
@state = :out
|
28
|
+
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
@
|
30
|
+
def parse!
|
31
|
+
root = node = Context.new(filename: @filename)
|
32
|
+
ctxt = nil
|
33
|
+
@input.each_with_index do |line, lnb|
|
34
|
+
@state, node, ctxt = self.class.parsers.fetch(@state).parse(line, lnb.succ, node, ctxt)
|
35
|
+
end
|
36
|
+
root
|
38
37
|
end
|
39
|
-
root
|
40
38
|
end
|
41
39
|
end
|
data/lib/speculations/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "0.
|
1
|
+
module Speculations
|
2
|
+
VERSION = "0.6.0"
|
3
3
|
end
|
data/lib/speculations.rb
CHANGED
@@ -1,2 +1,44 @@
|
|
1
|
-
|
1
|
+
require 'fileutils'
|
2
|
+
require_relative 'speculations/parser'
|
3
|
+
module Speculations extend self
|
4
|
+
|
5
|
+
DISCLAIMER = <<-EOD
|
6
|
+
# DO NOT EDIT!!!
|
7
|
+
# This file was generated from FILENAME with the speculate_about gem, if you modify this file
|
8
|
+
# one of two bad things will happen
|
9
|
+
# - your documentation specs are not correct
|
10
|
+
# - your modifications will be overwritten by the speculate rake task
|
11
|
+
# YOU HAVE BEEN WARNED
|
12
|
+
EOD
|
13
|
+
|
14
|
+
def compile(infile, outfile=nil)
|
15
|
+
raise ArgumentError, "#{infile} not found" unless File.readable? infile
|
16
|
+
outfile ||= _speculation_path(infile)
|
17
|
+
if _out_of_date?(outfile, infile)
|
18
|
+
ast = Speculations::Parser.new.parse_from_file(infile)
|
19
|
+
code = _decorated_ast_code ast, infile
|
20
|
+
File.write(outfile, code.join("\n"))
|
21
|
+
end
|
22
|
+
outfile
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def _decorated_ast_code ast, filename
|
28
|
+
[DISCLAIMER.gsub("FILENAME", filename.inspect).split("\n"), %{RSpec.describe #{filename.inspect} do}] +
|
29
|
+
ast.to_code + ["end"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def _out_of_date?(outf, inf)
|
33
|
+
return true unless File.exists? outf
|
34
|
+
return File.lstat(outf).mtime <= File.lstat(inf).mtime
|
35
|
+
end
|
36
|
+
|
37
|
+
def _speculation_path(file)
|
38
|
+
dir = File.dirname(file)
|
39
|
+
dest_dir = File.join("spec", "speculations", dir)
|
40
|
+
FileUtils.mkdir_p(dest_dir) unless File.directory?(dest_dir)
|
41
|
+
rspec = File.basename(file, ".md")
|
42
|
+
File.join(dest_dir, "#{rspec}_spec.rb")
|
43
|
+
end
|
2
44
|
end
|
metadata
CHANGED
@@ -1,48 +1,36 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speculate_about
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Dober
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
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'
|
11
|
+
date: 2022-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description: Allows Markdown or other text files to be used as literal specs, à la
|
28
14
|
Elixr doctest, but from any file.
|
29
15
|
email: robert.dober@gmail.com
|
30
|
-
executables:
|
16
|
+
executables:
|
17
|
+
- speculate
|
31
18
|
extensions: []
|
32
19
|
extra_rdoc_files: []
|
33
20
|
files:
|
21
|
+
- bin/speculate
|
34
22
|
- lib/speculate_about.rb
|
35
23
|
- lib/speculations.rb
|
24
|
+
- lib/speculations/cli.rb
|
36
25
|
- lib/speculations/parser.rb
|
37
26
|
- lib/speculations/parser/context.rb
|
38
27
|
- lib/speculations/parser/context/example.rb
|
39
28
|
- lib/speculations/parser/context/include.rb
|
40
|
-
- lib/speculations/parser/context/setup.rb
|
41
29
|
- lib/speculations/parser/state.rb
|
42
|
-
- lib/speculations/parser/state/
|
43
|
-
- lib/speculations/parser/state/
|
44
|
-
- lib/speculations/parser/state/inc.rb
|
30
|
+
- lib/speculations/parser/state/candidate.rb
|
31
|
+
- lib/speculations/parser/state/in.rb
|
45
32
|
- lib/speculations/parser/state/out.rb
|
33
|
+
- lib/speculations/parser/state/triggers.rb
|
46
34
|
- lib/speculations/version.rb
|
47
35
|
homepage: https://github.com/robertdober/speculate
|
48
36
|
licenses:
|
@@ -56,14 +44,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
44
|
requirements:
|
57
45
|
- - ">="
|
58
46
|
- !ruby/object:Gem::Version
|
59
|
-
version:
|
47
|
+
version: 3.1.0
|
60
48
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
49
|
requirements:
|
62
50
|
- - ">="
|
63
51
|
- !ruby/object:Gem::Version
|
64
52
|
version: '0'
|
65
53
|
requirements: []
|
66
|
-
rubygems_version: 3.
|
54
|
+
rubygems_version: 3.3.3
|
67
55
|
signing_key:
|
68
56
|
specification_version: 4
|
69
57
|
summary: Extract RSpecs from Markdown
|
@@ -1,28 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|