eceval 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []