eceval 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/exe/eceval +6 -0
- data/lib/eceval.rb +137 -0
- data/lib/eceval/cli.rb +43 -0
- data/lib/eceval/version.rb +3 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8eac2f4e105256977567c95878f0cdef4ad50ff89cb727de06f0abc9eed491de
|
4
|
+
data.tar.gz: 89526bf8ea8c2d04049d0f25db39d7f1071be0fb787280a34f3c483da64f624f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bb608743fec859e597016a9a71b253fbbc4c6826bc56423dff73fe179bfd2aa171b1ae2af47273e40c68dc3f8b66a6f2b301b3131fe3c1fd1a11d2b078382f8b
|
7
|
+
data.tar.gz: 6cc8a35647e048b2a27667beb302942e456910c992447bdf26a60ae7f7bea2026c4a0d1c7a437f43fe5ccd1a836905e3f7253f6de460e364fb014ff73034b63a
|
data/exe/eceval
ADDED
data/lib/eceval.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ECEVAL_MAIN_BINDING = binding
|
4
|
+
|
5
|
+
module Eceval
|
6
|
+
EVAL_MARKER = "#=>"
|
7
|
+
EVAL_EXCEPTION_MARKER = "#=> !!!"
|
8
|
+
BEGIN_CODE_BLOCK = '```ruby'
|
9
|
+
END_CODE_BLOCK = '```'
|
10
|
+
NEW_SCOPE_DIRECTIVE = '# eceval: new_scope'
|
11
|
+
|
12
|
+
NoExceptionRaised = Class.new(RuntimeError)
|
13
|
+
|
14
|
+
def self.augment(io, out: $stdout, filename: "[String]", lineno: 1)
|
15
|
+
context = EvaluationContext.new(filename: filename, lineno: lineno)
|
16
|
+
loop do
|
17
|
+
break if io.eof?
|
18
|
+
line = io.gets
|
19
|
+
out.puts(context.process_line(line.chomp))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class EvaluationContext
|
24
|
+
attr_reader :filename, :lineno
|
25
|
+
|
26
|
+
def initialize(filename:, lineno: 1)
|
27
|
+
@filename = filename
|
28
|
+
@lineno = lineno
|
29
|
+
@lines_consumed = 0
|
30
|
+
@chunk = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_line(line)
|
34
|
+
if @chunk
|
35
|
+
process_code_line(line)
|
36
|
+
else
|
37
|
+
process_noncode_line(line)
|
38
|
+
end
|
39
|
+
ensure
|
40
|
+
@lines_consumed += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def process_noncode_line(line)
|
46
|
+
begin_chunk if line.strip == BEGIN_CODE_BLOCK
|
47
|
+
line
|
48
|
+
end
|
49
|
+
|
50
|
+
def begin_chunk
|
51
|
+
raise "Already chunkin" if @chunk
|
52
|
+
|
53
|
+
@chunk = Chunk.new(
|
54
|
+
filename: filename,
|
55
|
+
lineno: lineno + @lines_consumed + 1, # starts on next line
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_code_line(line)
|
60
|
+
if line.strip == END_CODE_BLOCK
|
61
|
+
consume_chunk
|
62
|
+
line
|
63
|
+
elsif line.strip == NEW_SCOPE_DIRECTIVE
|
64
|
+
reset_scope
|
65
|
+
line
|
66
|
+
else
|
67
|
+
process_chunk_line(line)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset_scope
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_chunk_line(line)
|
75
|
+
@chunk << line
|
76
|
+
|
77
|
+
if line.rstrip.end_with?(EVAL_MARKER)
|
78
|
+
result = consume_chunk
|
79
|
+
begin_chunk
|
80
|
+
line.rstrip + ' ' + result.inspect
|
81
|
+
elsif line.rstrip.end_with?(EVAL_EXCEPTION_MARKER)
|
82
|
+
ex = consume_chunk(rescue_exceptions: true)
|
83
|
+
begin_chunk
|
84
|
+
line.rstrip + ' ' + format_exception(ex)
|
85
|
+
else
|
86
|
+
line
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def consume_chunk(rescue_exceptions: false)
|
91
|
+
old_chunk = @chunk
|
92
|
+
@chunk = nil
|
93
|
+
|
94
|
+
begin
|
95
|
+
old_chunk.evaluate
|
96
|
+
rescue Exception => ex
|
97
|
+
if rescue_exceptions
|
98
|
+
return ex
|
99
|
+
else
|
100
|
+
raise
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def format_exception(ex)
|
106
|
+
unless ex.is_a?(Exception)
|
107
|
+
raise NoExceptionRaised, "Expected an exception at #{current_pos}" \
|
108
|
+
" but none was raised. Instead, the code evaluated to: " +
|
109
|
+
ex.inspect
|
110
|
+
end
|
111
|
+
|
112
|
+
"#{ex.class}: #{ex.message}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def current_pos
|
116
|
+
"`#{filename}:#{lineno + @lines_consumed}`"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Chunk
|
121
|
+
attr_reader :filename, :lineno
|
122
|
+
|
123
|
+
def initialize(filename:, lineno:)
|
124
|
+
@filename = filename
|
125
|
+
@lineno = lineno
|
126
|
+
@buffered_lines = []
|
127
|
+
end
|
128
|
+
|
129
|
+
def <<(line)
|
130
|
+
@buffered_lines << line
|
131
|
+
end
|
132
|
+
|
133
|
+
def evaluate
|
134
|
+
ECEVAL_MAIN_BINDING.eval(@buffered_lines.join("\n"), filename, lineno)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/eceval/cli.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require_relative '../eceval'
|
3
|
+
|
4
|
+
module Eceval::CLI
|
5
|
+
def self.run(argv: ARGV, out: $stdout, err: $stderr)
|
6
|
+
old_out = $stdout
|
7
|
+
old_err = $stderr
|
8
|
+
|
9
|
+
begin
|
10
|
+
$stdout = out
|
11
|
+
$stderr = err
|
12
|
+
Dry::CLI.new(Command).call(arguments: argv, out: out, err: err)
|
13
|
+
ensure
|
14
|
+
$stdout = old_out
|
15
|
+
$stderr = old_err
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Command < Dry::CLI::Command
|
20
|
+
desc "Outputs the given markdown file after augmenting the code blocks with the results of evaluation"
|
21
|
+
argument :path, type: :string, required: true, desc: "The path the to markdown file. Reads from STDIN if the path is '-'."
|
22
|
+
option :require, type: :array, default: [], desc: "Libraries to load using `require`"
|
23
|
+
option :load_path, type: :array, default: [], desc: "Paths to add to $LOAD_PATH"
|
24
|
+
|
25
|
+
example [
|
26
|
+
"path/to/my_file.md",
|
27
|
+
"--load_path=lib --require=mygem README.md # load the gem in this repo before evaluation",
|
28
|
+
]
|
29
|
+
|
30
|
+
def call(path:, require:, load_path:, **)
|
31
|
+
$LOAD_PATH.unshift(*load_path)
|
32
|
+
require.each { |lib| require lib }
|
33
|
+
|
34
|
+
if path == '-'
|
35
|
+
Eceval.augment($stdin, filename: "[STDIN]")
|
36
|
+
else
|
37
|
+
File.open(path) do |file|
|
38
|
+
Eceval.augment(file, filename: path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eceval
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Dalling
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-08-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-cli
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: test_bench
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: super_diff
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: gem-release
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email: tom@tomdalling.com
|
85
|
+
executables:
|
86
|
+
- eceval
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- exe/eceval
|
91
|
+
- lib/eceval.rb
|
92
|
+
- lib/eceval/cli.rb
|
93
|
+
- lib/eceval/version.rb
|
94
|
+
homepage: https://github.com/tomdalling/eceval
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubygems_version: 3.1.2
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: Evaluates Ruby code embedded in markdown, merging results
|
117
|
+
test_files: []
|