rbs 0.3.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +7 -1
  3. data/.gitignore +1 -1
  4. data/CHANGELOG.md +39 -0
  5. data/COPYING +1 -1
  6. data/Gemfile +16 -2
  7. data/README.md +87 -48
  8. data/Rakefile +54 -22
  9. data/bin/rbs-prof +9 -0
  10. data/bin/run_in_md.rb +49 -0
  11. data/bin/test_runner.rb +0 -2
  12. data/docs/sigs.md +6 -6
  13. data/docs/stdlib.md +3 -5
  14. data/docs/syntax.md +6 -3
  15. data/goodcheck.yml +65 -0
  16. data/lib/rbs.rb +3 -0
  17. data/lib/rbs/ast/declarations.rb +115 -14
  18. data/lib/rbs/ast/members.rb +41 -17
  19. data/lib/rbs/cli.rb +315 -122
  20. data/lib/rbs/constant.rb +4 -4
  21. data/lib/rbs/constant_table.rb +51 -45
  22. data/lib/rbs/definition.rb +175 -59
  23. data/lib/rbs/definition_builder.rb +802 -604
  24. data/lib/rbs/environment.rb +352 -210
  25. data/lib/rbs/environment_walker.rb +14 -23
  26. data/lib/rbs/errors.rb +184 -3
  27. data/lib/rbs/factory.rb +14 -0
  28. data/lib/rbs/parser.y +95 -27
  29. data/lib/rbs/prototype/rb.rb +119 -117
  30. data/lib/rbs/prototype/rbi.rb +5 -3
  31. data/lib/rbs/prototype/runtime.rb +34 -7
  32. data/lib/rbs/substitution.rb +12 -1
  33. data/lib/rbs/test.rb +82 -3
  34. data/lib/rbs/test/errors.rb +5 -1
  35. data/lib/rbs/test/hook.rb +133 -259
  36. data/lib/rbs/test/observer.rb +17 -0
  37. data/lib/rbs/test/setup.rb +35 -19
  38. data/lib/rbs/test/setup_helper.rb +29 -0
  39. data/lib/rbs/test/spy.rb +0 -321
  40. data/lib/rbs/test/tester.rb +116 -0
  41. data/lib/rbs/test/type_check.rb +43 -7
  42. data/lib/rbs/type_name_resolver.rb +58 -0
  43. data/lib/rbs/types.rb +94 -2
  44. data/lib/rbs/validator.rb +51 -0
  45. data/lib/rbs/variance_calculator.rb +12 -2
  46. data/lib/rbs/version.rb +1 -1
  47. data/lib/rbs/writer.rb +127 -91
  48. data/rbs.gemspec +0 -9
  49. data/schema/annotation.json +14 -0
  50. data/schema/comment.json +26 -0
  51. data/schema/decls.json +353 -0
  52. data/schema/function.json +87 -0
  53. data/schema/location.json +56 -0
  54. data/schema/members.json +248 -0
  55. data/schema/methodType.json +44 -0
  56. data/schema/types.json +299 -0
  57. data/stdlib/benchmark/benchmark.rbs +151 -151
  58. data/stdlib/builtin/encoding.rbs +2 -0
  59. data/stdlib/builtin/enumerable.rbs +4 -4
  60. data/stdlib/builtin/enumerator.rbs +3 -1
  61. data/stdlib/builtin/fiber.rbs +5 -1
  62. data/stdlib/builtin/file.rbs +0 -3
  63. data/stdlib/builtin/io.rbs +4 -4
  64. data/stdlib/builtin/proc.rbs +1 -2
  65. data/stdlib/builtin/symbol.rbs +1 -1
  66. data/stdlib/builtin/thread.rbs +2 -2
  67. data/stdlib/csv/csv.rbs +4 -6
  68. data/stdlib/fiber/fiber.rbs +117 -0
  69. data/stdlib/json/json.rbs +1 -1
  70. data/stdlib/logger/formatter.rbs +23 -0
  71. data/stdlib/logger/log_device.rbs +39 -0
  72. data/stdlib/logger/logger.rbs +507 -0
  73. data/stdlib/logger/period.rbs +7 -0
  74. data/stdlib/logger/severity.rbs +8 -0
  75. data/stdlib/mutex_m/mutex_m.rbs +77 -0
  76. data/stdlib/pathname/pathname.rbs +6 -6
  77. data/stdlib/prime/integer-extension.rbs +1 -1
  78. data/stdlib/prime/prime.rbs +44 -44
  79. data/stdlib/pty/pty.rbs +159 -0
  80. data/stdlib/tmpdir/tmpdir.rbs +1 -1
  81. metadata +28 -116
  82. data/lib/rbs/test/test_helper.rb +0 -183
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddacdf6b9e8b5297a7a5845aa715ffc9202fa42a6a81b492e40cbeeabbc4c311
4
- data.tar.gz: f79a70c89b49abc949dfb9d168bac9684d90036b4ef496c839ec24c20be4d972
3
+ metadata.gz: a2e86d5e87d89f97eed8e2674aa5f5035413179ba104a5cb265ab748268c51fe
4
+ data.tar.gz: 9d58fa00413d5a334496ee688bbc72520e1911558f39ad2d01b180b790e5d894
5
5
  SHA512:
6
- metadata.gz: e6beaa6a9cb76ff862057a2bc8be4260e804c3b2737ccf0d1811b0c2d5aa5aa35b3832c01d936de76ee5f2ef6d64b2182b9830dc2ae178d54971332360e190b4
7
- data.tar.gz: ce16ec8b1174d15f5f0acf6fc9285abede6554bb036252f5ad4efab162e8a1976b9eefa37f1136a2e6b6d18e5f11505204f7fe15edf4f3c9d64a72329c9db63a
6
+ metadata.gz: 328850c1f912cd7cc45e1c210140c098206f668c0400a817cde00e36dec1591ec235b5b6a1255b0e66d585745d4f6f7effeb09f28ae2a7536091852f9ba8bc89
7
+ data.tar.gz: 96bc5adc81b98d723a98d20dcbeef13424fb0a4267272ff1ad6daaab5ef310f24c038f4e23793253beade1e55a00a6eece457697a3a4ec62413f08b7c42ace99
@@ -24,5 +24,11 @@ jobs:
24
24
  ruby -v
25
25
  gem install bundler
26
26
  bin/setup
27
- bundle exec rake
27
+ bundle exec rake test
28
+ bundle exec rake stdlib_test
29
+ bundle exec rake rubocop
30
+ bundle exec rake validate
31
+ bundle exec rake test_doc
28
32
  bundle exec rake build
33
+ RBS_GENERATE_TEST_PATH=/tmp/Array_test.rb bundle exec rake 'generate:stdlib_test[Array]'
34
+ ruby -c /tmp/Array_test.rb
data/.gitignore CHANGED
@@ -5,8 +5,8 @@
5
5
  /pkg/
6
6
  /spec/reports/
7
7
  /tmp/
8
- /Gemfile.lock
9
8
  /.ruby-version
10
9
  /lib/rbs/parser.rb
11
10
  /lib/rbs/parser.output
12
11
  /vendor/sigs
12
+ /Gemfile.lock
@@ -2,6 +2,45 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.8.0 (2020-08-01)
6
+
7
+ * Signature updates for `Enumerator` and `PTY`
8
+ * Fix prototype rb/rbi error handling [#349](https://github.com/ruby/rbs/pull/349)
9
+ * Runtime test improvements [#344](https://github.com/ruby/rbs/pull/344), [#343](https://github.com/ruby/rbs/pull/343)
10
+ * Add `...` syntax [#342](https://github.com/ruby/rbs/pull/342)
11
+
12
+ ## 0.7.0 (2020-07-20)
13
+
14
+ * Add `DefinitionBuilder#one_instance_ancestors` and `DefinitionBuilder#one_singleton_ancestors` [#341](https://github.com/ruby/rbs/pull/341)
15
+ * Bug fix in ConstantTable [#340](https://github.com/ruby/rbs/pull/340)
16
+ * Make `rbs validate` faster [#338](https://github.com/ruby/rbs/pull/338)
17
+ * Dedup methods generated by `rbs prototype rb` [#334](https://github.com/ruby/rbs/pull/334)
18
+
19
+ ## 0.6.0 (2020-07-12)
20
+
21
+ * Signature update for `Logger`.
22
+ * Clean `Environment#inspect`. [#331](https://github.com/ruby/rbs/pull/331)
23
+ * Module self type syntax update. [#329](https://github.com/ruby/rbs/pull/329)
24
+ * Better validation. [#328](https://github.com/ruby/rbs/pull/328)
25
+ * Parser performance improvement. [#327](https://github.com/ruby/rbs/pull/327)
26
+ * Runtime type checking performance improvements with sampling [#323](https://github.com/ruby/rbs/pull/323)
27
+
28
+ ## 0.5.0 (2020-07-04)
29
+
30
+ * Signature updates for `Mutex_m`, `IO`, and `Enumerable`.
31
+ * Syntax update. [#307](https://github.com/ruby/rbs/pull/307)
32
+ * AST command prints _absolute_ type names with file name filtering. [#312](https://github.com/ruby/rbs/pull/312)
33
+ * Improve CLI message. [#309](https://github.com/ruby/rbs/pull/309)
34
+
35
+ ## 0.4.0 (2020-06-15)
36
+
37
+ * Signature update for `Fiber`, `Encoding`, and `Enumerator`.
38
+ * Fix syntax error for underscore and `!` `?` [#304](https://github.com/ruby/rbs/pull/304)
39
+ * Improved return type inference in `rbs prototype rb` [#303](https://github.com/ruby/rbs/pull/303)
40
+ * Skip anonymous modules/classes in `rbs prototype runtime` [#302](https://github.com/ruby/rbs/pull/302)
41
+ * Fix `--require-relative` option in `rbs prototype runtime` [#299](https://github.com/ruby/rbs/pull/299)
42
+ * Add JSON schema for `rbs ast` [#295](https://github.com/ruby/rbs/pull/295)
43
+
5
44
  ## 0.3.1 (2020-05-22)
6
45
 
7
46
  * Fix constant resolution again [#289](https://github.com/ruby/rbs/pull/289)
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- ruby-signature is copyrighted free software by Soutaro Matsumoto <matsumoto@soutaro.com>.
1
+ rbs is copyrighted free software by Soutaro Matsumoto <matsumoto@soutaro.com>.
2
2
  You can redistribute it and/or modify it under either the terms of the
3
3
  2-clause BSDL (see the file BSDL), or the conditions below:
4
4
 
data/Gemfile CHANGED
@@ -1,6 +1,20 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in ruby-signature.gemspec
3
+ # Specify your gem's dependencies in rbs.gemspec
4
4
  gemspec
5
5
 
6
- gem "ruby-signature-amber", path: "test/assets/test-gem"
6
+ # Development dependencies
7
+ gem "rake"
8
+ gem "minitest"
9
+ gem "racc"
10
+ gem "rubocop"
11
+ gem "rubocop-rubycw"
12
+ gem "minitest-reporters"
13
+ gem "json"
14
+ gem "json-schema"
15
+ gem 'stackprof'
16
+ gem "goodcheck"
17
+
18
+ # Test gems
19
+ gem "rbs-amber", path: "test/assets/test-gem"
20
+
data/README.md CHANGED
@@ -1,79 +1,118 @@
1
1
  # RBS
2
2
 
3
- RBS provides syntax and semantics definition for the `Ruby Signature` language, `.rbs` files.
4
- It consists of a parser, the syntax, and class definition interpreter, the semantics.
3
+ RBS is a language to describe the structure of Ruby programs.
4
+ You can write down the definition of a class or module: methods defined in the class, instance variables and their types, and inheritance/mix-in relations.
5
+ It also allows declaring constants and global variables.
5
6
 
6
- ## Build
7
+ The following is a small example of RBS for a chat app.
7
8
 
8
- We haven't published a gem yet.
9
- You need to install the dependencies, and build its parser with `bin/setup`.
9
+ <!-- run-start:a.rbs:bundle exec rbs -I a.rbs validate -->
10
+ ```rbs
11
+ module ChatApp
12
+ VERSION: String
10
13
 
11
- ```
12
- $ bin/setup
13
- $ bundle exec exe/rbs
14
- ```
14
+ class User
15
+ attr_reader login: String
16
+ attr_reader email: String
15
17
 
16
- ## Usage
18
+ def initialize: (login: String, email: String) -> void
19
+ end
17
20
 
18
- ```
19
- $ rbs list
20
- $ rbs ancestors ::Object
21
- $ rbs methods ::Object
22
- $ rbs method ::Object tap
23
- ```
21
+ class Bot
22
+ attr_reader name: String
23
+ attr_reader email: String
24
+ attr_reader owner: User
24
25
 
25
- ### rbs [--class|--module|interface] list
26
+ def initialize: (name: String, owner: User) -> void
27
+ end
26
28
 
27
- ```
28
- $ rbs list
29
- ```
29
+ class Message
30
+ attr_reader id: String
31
+ attr_reader string: String
32
+ attr_reader from: User | Bot # `|` means union types: `#from` can be `User` or `Bot`
33
+ attr_reader reply_to: Message? # `?` means optional type: `#reply_to` can be `nil`
30
34
 
31
- This command lists all of the classes/modules/interfaces defined in `.rbs` files.
35
+ def initialize: (from: User | Bot, string: String) -> void
32
36
 
33
- ### rbs ancestors [--singleton|--instance] CLASS
37
+ def reply: (from: User | Bot, string: String) -> Message
38
+ end
34
39
 
40
+ class Channel
41
+ attr_reader name: String
42
+ attr_reader messages: Array[Message]
43
+ attr_reader users: Array[User]
44
+ attr_reader bots: Array[Bot]
45
+
46
+ def initialize: (name: String) -> void
47
+
48
+ def each_member: () { (User | Bot) -> void } -> void # `{` and `}` means block.
49
+ | () -> Enumerable[User | Bot, void] # Method can be overloaded.
50
+ end
51
+ end
35
52
  ```
36
- $ rbs ancestors Array # ([].class.ancestors)
37
- $ rbs ancestors --singleton Array # (Array.class.ancestors)
38
- ```
53
+ <!-- run-end -->
39
54
 
40
- This command prints the _ancestors_ of the class.
41
- The name of the command is borrowed from `Class#ancestors`, but the semantics is a bit different.
42
- The `ancestors` command is more precise (I believe).
55
+ ## Installation
43
56
 
44
- ### rbs methods [--singleton|--instance] CLASS
57
+ Install the `rbs` gem. `$ gem install rbs` from the command line, or add a line in your `Gemfile`.
45
58
 
46
- ```
47
- $ rbs methods ::Integer # 1.methods
48
- $ rbs methods --singleton ::Object # Object.methods
59
+ ```rb
60
+ gem "rbs"
49
61
  ```
50
62
 
51
- This command prints all methods provided for the class.
63
+ ## CLI
52
64
 
53
- ### rbs method [--singleton|--instance] CLASS METHOD
65
+ The gem ships with the `rbs` command line tool to demonstrate what it can do and help develop RBS.
54
66
 
55
- ```
56
- $ rbs method ::Integer '+' # 1+2
57
- $ rbs method --singleton ::Object tap # Object.tap { ... }
67
+ ```bash
68
+ $ rbs version
69
+ $ rbs list
70
+ $ rbs ancestors ::Object
71
+ $ rbs methods ::Object
72
+ $ rbs method Object then
58
73
  ```
59
74
 
60
- This command prints type and properties of the method.
75
+ ## Library
61
76
 
62
- ### Options
77
+ There are two important concepts, _environment_ and _definition_.
63
78
 
64
- It accepts two global options, `-r` and `-I`.
79
+ An _environment_ is a dictionary that keeps track of all declarations. What is the declaration associated with `String` class? An _environment_ will give you the answer.
65
80
 
66
- `-r` is for libraries. You can specify the names of libraries.
81
+ A _definition_ gives you the detail of the class. What is the type of the return value of `gsub` method of the `String` class? The _definition_ for `String` class knows the list of methods it provides and their types.
67
82
 
68
- ```
69
- $ rbs -r set list
70
- ```
83
+ The following is a small code to retrieve the definition of the `String#gsub` method.
71
84
 
72
- `-I` is for application signatures. You can specify the name of directory.
85
+ <!-- run-start:a.rb:bundle exec ruby a.rb -->
86
+ ```rb
87
+ require "rbs"
73
88
 
89
+ loader = RBS::EnvironmentLoader.new()
90
+
91
+ # loader.add(path: Pathname("sig")) # Load .rbs files from `sig` directory
92
+ # loader.add(library: "pathname") # Load pathname library
93
+
94
+ environment = RBS::Environment.from_loader(loader).resolve_type_names
95
+
96
+ # ::String
97
+ string = RBS::TypeName.new(name: :String, namespace: RBS::Namespace.root)
98
+
99
+ # Class declaration for ::String
100
+ decl = environment.class_decls[string]
101
+
102
+ # Builder provides the translation from `declaration` to `definition`
103
+ builder = RBS::DefinitionBuilder.new(env: environment)
104
+
105
+ # Definition of instance of String
106
+ instance = builder.build_instance(string)
107
+ # Print the types of `gsub` method
108
+ puts instance.methods[:gsub].method_types.join("\n")
109
+
110
+ # Definition of singleton of String
111
+ singleton = builder.build_singleton(string)
112
+ # No `gsub` method for String singleton
113
+ puts singleton.methods[:gsub]
74
114
  ```
75
- $ rbs -I sig list
76
- ```
115
+ <!-- run-end -->
77
116
 
78
117
  ## Guides
79
118
 
@@ -90,4 +129,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
90
129
 
91
130
  ## Contributing
92
131
 
93
- Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/ruby-signature.
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/rbs.
data/Rakefile CHANGED
@@ -9,10 +9,23 @@ Rake::TestTask.new(:test) do |t|
9
9
  end
10
10
  end
11
11
 
12
- multitask :default => [:test, :stdlib_test, :rubocop, :validate]
12
+ multitask :default => [:test, :stdlib_test, :rubocop, :validate, :test_doc]
13
+
14
+ task :test_doc => :parser do
15
+ files = Dir.chdir(File.expand_path('..', __FILE__)) do
16
+ `git ls-files -z`.split("\x0").select do |file| Pathname(file).extname == ".md" end
17
+ end
18
+
19
+ sh "ruby bin/run_in_md.rb #{files.join(" ")}"
20
+ end
13
21
 
14
22
  task :validate => :parser do
15
23
  sh "rbs validate"
24
+
25
+ FileList["stdlib/*"].each do |path|
26
+ next if path =~ %r{stdlib/builtin}
27
+ sh "rbs -r#{File.basename(path)} validate"
28
+ end
16
29
  end
17
30
 
18
31
  FileList["test/stdlib/**/*_test.rb"].each do |test|
@@ -41,11 +54,11 @@ namespace :generate do
41
54
  raise "Class name is necessary. e.g. rake 'generate:stdlib_test[String]'"
42
55
  end
43
56
 
44
- path = Pathname("test/stdlib/#{klass}_test.rb")
57
+ path = Pathname(ENV["RBS_GENERATE_TEST_PATH"] || "test/stdlib/#{klass}_test.rb")
45
58
  raise "#{path} already exists!" if path.exist?
46
59
 
47
60
  require "erb"
48
- require "ruby/signature"
61
+ require "rbs"
49
62
 
50
63
  class TestTemplateBuilder
51
64
  attr_reader :klass, :env
@@ -53,35 +66,54 @@ namespace :generate do
53
66
  def initialize(klass)
54
67
  @klass = klass
55
68
 
56
- @env = Ruby::Signature::Environment.new
57
- Ruby::Signature::EnvironmentLoader.new.load(env: @env)
69
+ loader = RBS::EnvironmentLoader.new
70
+ Dir['stdlib/*'].each do |lib|
71
+ next if lib.end_with?('builtin')
72
+
73
+ loader.add(library: File.basename(lib))
74
+ end
75
+ @env = RBS::Environment.from_loader(loader).resolve_type_names
58
76
  end
59
77
 
60
78
  def call
61
79
  ERB.new(<<~ERB, trim_mode: "-").result(binding)
62
80
  require_relative "test_helper"
63
81
 
64
- class <%= klass %>Test < StdlibTest
65
- target <%= klass %>
82
+ <%- unless class_methods.empty? -%>
83
+ class <%= klass %>SingletonTest < Minitest::Test
84
+ include TypeAssertions
85
+
66
86
  # library "pathname", "set", "securerandom" # Declare library signatures to load
67
- using hook.refinement
87
+ testing "singleton(::<%= klass %>)"
88
+
68
89
  <%- class_methods.each do |method_name, definition| %>
69
- def test_class_method_<%= test_name_for(method_name) %>
70
- <%- definition.method_types.each do |method_type| -%>
71
- # <%= method_type %>
72
- <%= klass %>.<%= method_name %>
73
- <%- end -%>
90
+ def test_<%= test_name_for(method_name) %>
91
+ <%- definition.method_types.each do |method_type| -%>
92
+ assert_send_type "<%= method_type %>",
93
+ <%= klass %>, :<%= method_name %>
94
+ <%- end -%>
74
95
  end
75
96
  <%- end -%>
97
+ end
98
+ <%- end -%>
99
+
100
+ <%- unless instance_methods.empty? -%>
101
+ class <%= klass %>Test < Minitest::Test
102
+ include TypeAssertions
103
+
104
+ # library "pathname", "set", "securerandom" # Declare library signatures to load
105
+ testing "::<%= klass %>"
106
+
76
107
  <%- instance_methods.each do |method_name, definition| %>
77
108
  def test_<%= test_name_for(method_name) %>
78
- <%- definition.method_types.each do |method_type| -%>
79
- # <%= method_type %>
80
- <%= klass %>.new.<%= method_name %>
81
- <%- end -%>
109
+ <%- definition.method_types.each do |method_type| -%>
110
+ assert_send_type "<%= method_type %>",
111
+ <%= klass %>.new, :<%= method_name %>
112
+ <%- end -%>
82
113
  end
83
114
  <%- end -%>
84
115
  end
116
+ <%- end -%>
85
117
  ERB
86
118
  end
87
119
 
@@ -117,18 +149,18 @@ namespace :generate do
117
149
  end
118
150
 
119
151
  def type_name
120
- @type_name ||= Ruby::Signature::TypeName.new(name: klass.to_sym, namespace: Ruby::Signature::Namespace.new(path: [], absolute: true))
152
+ @type_name ||= RBS::TypeName.new(name: klass.to_sym, namespace: RBS::Namespace.new(path: [], absolute: true))
121
153
  end
122
154
 
123
155
  def class_methods
124
- @class_methods ||= Ruby::Signature::DefinitionBuilder.new(env: env).build_singleton(type_name).methods.select {|_, definition|
125
- definition.implemented_in.name.absolute! == type_name
156
+ @class_methods ||= RBS::DefinitionBuilder.new(env: env).build_singleton(type_name).methods.select {|_, definition|
157
+ definition.implemented_in == type_name
126
158
  }
127
159
  end
128
160
 
129
161
  def instance_methods
130
- @instance_methods ||= Ruby::Signature::DefinitionBuilder.new(env: env).build_instance(type_name).methods.select {|_, definition|
131
- definition.implemented_in.name.absolute! == type_name
162
+ @instance_methods ||= RBS::DefinitionBuilder.new(env: env).build_instance(type_name).methods.select {|_, definition|
163
+ definition.implemented_in == type_name
132
164
  }
133
165
  end
134
166
  end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "stackprof"
4
+
5
+ out = ENV["RBS_STACKPROF_OUT"] || 'tmp/stackprof-cpu-rbs.dump'
6
+
7
+ StackProf.run(mode: :cpu, out: out) do
8
+ load File.join(__dir__, "../exe/rbs")
9
+ end
@@ -0,0 +1,49 @@
1
+ require "tmpdir"
2
+ require "shellwords"
3
+
4
+ ARGV.each do |path|
5
+ puts "~~~~~~~ Checking #{path} ~~~~~~"
6
+ content = File.read(path)
7
+
8
+ snippets = []
9
+ lines = []
10
+ content.lines.each.with_index do |line, index|
11
+ case line
12
+ when /run-start:/
13
+ lines = [[line, index+1]]
14
+ when /run-end/
15
+ lines << [line, index+1]
16
+ snippets << lines
17
+ lines = []
18
+ else
19
+ lines << [line, index+1]
20
+ end
21
+ end
22
+
23
+ snippets.each do |lines|
24
+ puts ">>> Code detected: "
25
+ hd, _, *code_lines, _, _ = lines
26
+
27
+ head = hd[0]
28
+ head.gsub!(/^<!-- +/, "").gsub!(/ +-->$/, "")
29
+ _,name,command = head.split(/:/)
30
+
31
+ puts "# command=#{Shellwords.split(command).inspect}"
32
+ puts "# name=#{name}"
33
+ puts code_lines.map {|line, i| "#{"%4d" % i} #{line}" }.join
34
+
35
+ code = code_lines.map(&:first).join
36
+
37
+ puts ">>> Running..."
38
+ Dir.mktmpdir do |dir|
39
+ File.write(File.join(dir, name), code)
40
+ pid = spawn({ "BUNDLE_GEMFILE" => File.join(__dir__, "../Gemfile") },
41
+ *Shellwords.split(command),
42
+ chdir: dir)
43
+
44
+ _, status = Process.waitpid2(pid)
45
+
46
+ status.success? or raise "Failed to execute code: #{code_lines.join}"
47
+ end
48
+ end
49
+ end