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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d08ccbaea3017274bdc86160387aeed915eba6aac5e5119cca48c2e8f4a53a3
4
- data.tar.gz: dd53be1c870c773d263a59b03204b469c593e0244df48ea19aaac1be87a45b5d
3
+ metadata.gz: ddf1c9fa5d8bb2d18f142e4fcc18aad6ca71d0694829b7bd0f7d291b0e3c97d2
4
+ data.tar.gz: '06689091cee69a4f3df817efc5ba8be2ed754d85fe98ed5cb9d27c28b58be506'
5
5
  SHA512:
6
- metadata.gz: 222b7eff1615cae7c4e3af5fbaffae7812e9e1144de7884b5a3b7d798e7647f9ea259dddf632e5262977d6a07da1234f22457c84eb656c225ba7745b1a317f49
7
- data.tar.gz: 70c8e9c20f41acc002bfdda9ae3fa6f63561538730989c63e25e0460a65785b21d5835c9f16efc4f41ccaf22a51b179a27bc77c42a7585afa0474d2e95a9dcb8
6
+ metadata.gz: c382d674930d2e61a8494de4331a4b4f3afef2cc6b77193fbb09af4610f3c8944f65edb09ff5ff550dfea5f56114692c1d93f37f04a88d30e91d014deba7b7f0
7
+ data.tar.gz: b29b76a402e49063919eeded1724c7ea2d87c557fba1ea6f4e526d9e9a322da746669d460d3effbc4146090f57559a4c77523464e9cc44f324172c0363e783f1
data/bin/speculate ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../lib/speculations/cli'
3
+
4
+ Speculations::CLI.run(ARGV)
@@ -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, :name, :parent
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:, line:, parent:, name: nil)
24
+ def initialize(lnb:, parent:, title:)
28
25
  @lnb = lnb
29
26
  @parent = parent
30
- @name = _compute_name(lnb: lnb, line: line, parent: parent, name: name)
27
+ @title = _compute_title(title)
31
28
  end
32
29
 
33
- def _compute_name(lnb:, parent:, line:, name:)
34
- _, name1 = NAMED_EXAMPLE.match(line).to_a
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 "#{name}" do}
35
+ %{it "#{title}" do}
47
36
  end
48
37
  end
@@ -12,7 +12,7 @@ class Speculations::Parser::Context::Include
12
12
  end
13
13
 
14
14
  def to_code
15
- parent.map_lines(lines)
15
+ parent.map_lines("# #{parent.filename}:#{lnb}", lines)
16
16
  end
17
17
 
18
18
  private
@@ -1,109 +1,120 @@
1
- class Speculations::Parser::Context
2
- require_relative './context/include'
3
- require_relative './context/example'
4
- require_relative './context/setup'
5
-
6
- attr_reader :alternate_syntax, :filename, :level, :lnb, :name, :orig_filename, :parent, :potential_name, :setup
7
-
8
- def add_child(name:, lnb:)
9
- raise "Illegal nesting" if parent
10
- children << self.class.new(alternate_syntax: alternate_syntax, name: name, lnb: lnb, parent: self)
11
- children.last
12
- end
13
-
14
- def add_example(lnb:, line:)
15
- examples << Example.new(lnb: lnb, parent: self, line: line, name: potential_name)
16
- reset_context
17
- examples.last
18
- end
19
-
20
- def add_include(lnb:)
21
- includes << Include.new(lnb: lnb, parent: self)
22
- @given = false
23
- includes.last
24
- end
25
-
26
- def children
27
- @__children__ ||= []
28
- end
29
-
30
- def examples
31
- @__examples__ ||= []
32
- end
33
-
34
- def given?
35
- @given
36
- end
37
-
38
- def includes
39
- @__includes__ ||= []
40
- end
41
-
42
- def map_lines(*lines, indent: 0)
43
- prefix = " " * (level + indent)
44
- lines.flatten.map{ |line| "#{prefix}#{line.strip}" }.join("\n")
45
- end
46
-
47
- def reset_context
48
- @potential_name = nil
49
- @given = false
50
- self
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 Bef extend self
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
- [:bef, node.add_line(line)]
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
- if node.alternate_syntax
8
- _parse_alternate line, lnb, node
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
- _parse_classical line, lnb, node
15
+ [:out, node]
11
16
  end
12
17
  end
13
18
 
14
19
  private
15
20
 
16
- def _parse_alternate line, lnb, node
17
- case
18
- when name = State.context_match(line)
19
- node = node.parent if node.parent
20
- new_node = node.add_child(name: name, lnb: lnb)
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/bef'
3
- require_relative './state/exa'
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
- 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
- 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
@@ -1,41 +1,39 @@
1
- require_relative '../speculations'
2
- class Speculations::Parser
3
- require_relative './parser/context'
4
- require_relative './parser/state'
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
- def self.parsers
9
- @__parsers__ ||= {
10
- bef: State::Bef,
11
- exa: State::Exa,
12
- inc: State::Inc,
13
- out: State::Out
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
- def parse_from_file file, orig_filename = nil, alternate_syntax: false
18
- @alternate_syntax = alternate_syntax
19
- @filename = file
20
- @orig_filename = orig_filename || file
21
- @input = File
22
- .new(file)
23
- .each_line(chomp: true)
24
- .lazy
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
- private
24
+ private
29
25
 
30
- def initialize
31
- @state = :out
32
- end
26
+ def initialize
27
+ @state = :out
28
+ end
33
29
 
34
- def parse!
35
- root = node = Context.new(alternate_syntax: alternate_syntax, name: "Speculations from #{@filename}", lnb: 0, filename: @filename, orig_filename: orig_filename, parent: nil)
36
- input.each_with_index do |line, lnb|
37
- @state, node = self.class.parsers.fetch(@state).parse(line, lnb.succ, node)
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,3 +1,3 @@
1
- module SpeculateAbout
2
- VERSION = "0.4.2"
1
+ module Speculations
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/speculations.rb CHANGED
@@ -1,2 +1,44 @@
1
- module Speculations
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.2
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: 2020-11-20 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'
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/bef.rb
43
- - lib/speculations/parser/state/exa.rb
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: 2.7.0
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.1.4
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