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.
@@ -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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/eceval/cli'
4
+
5
+ ARGV << '--help' if ARGV.empty?
6
+ Eceval::CLI.run
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Eceval
2
+ VERSION = '0.1'
3
+ 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: []