speculate_about 0.4.0 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/speculate +4 -0
- data/lib/speculate_about.rb +8 -26
- data/lib/speculations.rb +43 -1
- data/lib/speculations/cli.rb +49 -0
- data/lib/speculations/parser.rb +30 -32
- data/lib/speculations/parser/context.rb +118 -107
- data/lib/speculations/parser/context/example.rb +6 -17
- data/lib/speculations/parser/context/include.rb +1 -1
- data/lib/speculations/parser/state.rb +4 -55
- 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 -58
- data/lib/speculations/parser/state/triggers.rb +35 -0
- data/lib/speculations/version.rb +2 -2
- metadata +29 -13
- 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: d7347969c0f7e0d357050ba84401e3c069366c052b028a3e123c94ee355b9829
|
4
|
+
data.tar.gz: 277902ff252cfade683c395485015f03700028839dcd986d965dbfc5f6c1a425
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62daedfb2b2e00c7d9d1cac4fbe5c5fefcbfbb6dbfd49fed4cbdb4c224c37c54f80edd53aeb23215ca2192e0957a323dd7669c190917443b412e73976b8abbeb
|
7
|
+
data.tar.gz: 2a8626af0e4d46e63b514416d0341aaa61aa504f28ccf777827c701230ed1dddd08aa6b63314a066820799e05bb490b221f3481002eff144853c79732ce96464
|
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|
|
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
|
@@ -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
|
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
|
@@ -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
|
@@ -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,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\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
|
@@ -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,70 +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
|
-
[:out, node.set_given(true)]
|
34
|
-
when State.ruby_match(line)
|
35
|
-
if node.potential_name
|
36
|
-
node = node.add_example(lnb: lnb, line: line)
|
37
|
-
[:exa, node]
|
38
|
-
elsif node.given?
|
39
|
-
node = node.add_include(lnb: lnb)
|
40
|
-
[:inc, node]
|
41
|
-
else
|
42
|
-
[:out, node]
|
43
|
-
end
|
44
|
-
else
|
45
|
-
[:out, node.reset_context]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def _parse_classical line, lnb, node
|
50
|
-
case
|
51
|
-
when name = State.context_match(line)
|
52
|
-
node = node.parent if node.parent
|
53
|
-
new_node = node.add_child(name: name, lnb: lnb)
|
54
|
-
[:out, new_node]
|
55
|
-
when State.before_match(line)
|
56
|
-
node = node.set_setup(lnb: lnb)
|
57
|
-
[:bef, node]
|
58
|
-
when State.example_match(line)
|
59
|
-
node = node.add_example(lnb: lnb, line: line)
|
60
|
-
[:exa, node]
|
61
|
-
when State.include_match(line)
|
62
|
-
node = node.add_include(lnb: lnb)
|
63
|
-
[:inc, node]
|
64
|
-
when name = State.potential_name(line)
|
65
|
-
node.set_name(name[1])
|
66
|
-
[:out, node]
|
67
|
-
else
|
68
|
-
[:out, node]
|
69
|
-
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]
|
70
26
|
end
|
71
27
|
|
72
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
|
data/lib/speculations/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "0.
|
1
|
+
module Speculations
|
2
|
+
VERSION = "0.5.2"
|
3
3
|
end
|
metadata
CHANGED
@@ -1,54 +1,70 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speculate_about
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Dober
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '13.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '13.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: '3.
|
33
|
+
version: '3.10'
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
38
|
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: '3.
|
40
|
+
version: '3.10'
|
27
41
|
description: Allows Markdown or other text files to be used as literal specs, à la
|
28
42
|
Elixr doctest, but from any file.
|
29
43
|
email: robert.dober@gmail.com
|
30
|
-
executables:
|
44
|
+
executables:
|
45
|
+
- speculate
|
31
46
|
extensions: []
|
32
47
|
extra_rdoc_files: []
|
33
48
|
files:
|
49
|
+
- bin/speculate
|
34
50
|
- lib/speculate_about.rb
|
35
51
|
- lib/speculations.rb
|
52
|
+
- lib/speculations/cli.rb
|
36
53
|
- lib/speculations/parser.rb
|
37
54
|
- lib/speculations/parser/context.rb
|
38
55
|
- lib/speculations/parser/context/example.rb
|
39
56
|
- lib/speculations/parser/context/include.rb
|
40
|
-
- lib/speculations/parser/context/setup.rb
|
41
57
|
- lib/speculations/parser/state.rb
|
42
|
-
- lib/speculations/parser/state/
|
43
|
-
- lib/speculations/parser/state/
|
44
|
-
- lib/speculations/parser/state/inc.rb
|
58
|
+
- lib/speculations/parser/state/candidate.rb
|
59
|
+
- lib/speculations/parser/state/in.rb
|
45
60
|
- lib/speculations/parser/state/out.rb
|
61
|
+
- lib/speculations/parser/state/triggers.rb
|
46
62
|
- lib/speculations/version.rb
|
47
63
|
homepage: https://github.com/robertdober/speculate
|
48
64
|
licenses:
|
49
65
|
- Apache-2.0
|
50
66
|
metadata: {}
|
51
|
-
post_install_message:
|
67
|
+
post_install_message:
|
52
68
|
rdoc_options: []
|
53
69
|
require_paths:
|
54
70
|
- lib
|
@@ -63,8 +79,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
79
|
- !ruby/object:Gem::Version
|
64
80
|
version: '0'
|
65
81
|
requirements: []
|
66
|
-
rubygems_version: 3.
|
67
|
-
signing_key:
|
82
|
+
rubygems_version: 3.2.3
|
83
|
+
signing_key:
|
68
84
|
specification_version: 4
|
69
85
|
summary: Extract RSpecs from Markdown
|
70
86
|
test_files: []
|
@@ -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
|