doc_rspec 0.1.2 → 0.2.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: 1ea386bcbd5e9be13a6eb83e7476d536fc4b15a565312c422dda406a1fe93d96
4
- data.tar.gz: b05c0db28f9d81c95dddb6f782c18ba8cce4bd6e6bdf6a208b2987b7378a856f
3
+ metadata.gz: 88e3a986df1d2228b1455a425584ffa80e6ca725961a5e86e2cf14eb7d600b0c
4
+ data.tar.gz: ccbe8af928c2762e281e776b451d7a14a981d8f28fef39ac2ceb72f447b297de
5
5
  SHA512:
6
- metadata.gz: 6faa80959f62b2a1a3a9e4147bc5506ec274fc64189b622d6efd3df0d5c817807636db842d7a116f2738ebeed4ac494ac98a4f52fcd6b179dffa4f9be337b322
7
- data.tar.gz: 37c7b059562e6cc58ba4ad5aa759a752c5cf94472e55260d80178a77b789082dfa23da37d4ecb5fb7e172a492b2b198e3106217e0b817f221588d8345e48d70c
6
+ metadata.gz: 9a1a4f71792902e0454f2e857471f0122f06c2cea648401cdee47e7c6e743fbcd52ea689771edd675d3d69a529c77b2a602b130bbb2246dce045a3e51a020df2
7
+ data.tar.gz: 0f51c159a2cdde070fc6339c4bf7c2fab713047d391b649f887337c24544ee039f5493df0e66d8172d228b49d630767e61d4a9bf470aed31da3518458970d346
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DocRSpec
4
- class Ast
4
+ class Context
5
5
  class Example
6
6
  attr_reader :lines, :lnb, :name
7
7
 
@@ -13,9 +13,11 @@ class DocRSpec
13
13
  "#{name || "example from rdoc"} (#{path}:#{lnb})"
14
14
  end
15
15
 
16
+ def empty? = lines.empty?
17
+
16
18
  private
17
- def initialize(lnb,match)
18
- @name = match[2] unless match[2].empty?
19
+ def initialize(lnb, name:)
20
+ @name = name
19
21
  @lnb = lnb
20
22
  @lines = []
21
23
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'context/example'
4
+ class DocRSpec
5
+ class Context
6
+ attr_reader :examples, :lnb, :name
7
+
8
+ def add_example(lnb, name:)
9
+ examples.pop if examples.last&.empty?
10
+ examples << Example.new(lnb, name:)
11
+ end
12
+
13
+ def add_example_line(op, lhs, rhs=nil)
14
+ examples.last.add(op, lhs, rhs)
15
+ end
16
+
17
+ def context_name(path)
18
+ "#{name || "docspec"} (#{path}:#{lnb})"
19
+ end
20
+
21
+ def empty? = examples.empty?
22
+
23
+ private
24
+ def initialize(lnb, name: nil)
25
+ @lnb = lnb
26
+ @examples = []
27
+ @name = name
28
+ end
29
+
30
+ end
31
+ end
32
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,22 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'ast'
3
+ require_relative 'context'
4
4
 
5
5
  class DocRSpec
6
6
  class Parser
7
7
  class InternalError < RuntimeError; end
8
8
  class SyntaxError < RuntimeError; end
9
9
 
10
- COMMENT = %r{\A \s* \# \s*}x
11
10
 
12
- EMPTY_LINE = %r{\A \s* \#+ \s* \z}x
11
+ CONTEXT_DEFINITION = %r{\A \s* \# \s ={1,7} \s (.*)}x
12
+
13
13
  EQUALS_OP = %r{\s+ => \s+}x
14
- EXAMPLE_LINE = %r{\A \s* \#+ \s+ (.*)}x
14
+ EXAMPLE_LINE = %r{\A \s* \# \s{4,} (.*)}x
15
15
 
16
- START_EXAMPLE = %r{\A \s* \#+ \s+ ```spec \s* (\# \s+)?(.*)?}x
17
- START_RDOC_COMMENT = %r{\A \s* \#\#}x
18
- STOP_EXAMPLE = %r{\A \s* \#+ \s+ ```}x
19
- STOP_RDOC_COMMENT = %r{\A \s* [^#]* \z}x
16
+ START_EXAMPLE = %r{\A \s* \# \s{4,} \# \s example: \s (.*)}x
20
17
 
21
18
  attr_reader :ast, :lines, :state
22
19
 
@@ -24,26 +21,40 @@ class DocRSpec
24
21
  lines.each_with_index do |line, lnb|
25
22
  parse_line(line, lnb.succ)
26
23
  end
27
- ast
24
+ ast.reject(&:empty?)
28
25
  end
29
26
 
30
27
  private
31
28
  def initialize(lines)
32
- @ast = []
29
+ @ast = [Context.new(0)]
33
30
  @state = :outside
34
31
  @lines = lines
35
32
  end
36
33
 
37
- def parse_example_line(line, lnb)
38
- EXAMPLE_LINE.match(line) => [code]
34
+ def add_example(lnb, match)
35
+ name = match[1]
36
+ current_context.add_example(lnb, name:)
37
+ @state = :inside
38
+ end
39
+
40
+ def current_context = ast.last
41
+
42
+ def maybe_new_context(lnb, match)
43
+ name = match[1]
44
+ ast.pop if current_context.empty?
45
+ ast << Context.new(lnb, name:)
46
+ @state = :outside
47
+ end
48
+
49
+ def parse_example_line(match)
50
+ code = match[1]
39
51
 
40
52
  case code.split(EQUALS_OP)
41
53
  in [lhs, rhs]
42
54
  ast.last.add_example_line(:eq, lhs, rhs)
43
55
  else
44
- ast.last.add_example_line(:verb, line.gsub(COMMENT, ''))
56
+ ast.last.add_example_line(:verb, code) unless code.empty?
45
57
  end
46
-
47
58
  end
48
59
 
49
60
  def parse_line(line, lnb)
@@ -51,45 +62,32 @@ class DocRSpec
51
62
  case state
52
63
  when :outside
53
64
  parse_line_outside(line, lnb)
54
- when :inside_rdoc
55
- parse_line_inside_rdoc(line, lnb)
56
- when :inside_example
57
- parse_line_inside_example(line, lnb)
65
+ when :inside
66
+ parse_line_inside(line, lnb)
58
67
  else
59
68
  raise InternalError, "unexpected state :#{state} (in line: #{line} at #{lnb}"
60
69
  end
61
70
  end
62
71
 
63
- def parse_line_inside_example(line, lnb)
64
- case line
65
- when EMPTY_LINE
66
- nil
67
- when STOP_EXAMPLE
68
- ast.last.close_example { raise SyntaxError, "Empty docspec example in line #{ast.last.examples.last.lnb}" }
69
- @state = :inside_rdoc
70
- when STOP_RDOC_COMMENT
71
- raise SyntaxError, "Missing end of ```spec (starting at #{ast.last.examples.last.lnb}) at end of rdoc comment at #{lnb}"
72
- else
73
- parse_example_line(line, lnb)
74
- end
75
- end
76
-
77
- def parse_line_inside_rdoc(line, lnb)
72
+ def parse_line_inside(line, lnb)
78
73
  case line
74
+ when CONTEXT_DEFINITION
75
+ maybe_new_context(lnb, Regexp.last_match)
79
76
  when START_EXAMPLE
80
- ast.last.add_example(lnb, Regexp.last_match)
81
- @state = :inside_example
77
+ add_example(lnb, Regexp.last_match)
78
+ when EXAMPLE_LINE
79
+ parse_example_line(Regexp.last_match)
82
80
  end
83
81
  end
84
82
 
85
83
  def parse_line_outside(line, lnb)
86
84
  case line
87
- when START_RDOC_COMMENT
88
- ast << Ast.new(lnb)
89
- @state = :inside_rdoc
85
+ when CONTEXT_DEFINITION
86
+ maybe_new_context(lnb, Regexp.last_match)
87
+ when START_EXAMPLE
88
+ add_example(lnb, Regexp.last_match)
90
89
  end
91
90
  end
92
-
93
91
  end
94
92
  end
95
93
  # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DocRSpec
4
- module ExampleGroup
4
+ module RSpecExampleGroup
5
5
  def docspec(path)
6
6
  DocRSpec.new(path, self).generate_specs
7
7
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  class DocRSpec
4
4
  module Version
5
- VERSION = '0.1.2'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: AGPL-3.0-or-later
data/lib/doc_rspec.rb CHANGED
@@ -2,87 +2,66 @@
2
2
 
3
3
  require 'rspec'
4
4
  require_relative 'doc_rspec/compiler'
5
- require_relative 'doc_rspec/example_group'
5
+ require_relative 'doc_rspec/rspec_example_group'
6
6
  require_relative 'doc_rspec/parser'
7
- RSpec.configure { it.extend DocRSpec::ExampleGroup }
7
+ RSpec.configure { it.extend DocRSpec::RSpecExampleGroup }
8
8
 
9
9
  ##
10
- # # Usage
10
+ # = Usage
11
11
  #
12
- # Install the gem `gem install doc_rspec` or put `doc_rspec` in your Gemfile
12
+ # Install the gem <tt>gem install doc_rspec</tt> or put +doc_rspec+ in your Gemfile
13
13
  # or gemspec file.
14
14
  #
15
- # Then `require doc_rspec` in the file you want to use the `docspec` macro
16
- # or put it into your `spec_helper.rb` or use any advanced require strategy
15
+ # Then <tt>require doc_rspec</tt> in the file you want to use the +docspec+ macro
16
+ # or put it into your +spec_helper.rb+ or use any advanced require strategy
17
17
  # you are used to.
18
18
  #
19
- # # Abstract
19
+ # Inside your +RSpec+ file, at the example group level then call
20
20
  #
21
- # DocRSpec allows us to call `doctest <sourcefile>` and the specs
22
- # between ` ```spec ` and ` ``` ` will be executed in the context
23
- # of the `RSpec` example group `doctest` has been called.
21
+ # # Usage example
22
+ # docspec '<path_to_file>'
24
23
  #
25
- # For each `rdoc` block an `ExampleGroup` is created and the lines
26
- # between ` ```spec ` and ` ``` ` will be executed in the context
27
- # of one example.
24
+ # Where +path_to_file+ is relative to the +lib+ directory
28
25
  #
29
- # ## Simplified exceptions in examples
26
+ # = Abstract
30
27
  #
31
- # ```spec
32
- # 41 + 1 => 42
33
- # ```
28
+ # Ruby Codeblocks that start with a comment line as follows
29
+ # # example: example_name
34
30
  #
35
- # but can also fail (and therefore this example is not marked with `spec`)
31
+ # are considered an +RSpec+ example of the +RSpec+ example group +doctest+ has been called.
36
32
  #
37
- # ```not-spec
38
- # 41 => 42
39
- # ```
33
+ # = RSpec Examples
40
34
  #
41
- # We have acces to the environment of the RSpec example
42
- # group we call `docspec` in.
35
+ # == Just plain old Ruby Code
43
36
  #
44
- # ```spec
45
- # this_is_available(21) => 42
46
- # ```
37
+ # # example: just an RSpec example
38
+ # expect(2*21).to eq(42)
47
39
  #
48
- # Morever we will adapt the `to eq` matcher to the `to match` matcher
49
- # if the expression right to `=>` evaluates to a `Regexp`.
40
+ # = Implementation Notice
50
41
  #
51
- # ```spec
52
- # DocRSpec::Version::VERSION => %r{\d+\.\d+\.\d+}
53
- # ```
42
+ # # example: We have access to the wrapping context
54
43
  #
55
- # ## Just plain old RSpec...
44
+ # this_is_available(22) => 44
56
45
  #
57
- # is available for all lines that do not contain a `=>` string.
46
+ # Although this gem is named +doc_rspec+ and its goal is certainly to document behavior
47
+ # of Ruby code inside the +RDoc+ documentation of that code, and _most_ _importantly_
48
+ # backing the claims made in this documentation up with *actual* +RSpec+ examples,
49
+ # in this _MVP_ the parser will simply generate code for all lines matching
50
+ # <tt>/\A \s* \# \s{3,}/x</tt> after a triggering line macthing
51
+ # <tt>/\A \s* \# \s{3,} \# \s example: \s (.+)/x</tt>
52
+ # and potentially ended with the aforementioned <tt>/\A \s* \# \s{3,} \# \s end \s of \s example/x</tt>
58
53
  #
59
- # ```spec
60
- # expect(42).to be > 41
61
- # ```
62
- # ## Naming Examples
54
+ # This implies two thing
63
55
  #
64
- # Without naming examples the output when running a doctest with
65
- # `formatter=:doc` looks like this:
56
+ # - you can also write +RSpec+ examples inside your code (inside comments) which are not part of rdoc
57
+ # - you can use (although that would be missleading) an example like syntax in your +RDoc+ comments
58
+ # by spacing the ruby code block with only two spaces.
66
59
  #
67
- # DocRSpec
68
- # example from rdoc (doc_rspec.rb:21)
69
- # example from rdoc (doc_rspec.rb:34)
70
- # example from rdoc (doc_rspec.rb:41)
71
- # example from rdoc (doc_rspec.rb:49)
60
+ # The context is taken from the last headline
61
+ # == First Examples
72
62
  #
73
- # We can improve on this by giving an example a name with the ` ```spec # <name>` syntax
74
- #
75
- # ```spec # fourtytwo is the answer
76
- # the_answer = 42
77
- # expect(42).to eq(the_answer)
78
- # ```
79
- #
80
- # Now the output looks like:
81
- #
82
- # DocRSpec
83
- # example from rdoc (doc_rspec.rb:21)
84
- # ...
85
- # fourtytwo is the answer (doc_rspec.rb:65)
63
+ # # example: equality
64
+ # expect(41 + 1).to eq(42)
86
65
  #
87
66
  class DocRSpec
88
67
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doc_rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
@@ -37,9 +37,7 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: 3.13.0
40
- description: 'Implementing docspecs à la Elixir''s doctests
41
-
42
- '
40
+ description: Extract RSpec Examples from RDocs formatted with RDoc::Markup (the default)
43
41
  email: robert.dober@gmail.com
44
42
  executables: []
45
43
  extensions: []
@@ -48,13 +46,13 @@ files:
48
46
  - LICENSE
49
47
  - README.md
50
48
  - lib/doc_rspec.rb
51
- - lib/doc_rspec/ast.rb
52
- - lib/doc_rspec/ast/example.rb
53
49
  - lib/doc_rspec/compiler.rb
54
- - lib/doc_rspec/example_group.rb
50
+ - lib/doc_rspec/context.rb
51
+ - lib/doc_rspec/context/example.rb
55
52
  - lib/doc_rspec/parser.rb
53
+ - lib/doc_rspec/rspec_example_group.rb
56
54
  - lib/doc_rspec/version.rb
57
- homepage: https://codeberg.org/lab419/doc_spec
55
+ homepage: https://codeberg.org/lab419/doc_rspec
58
56
  licenses:
59
57
  - AGPL-3.0-or-later
60
58
  metadata: {}
@@ -74,5 +72,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
72
  requirements: []
75
73
  rubygems_version: 3.6.7
76
74
  specification_version: 4
77
- summary: '["Doc rspecs for Ruby"]'
75
+ summary: '["Extract RSpec Examples from RDocs formatted with RDoc::Markup (the default)"]'
78
76
  test_files: []
data/lib/doc_rspec/ast.rb DELETED
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'ast/example'
4
- class DocRSpec
5
- class Ast
6
- attr_reader :examples, :lnb
7
-
8
- def add_example(lnb, match)
9
- examples << Example.new(lnb, match)
10
- end
11
-
12
- def add_example_line(op, lhs, rhs=nil)
13
- examples.last.add(op, lhs, rhs)
14
- end
15
-
16
- def close_example(&error_handler)
17
- return unless examples.last.lines.empty?
18
-
19
- error_handler.()
20
- end
21
-
22
- def context_name(path)
23
- "docspec #{path}:#{lnb}"
24
- end
25
-
26
- private
27
- def initialize(lnb)
28
- @lnb = lnb
29
- @examples = []
30
- end
31
-
32
- end
33
- end
34
- # SPDX-License-Identifier: AGPL-3.0-or-later