lite-utils-md_code 0.0.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 +7 -0
- data/.rubocop.yml +88 -0
- data/Gemfile +13 -0
- data/README.md +94 -0
- data/lib/lite/utils/md_code/error.rb +9 -0
- data/lib/lite/utils/md_code/extraction.rb +42 -0
- data/lib/lite/utils/md_code/instance.rb +47 -0
- data/lib/lite/utils/md_code/version.rb +9 -0
- data/lib/lite/utils/md_code.rb +16 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d4f6dae34cdd94c22ab7eec0c7f639b844a5c665c3ceee865117609a65024602
|
|
4
|
+
data.tar.gz: 0350753f46a05fa6ac07d0c5a0a4d29bbfccc13fe0be3d282e571536f5560c29
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d5680075287f717edd30aac0e2ea468d40269330ccd9d0e914d9c720a80ec6dd10f52438b08e2ca2f72cdf7a75e23c92381869fbe386a298a50e430e571d31d5
|
|
7
|
+
data.tar.gz: 3f9fa8897dd8e3ff95985b83979cbb3785eca986237bc75a8da563237565e12786ac4f269056cd58a10b7d55c7b38aad8d87b19f5ca08d37852297cbf293220a
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rspec
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
NewCops: enable
|
|
6
|
+
|
|
7
|
+
Layout/EndAlignment:
|
|
8
|
+
EnforcedStyleAlignWith: start_of_line
|
|
9
|
+
|
|
10
|
+
Layout/LineLength:
|
|
11
|
+
Exclude:
|
|
12
|
+
- spec/**/*.rb
|
|
13
|
+
|
|
14
|
+
Lint/BooleanSymbol:
|
|
15
|
+
Enabled: false
|
|
16
|
+
|
|
17
|
+
Lint/ConstantDefinitionInBlock:
|
|
18
|
+
Exclude:
|
|
19
|
+
- spec/**/*.rb
|
|
20
|
+
|
|
21
|
+
Lint/ShadowingOuterLocalVariable:
|
|
22
|
+
Enabled: false
|
|
23
|
+
|
|
24
|
+
Metrics/BlockLength:
|
|
25
|
+
Exclude:
|
|
26
|
+
- spec/**/*.rb
|
|
27
|
+
|
|
28
|
+
Metrics/MethodLength:
|
|
29
|
+
Max: 20
|
|
30
|
+
|
|
31
|
+
Metrics/ModuleLength:
|
|
32
|
+
Max: 200
|
|
33
|
+
|
|
34
|
+
Naming/BinaryOperatorParameterName:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
37
|
+
Naming/BlockForwarding:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Naming/PredicateMethod:
|
|
41
|
+
Enabled: false
|
|
42
|
+
|
|
43
|
+
Naming/VariableNumber:
|
|
44
|
+
Exclude:
|
|
45
|
+
- spec/**/*.rb
|
|
46
|
+
|
|
47
|
+
RSpec/ExampleLength:
|
|
48
|
+
Max: 10
|
|
49
|
+
|
|
50
|
+
RSpec/IndexedLet:
|
|
51
|
+
Enabled: false
|
|
52
|
+
|
|
53
|
+
RSpec/MultipleMemoizedHelpers:
|
|
54
|
+
Enabled: false
|
|
55
|
+
|
|
56
|
+
Style/ArgumentsForwarding:
|
|
57
|
+
Enabled: false
|
|
58
|
+
|
|
59
|
+
Style/Documentation:
|
|
60
|
+
Enabled: false
|
|
61
|
+
|
|
62
|
+
Style/DocumentDynamicEvalDefinition:
|
|
63
|
+
Enabled: false
|
|
64
|
+
|
|
65
|
+
Style/EachWithObject:
|
|
66
|
+
Enabled: false
|
|
67
|
+
|
|
68
|
+
Style/HashConversion:
|
|
69
|
+
Enabled: false
|
|
70
|
+
|
|
71
|
+
Style/HashSyntax:
|
|
72
|
+
Enabled: false
|
|
73
|
+
|
|
74
|
+
Style/MethodCallWithoutArgsParentheses:
|
|
75
|
+
Enabled: false
|
|
76
|
+
|
|
77
|
+
Style/MultilineBlockChain:
|
|
78
|
+
Enabled: false
|
|
79
|
+
|
|
80
|
+
Style/RedundantArrayConstructor:
|
|
81
|
+
Enabled: false
|
|
82
|
+
|
|
83
|
+
Style/RedundantSelfAssignment:
|
|
84
|
+
Enabled: false
|
|
85
|
+
|
|
86
|
+
Style/TrailingUnderscoreVariable:
|
|
87
|
+
Enabled: false
|
|
88
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# lite-utils-md_code
|
|
2
|
+
Extracts runnable code inside MD fences,
|
|
3
|
+
tagged with an identifier, so you can `eval`
|
|
4
|
+
them from your test suite. The main use case:
|
|
5
|
+
keeping documentation examples honest by actually
|
|
6
|
+
running them as specs.
|
|
7
|
+
|
|
8
|
+
## Writing the documentation
|
|
9
|
+
Write runnable code in the MD file and mark it
|
|
10
|
+
with a tag and a unique identifier. The
|
|
11
|
+
fenced block looks somewhat like this in raw MD:
|
|
12
|
+
````ruby test rspec_example
|
|
13
|
+
```ruby test rspec_example
|
|
14
|
+
expect(true).to be(true)
|
|
15
|
+
```
|
|
16
|
+
````
|
|
17
|
+
|
|
18
|
+
The info string after the opening triple-backtick follows a fixed
|
|
19
|
+
grammar: `<language> <tag> <key>`, space-separated.
|
|
20
|
+
|
|
21
|
+
- `language` – the language identifier used for syntax highlighting
|
|
22
|
+
(e.g. `ruby`). Not interpreted by this library.
|
|
23
|
+
- `tag` – groups snippets so they can be filtered together. Matches
|
|
24
|
+
the `tag:` keyword passed to `MdCode.instance`.
|
|
25
|
+
- `key` – an identifier for this specific snippet, used later
|
|
26
|
+
to reference it via `SPECS.snippet!(:key)`. Must be unique
|
|
27
|
+
in a tag.
|
|
28
|
+
|
|
29
|
+
So ` ```ruby test unique_identifier ` declares a Ruby snippet, tagged
|
|
30
|
+
`test`, retrievable under the key `unique_identifier`.
|
|
31
|
+
|
|
32
|
+
## Writing the tests
|
|
33
|
+
Load the snippets somewhere in your specs. The positional
|
|
34
|
+
argument is the path to the file containing code snippets,
|
|
35
|
+
optional `tag` keyword specifies the tag used to filter
|
|
36
|
+
them – defaults to `:test`. Another
|
|
37
|
+
optional keyword is `dir`, which can be used to pass over
|
|
38
|
+
`__dir__` from the caller's location.
|
|
39
|
+
```ruby test extract_snippets
|
|
40
|
+
SPECS = MdCode.instance('../../../README.md', dir: __dir__)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Configure RSpec so that it ensures all snippets have been consumed
|
|
44
|
+
```ruby test rspec_config
|
|
45
|
+
RSpec.configure do |config|
|
|
46
|
+
config.add_setting :readme_spec, default: false
|
|
47
|
+
|
|
48
|
+
config.define_derived_metadata(file_path: %r{spec/utils/md_code}) do |meta|
|
|
49
|
+
config.readme_spec = true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
config.after(:suite) do
|
|
53
|
+
next unless config.readme_spec
|
|
54
|
+
|
|
55
|
+
SPECS.ensure_consumed!
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You may need a more complex configuration depending on
|
|
61
|
+
how you structure your specs; however, the setup shown
|
|
62
|
+
above works even with complex readme specs spreading
|
|
63
|
+
across multiple files.
|
|
64
|
+
|
|
65
|
+
Eval the snippets inside your specs – either as a part of the context
|
|
66
|
+
or inside your examples. Note the bang at the end of the method name –
|
|
67
|
+
this method tracks usage of individual keys and raises if it was
|
|
68
|
+
called twice with the same key. This makes it easier to avoid mistakes
|
|
69
|
+
where your test evals some key by mistake and omits another,
|
|
70
|
+
making sure your whole documentation is tested as intended.
|
|
71
|
+
```ruby test rspec_eval
|
|
72
|
+
it 'is true' do
|
|
73
|
+
eval(SPECS.snippet!(:rspec_example))
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Obviously the bang variant is meant for snippets that run
|
|
78
|
+
exactly once across the whole suite. When the snippet
|
|
79
|
+
represents shared setup – like a memoized helper body –
|
|
80
|
+
it must be allowed to evaluate repeatedly. In such
|
|
81
|
+
case use `SPECS.snippet` without the bang. It still tracks
|
|
82
|
+
usage but doesn't raise when the key is accessed multiple times.
|
|
83
|
+
|
|
84
|
+
**A note on `eval`**: This library works by evaluating snippet contents
|
|
85
|
+
as Ruby source. That's inherent to the pattern – the whole point is
|
|
86
|
+
to run your documentation's example code.
|
|
87
|
+
|
|
88
|
+
Snippets only ever come from files that already live in your own
|
|
89
|
+
repository, and they're only evaluated inside your own test suite.
|
|
90
|
+
You're not pulling snippets from user input, a network request,
|
|
91
|
+
or any other untrusted source – just from docs you wrote yourself.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
This library is published under the MIT license
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'markly'
|
|
4
|
+
|
|
5
|
+
require_relative 'error'
|
|
6
|
+
|
|
7
|
+
module Lite
|
|
8
|
+
module Utils
|
|
9
|
+
module MdCode
|
|
10
|
+
module Extraction
|
|
11
|
+
def self.extract(path, dir, tag)
|
|
12
|
+
extract_each(Markly.parse(read(path, dir)), tag)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.extract_each(doc, tag)
|
|
16
|
+
specs = {}
|
|
17
|
+
regex = Regexp.new("^\\S+\\s+#{tag}\\s+(\\S+)$")
|
|
18
|
+
|
|
19
|
+
doc.walk do |node|
|
|
20
|
+
next unless node.type == :code_block
|
|
21
|
+
next unless (match = regex.match(node.fence_info))
|
|
22
|
+
|
|
23
|
+
key = match[1].to_sym
|
|
24
|
+
raise Error, "Duplicate #{tag} key: #{key}" if specs.key?(key)
|
|
25
|
+
|
|
26
|
+
specs.store(key, node.string_content.freeze)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
specs.freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.read(path, dir)
|
|
33
|
+
full_path(path, dir).read
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.full_path(path, dir)
|
|
37
|
+
dir ? Pathname.new(dir).join(path) : Pathname.new(path)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lite/data'
|
|
4
|
+
|
|
5
|
+
require_relative 'error'
|
|
6
|
+
|
|
7
|
+
module Lite
|
|
8
|
+
module Utils
|
|
9
|
+
module MdCode
|
|
10
|
+
class Instance
|
|
11
|
+
Lite::Data.define(self, args: %i[snippets usage])
|
|
12
|
+
|
|
13
|
+
def self.instance(snippets)
|
|
14
|
+
new snippets, {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(snippets, usage)
|
|
18
|
+
super(snippets.freeze, usage)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def snippet!(key)
|
|
22
|
+
consume key, true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def snippet(key)
|
|
26
|
+
consume key, false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def consume(key, deplete)
|
|
30
|
+
raise Error, "Snippet not defined: #{key}" unless snippets.key?(key)
|
|
31
|
+
raise Error, "Key already used: #{key}" if usage[key] == :depleted
|
|
32
|
+
|
|
33
|
+
case usage[key]
|
|
34
|
+
when nil, :consumed then usage[key] = deplete ? :depleted : :consumed
|
|
35
|
+
end
|
|
36
|
+
snippets[key]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ensure_consumed!
|
|
40
|
+
unconsumed = snippets.keys - usage.keys
|
|
41
|
+
|
|
42
|
+
raise Error, "Some snippets haven't been consumed: #{unconsumed.join(', ')}" unless unconsumed.empty?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'md_code/version'
|
|
4
|
+
require_relative 'md_code/extraction'
|
|
5
|
+
require_relative 'md_code/instance'
|
|
6
|
+
|
|
7
|
+
module Lite
|
|
8
|
+
module Utils
|
|
9
|
+
module MdCode
|
|
10
|
+
def self.instance(path, dir: nil, tag: :test)
|
|
11
|
+
snippets = Extraction.extract(path, dir, tag)
|
|
12
|
+
Instance.instance snippets
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lite-utils-md_code
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tomas Milsimer
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: lite-data
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: markly
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
description: |
|
|
42
|
+
Extracts runnable code inside MD fences, tagged with an identifier,
|
|
43
|
+
so you can `eval` them from your test suite.
|
|
44
|
+
email:
|
|
45
|
+
- tomas.milsimer@protonmail.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".rubocop.yml"
|
|
51
|
+
- Gemfile
|
|
52
|
+
- README.md
|
|
53
|
+
- lib/lite/utils/md_code.rb
|
|
54
|
+
- lib/lite/utils/md_code/error.rb
|
|
55
|
+
- lib/lite/utils/md_code/extraction.rb
|
|
56
|
+
- lib/lite/utils/md_code/instance.rb
|
|
57
|
+
- lib/lite/utils/md_code/version.rb
|
|
58
|
+
homepage: https://github.com/lame-impala/lite-utils-md_code
|
|
59
|
+
licenses:
|
|
60
|
+
- MIT
|
|
61
|
+
metadata:
|
|
62
|
+
rubygems_mfa_required: 'true'
|
|
63
|
+
allowed_push_host: https://rubygems.org
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 3.0.0
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 3.5.22
|
|
80
|
+
signing_key:
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Connects a MD file to your code through MD fencing
|
|
83
|
+
test_files: []
|