doc_rspec 0.1.5 → 0.2.1

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: 2bd030fdeb4bdb9db6f545adaaeaf107e1d9ee38cceb00d7f194f0068b7ed40b
4
- data.tar.gz: d2e2a7ffb00c7f7d871b1de384b61c5c03de5b6e9c23ebdde3263c78273e5335
3
+ metadata.gz: 2fb556df1f9e8f00edb81eb6fc6db4c0d86d7864dbaedda0a9e559625df7fe98
4
+ data.tar.gz: 69296b360896f5a2258884bc59dc26b115648b107242f8f877eecca507b3b371
5
5
  SHA512:
6
- metadata.gz: 9ac8f0128cc6317a4772f9af81255382725250d33aaf2511e89a46de5a64170713106dce69cc730555c0c912ca517ba1fb713eab26edd0252a9f74985fdb2d1d
7
- data.tar.gz: 1bdb201bddfee20018855b99f074e683d952f5f67e94f955d32d941f77594cec847baf4f95d630f7f678a64a7526266609c3a9fa27f4f01f68565522a17b8ac6
6
+ metadata.gz: 641237f09a349ea510b93a562d3548e8398409af64c58f03be21499ce42eee8053f0b2fa4d596befdb6ac035747ba761f3e6e494de5c317966554756c74921cc
7
+ data.tar.gz: b7100cb9cf8d8b016c4466409fd4a608d569f022e62e20a30bacf90031e65f7047fad2d7d3e758504db4fb64d732a072c4cbb1a8fef853a40e8588a4a17b0230
@@ -30,15 +30,13 @@ class DocRSpec
30
30
  def compile_example(example_spec)
31
31
  # See comment above for an explanation why we capture
32
32
  # this method as a function into a closure
33
- compile_example_line = method(:compile_example_line)
34
33
  example_group.it example_spec.it_name(path) do
35
34
  example = self
36
- code_lines = example_spec
35
+ code = example_spec
37
36
  .lines
38
- .map { compile_example_line.(it, example) }
39
- .compact
37
+ .join("\n")
40
38
 
41
- eval(code_lines.join("\n")) unless code_lines.empty?
39
+ eval(code) unless code.empty?
42
40
  end
43
41
  end
44
42
 
@@ -1,21 +1,23 @@
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
 
8
- def add(op, lhs, rhs)
9
- lines << [op, lhs, rhs].compact
8
+ def add(code)
9
+ lines << code
10
10
  end
11
11
 
12
12
  def it_name(path)
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(code)
14
+ examples.last.add(code)
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,21 @@
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
13
- EQUALS_OP = %r{\s+ => \s+}x
14
- EXAMPLE_LINE = %r{\A \s* \#+ \s+ (.*)}x
11
+ CONTEXT_DEFINITION = %r{\A \s* \# \s ={1,7} \s (.*)}x
15
12
 
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
13
+ EXAMPLE_LINE = %r{\A \s* \# \s{4,} (.*)}x
14
+
15
+ SHORT_EQUALS = %r{\s eq\! \s}x
16
+ SHORT_MATCHES = %r{\s \= \~ \s}x
17
+ SHORT_PREDICATE = %r{\s is\! \s}x
18
+ START_EXAMPLE = %r{\A \s* \# \s{4,} \# \s example: \s (.*)}x
20
19
 
21
20
  attr_reader :ast, :lines, :state
22
21
 
@@ -24,26 +23,51 @@ class DocRSpec
24
23
  lines.each_with_index do |line, lnb|
25
24
  parse_line(line, lnb.succ)
26
25
  end
27
- ast
26
+ ast.reject(&:empty?)
28
27
  end
29
28
 
30
29
  private
31
30
  def initialize(lines)
32
- @ast = []
31
+ @ast = [Context.new(0)]
33
32
  @state = :outside
34
33
  @lines = lines
35
34
  end
36
35
 
37
- def parse_example_line(line, lnb)
38
- EXAMPLE_LINE.match(line) => [code]
36
+ def add_example(lnb, match)
37
+ name = match[1]
38
+ current_context.add_example(lnb, name:)
39
+ @state = :inside
40
+ end
41
+
42
+ def current_context = ast.last
43
+
44
+ def maybe_new_context(lnb, match)
45
+ name = match[1]
46
+ ast.pop if current_context.empty?
47
+ ast << Context.new(lnb, name:)
48
+ @state = :outside
49
+ end
50
+
51
+ def parse_example_line(match)
52
+ code = match[1]
53
+ return if code.empty?
39
54
 
40
- case code.split(EQUALS_OP)
55
+ case code.split(SHORT_EQUALS)
41
56
  in [lhs, rhs]
42
- ast.last.add_example_line(:eq, lhs, rhs)
57
+ ast.last.add_example_line("expect(#{lhs}).to eq(#{rhs})")
43
58
  else
44
- ast.last.add_example_line(:verb, line.gsub(COMMENT, ''))
59
+ case code.split(SHORT_MATCHES)
60
+ in [lhs, rhs]
61
+ ast.last.add_example_line("expect(#{lhs}).to match(#{rhs})")
62
+ else
63
+ case code.split(SHORT_PREDICATE)
64
+ in [lhs, rhs]
65
+ ast.last.add_example_line("expect(#{lhs}).to be_#{rhs}")
66
+ else
67
+ ast.last.add_example_line(code)
68
+ end
69
+ end
45
70
  end
46
-
47
71
  end
48
72
 
49
73
  def parse_line(line, lnb)
@@ -51,45 +75,32 @@ class DocRSpec
51
75
  case state
52
76
  when :outside
53
77
  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)
78
+ when :inside
79
+ parse_line_inside(line, lnb)
58
80
  else
59
81
  raise InternalError, "unexpected state :#{state} (in line: #{line} at #{lnb}"
60
82
  end
61
83
  end
62
84
 
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)
85
+ def parse_line_inside(line, lnb)
78
86
  case line
87
+ when CONTEXT_DEFINITION
88
+ maybe_new_context(lnb, Regexp.last_match)
79
89
  when START_EXAMPLE
80
- ast.last.add_example(lnb, Regexp.last_match)
81
- @state = :inside_example
90
+ add_example(lnb, Regexp.last_match)
91
+ when EXAMPLE_LINE
92
+ parse_example_line(Regexp.last_match)
82
93
  end
83
94
  end
84
95
 
85
96
  def parse_line_outside(line, lnb)
86
97
  case line
87
- when START_RDOC_COMMENT
88
- ast << Ast.new(lnb)
89
- @state = :inside_rdoc
98
+ when CONTEXT_DEFINITION
99
+ maybe_new_context(lnb, Regexp.last_match)
100
+ when START_EXAMPLE
101
+ add_example(lnb, Regexp.last_match)
90
102
  end
91
103
  end
92
-
93
104
  end
94
105
  end
95
106
  # 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.5'
5
+ VERSION = '0.2.1'
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,88 @@
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
+ # = Code
11
11
  #
12
- # Install the gem `gem install doc_rspec` or put `doc_rspec` in your Gemfile
12
+ # can be found [at Codeberg](https://codeberg.org/lab419/doc_rspec)
13
+ #
14
+ # = Usage
15
+ #
16
+ # Install the gem <tt>gem install doc_rspec</tt> or put +doc_rspec+ in your Gemfile
13
17
  # or gemspec file.
14
18
  #
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
19
+ # Then <tt>require doc_rspec</tt> in the file you want to use the +docspec+ macro
20
+ # or put it into your +spec_helper.rb+ or use any advanced require strategy
17
21
  # you are used to.
18
22
  #
19
- # # Abstract
23
+ # Inside your +RSpec+ file, at the example group level then call
24
+ #
25
+ # docspec '<path_to_file>'
26
+ #
27
+ # Where +path_to_file+ is relative to the +lib+ directory
28
+ #
29
+ # = Abstract
30
+ #
31
+ # Ruby Codeblocks that start with a comment line as follows
32
+ # # example: example_name
33
+ #
34
+ # are considered an +RSpec+ example of the +RSpec+ example group +doctest+ has been called.
35
+ #
36
+ # = RSpec Examples
37
+ #
38
+ # == Just plain old Ruby Code
39
+ #
40
+ # # example: just an RSpec example
41
+ # expect(2*21).to eq(42)
42
+ #
43
+ # = Implementation Notice
44
+ #
45
+ # # example: We have access to the wrapping context
20
46
  #
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.
47
+ # this_is_available(22) eq! 44
24
48
  #
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.
49
+ # This implies two thing
28
50
  #
29
- # ## Simplified exceptions in examples
51
+ # - you can also write +RSpec+ examples inside your code (inside comments) which are not part of rdoc
52
+ # - you can use (although that would be missleading) an example like syntax in your +RDoc+ comments
53
+ # by spacing the ruby code block with only two spaces.
30
54
  #
31
- # ```spec
32
- # 41 + 1 => 42
33
- # ```
55
+ # The context is taken from the last headline
56
+ # == First Examples
34
57
  #
35
- # but can also fail (and therefore this example is not marked with `spec`)
58
+ # # example: equality
59
+ # expect(41 + 1).to eq(42)
36
60
  #
37
- # ```not-spec
38
- # 41 => 42
39
- # ```
61
+ # == Shortcuts
40
62
  #
41
- # We have acces to the environment of the RSpec example
42
- # group we call `docspec` in.
63
+ # In addition to _standard_ +RSpec+ code, one can use 3 shorthand forms that are compiled as follows
43
64
  #
44
- # ```spec
45
- # this_is_available(21) => 42
46
- # ```
65
+ # === +eq!+
47
66
  #
48
- # Morever we will adapt the `to eq` matcher to the `to match` matcher
49
- # if the expression right to `=>` evaluates to a `Regexp`.
67
+ # # example: eq! shorthand for eq
50
68
  #
51
- # ```spec
52
- # DocRSpec::Version::VERSION => %r{\d+\.\d+\.\d+}
53
- # ```
69
+ # answer = 42
54
70
  #
55
- # ## Just plain old RSpec...
71
+ # answer eq! 42
56
72
  #
57
- # is available for all lines that do not contain a `=>` string.
73
+ # this was compiled to <tt>expect(answer).to eq(42)</tt>
58
74
  #
59
- # ```spec
60
- # expect(42).to be > 41
61
- # ```
62
- # ## Naming Examples
75
+ # === +=~+ for match
63
76
  #
64
- # Without naming examples the output when running a doctest with
65
- # `formatter=:doc` looks like this:
77
+ # # example: =~ shorthand for match
66
78
  #
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)
79
+ # "alpha" =~ /\Aa.+a\z/
72
80
  #
73
- # We can improve on this by giving an example a name with the ` ```spec # <name>` syntax
81
+ # === +is!+ for be_
74
82
  #
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:
83
+ # # example: =~ shorthand for be
81
84
  #
82
- # DocRSpec
83
- # example from rdoc (doc_rspec.rb:21)
84
- # ...
85
- # fourtytwo is the answer (doc_rspec.rb:65)
85
+ # require 'ostruct'
86
+ # OpenStruct.new(ok?: true) is! ok
86
87
  #
87
88
  class DocRSpec
88
89
 
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.5
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
@@ -37,25 +37,22 @@ 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: []
46
44
  extra_rdoc_files: []
47
45
  files:
48
- - ".rdoc_options"
49
46
  - LICENSE
50
47
  - README.md
51
48
  - lib/doc_rspec.rb
52
- - lib/doc_rspec/ast.rb
53
- - lib/doc_rspec/ast/example.rb
54
49
  - lib/doc_rspec/compiler.rb
55
- - lib/doc_rspec/example_group.rb
50
+ - lib/doc_rspec/context.rb
51
+ - lib/doc_rspec/context/example.rb
56
52
  - lib/doc_rspec/parser.rb
53
+ - lib/doc_rspec/rspec_example_group.rb
57
54
  - lib/doc_rspec/version.rb
58
- homepage: https://codeberg.org/lab419/doc_spec
55
+ homepage: https://codeberg.org/lab419/doc_rspec
59
56
  licenses:
60
57
  - AGPL-3.0-or-later
61
58
  metadata: {}
@@ -73,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
70
  - !ruby/object:Gem::Version
74
71
  version: '0'
75
72
  requirements: []
76
- rubygems_version: 3.6.7
73
+ rubygems_version: 3.6.8
77
74
  specification_version: 4
78
- summary: '["Doc rspecs for Ruby"]'
75
+ summary: '["Extract RSpec Examples from RDocs formatted with RDoc::Markup (the default)"]'
79
76
  test_files: []
data/.rdoc_options DELETED
@@ -1,27 +0,0 @@
1
- ---
2
- encoding: UTF-8
3
- static_path: []
4
- rdoc_include: []
5
- page_dir:
6
- apply_default_exclude: true
7
- autolink_excluded_words: []
8
- charset: UTF-8
9
- class_module_path_prefix:
10
- embed_mixins: false
11
- exclude: []
12
- file_path_prefix:
13
- hyperlink_all: false
14
- line_numbers: false
15
- locale_dir: locale
16
- locale_name:
17
- main_page:
18
- markup: markdown
19
- output_decoration: true
20
- show_hash: false
21
- skip_tests: true
22
- tab_width: 8
23
- template_stylesheets: []
24
- title:
25
- visibility: :protected
26
- warn_missing_rdoc_ref: true
27
- webcvs:
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