temill 0.1.0
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/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/temill/core.rb +305 -0
- data/lib/temill/emitter.rb +166 -0
- data/lib/temill/parser.rb +140 -0
- data/lib/temill/version.rb +3 -0
- data/lib/temill.rb +18 -0
- data/temill.gemspec +33 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1155d75f0ef504687a7d27f1e32e5f78fba0bfec
|
4
|
+
data.tar.gz: 27230f848bea78c6b73c77a9447c4b423e84efd8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c1b8272fab37933ca4dca81c021076b54f14eef52964627f3ce75a588288e755fba9791a4ed97a8cb8d42c5966c85c3c931c28b088e5553abaca7a5623066fc
|
7
|
+
data.tar.gz: 60e8050055d35c03a5e43c9ae75b4a72cabc27d3cac1fda9668a3af24def3eb9d699f1652afae4fff8d0c71e26b93395964f4bc1e34b8d610e7e5bab0dfa1525
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Yusuke Takeuchi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Temill
|
2
|
+
|
3
|
+
Temill shows objects to embedded comments in source code.
|
4
|
+
|
5
|
+
## Sample
|
6
|
+
```ruby
|
7
|
+
require 'temill'
|
8
|
+
|
9
|
+
Temill.show('Hello!, World!')
|
10
|
+
|
11
|
+
Temill.show <<-EOT
|
12
|
+
You can use
|
13
|
+
heredoc
|
14
|
+
EOT
|
15
|
+
|
16
|
+
5.times{| i |
|
17
|
+
Temill.show(i ** 5)
|
18
|
+
}
|
19
|
+
|
20
|
+
Temill.show(
|
21
|
+
%w(More complex arguments).map{| s |
|
22
|
+
[s, s.upcase, s.downcase]
|
23
|
+
}
|
24
|
+
)
|
25
|
+
|
26
|
+
Temill.emit
|
27
|
+
```
|
28
|
+
will output
|
29
|
+
```ruby
|
30
|
+
#--------------------------------
|
31
|
+
#/path/to/source.rb
|
32
|
+
#--------------------------------
|
33
|
+
require 'temill'
|
34
|
+
|
35
|
+
Temill.show('Hello!, World!')
|
36
|
+
# temill showing 1 results for line 3 (line 3 in this output)
|
37
|
+
# "Hello!, World!"
|
38
|
+
|
39
|
+
Temill.show <<-EOT
|
40
|
+
You can use
|
41
|
+
heredoc
|
42
|
+
EOT
|
43
|
+
# temill showing 1 results for line 5 (line 7 in this output)
|
44
|
+
# " You can use\n heredoc\n"
|
45
|
+
|
46
|
+
5.times{| i |
|
47
|
+
Temill.show(i ** 5)
|
48
|
+
# temill showing 5 results for line 11 (line 15 in this output)
|
49
|
+
# 0
|
50
|
+
# 1
|
51
|
+
# 32
|
52
|
+
# 243
|
53
|
+
# 1024
|
54
|
+
}
|
55
|
+
|
56
|
+
Temill.show(
|
57
|
+
%w(More complex arguments).map{| s |
|
58
|
+
[s, s.upcase, s.downcase]
|
59
|
+
}
|
60
|
+
)
|
61
|
+
# temill showing 1 results for line 14 (line 24 in this output)
|
62
|
+
# [["More", "MORE", "more"],
|
63
|
+
# ["complex", "COMPLEX", "complex"],
|
64
|
+
# ["arguments", "ARGUMENTS", "arguments"]]
|
65
|
+
|
66
|
+
Temill.emit
|
67
|
+
```
|
68
|
+
|
69
|
+
|
70
|
+
## Installation
|
71
|
+
|
72
|
+
Add this line to your application's Gemfile:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
gem 'temill'
|
76
|
+
```
|
77
|
+
|
78
|
+
And then execute:
|
79
|
+
|
80
|
+
$ bundle
|
81
|
+
|
82
|
+
Or install it yourself as:
|
83
|
+
|
84
|
+
$ gem install temill
|
85
|
+
|
86
|
+
|
87
|
+
## Development
|
88
|
+
|
89
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
90
|
+
|
91
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
92
|
+
|
93
|
+
## Contributing
|
94
|
+
|
95
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/YusukeTakeuchi/temill.
|
96
|
+
|
97
|
+
|
98
|
+
## License
|
99
|
+
|
100
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
101
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "temill"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/temill/core.rb
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'digest/sha2'
|
3
|
+
require 'awesome_print'
|
4
|
+
|
5
|
+
# most features are implemented in TemillImpl
|
6
|
+
# @see Temill::TemillImpl
|
7
|
+
class Temill
|
8
|
+
|
9
|
+
# @param [Hash] opts
|
10
|
+
# @option opts [String] :default_indent indent string used when indent guess fails
|
11
|
+
# (defaults to ' ')
|
12
|
+
# @option opts [Integer] :tabstop the number of spaces a tab counts for.
|
13
|
+
# make sense only if both spaces and tabs are used in a line
|
14
|
+
# (defaults to 4)
|
15
|
+
# @option opts [bool] :compact if true, show only lines that #show methods called are involved
|
16
|
+
# (defaults to false)
|
17
|
+
# @option opts [Symbol,#call] :inspect method to convert obj into String
|
18
|
+
# (defaults to :pretty_inspect)
|
19
|
+
def initialize(**opts)
|
20
|
+
_initialize(**opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
EVAL_PATH_PREFIX = '(temill eval)/'
|
24
|
+
|
25
|
+
DefaultOptions = {
|
26
|
+
tabstop: 4,
|
27
|
+
default_indent: ' ',
|
28
|
+
compact: false,
|
29
|
+
inspect: :pretty_inspect
|
30
|
+
}
|
31
|
+
|
32
|
+
# Implementation of Temill:
|
33
|
+
# this module will be both included and extended to Temill
|
34
|
+
module TemillImpl
|
35
|
+
def _initialize(**options)
|
36
|
+
@options = DefaultOptions.merge(options)
|
37
|
+
reset
|
38
|
+
end
|
39
|
+
|
40
|
+
# clear all results added by #show
|
41
|
+
def reset
|
42
|
+
# absolute_path => SourceFile
|
43
|
+
@source_files = {}
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# set options
|
48
|
+
# @see Temil#initialize
|
49
|
+
def set_options(**options)
|
50
|
+
@options.update(options)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# store values to be shown. You have to use emit to actually output the results.
|
55
|
+
# @overload show(val)
|
56
|
+
# @param [Object] val value to show
|
57
|
+
# @overload show(*vals)
|
58
|
+
# @param [Array<Object>] vals values to show
|
59
|
+
# @overload show(&block)
|
60
|
+
# show the result of block
|
61
|
+
# @return [Object] the object shown
|
62
|
+
def show(*vals, &block)
|
63
|
+
if not vals.empty? and block
|
64
|
+
raise ArgumentError, 'either value or block can be specified'
|
65
|
+
end
|
66
|
+
|
67
|
+
loc = caller_locations.first
|
68
|
+
path = loc.absolute_path
|
69
|
+
|
70
|
+
sf = (@source_files[path] ||= SourceFile.from_path(path, @options))
|
71
|
+
|
72
|
+
if block
|
73
|
+
obj = block.call
|
74
|
+
else
|
75
|
+
case vals.size
|
76
|
+
when 0
|
77
|
+
obj = nil
|
78
|
+
when 1
|
79
|
+
obj = vals.first
|
80
|
+
else
|
81
|
+
obj = vals
|
82
|
+
end
|
83
|
+
end
|
84
|
+
sf.add_result(loc, obj, block_given?)
|
85
|
+
|
86
|
+
obj
|
87
|
+
end
|
88
|
+
|
89
|
+
# same as Kernel.eval, but the evaluated code is handled as if
|
90
|
+
# it was an independent file.
|
91
|
+
# @param [String] src
|
92
|
+
# @param [Binding] bind
|
93
|
+
# @param [String] fname
|
94
|
+
def eval(src, bind=TOPLEVEL_BINDING, fname=default_eval_fname(caller_locations.first))
|
95
|
+
path = EVAL_PATH_PREFIX + Digest::SHA256.hexdigest(src) + '/' + fname
|
96
|
+
@source_files[path] ||= SourceFile.from_inline_source(src, path, @options)
|
97
|
+
Kernel.eval(src, bind, path)
|
98
|
+
end
|
99
|
+
|
100
|
+
private def default_eval_fname(loc)
|
101
|
+
"#{File.basename(loc.path)}:#{loc.lineno}:#{loc.base_label}"
|
102
|
+
end
|
103
|
+
|
104
|
+
# output results to stdout or any IO
|
105
|
+
# @param [IO] f IO to output to
|
106
|
+
def emit(f=$stdout)
|
107
|
+
execute_emitter(Emitters::StdoutEmitter.new(f, @options))
|
108
|
+
end
|
109
|
+
|
110
|
+
# output results to files in a directory.
|
111
|
+
# a file of original source code corresponds to a output file.
|
112
|
+
# @param [String] dir
|
113
|
+
def emit_to_directory(dir)
|
114
|
+
execute_emitter(Emitters::DirectoryEmitter.new(dir, @options))
|
115
|
+
end
|
116
|
+
|
117
|
+
# output results to strings.
|
118
|
+
# @return [Hash<String,String>] the keys are filenames and the values are the result for the file
|
119
|
+
def emit_to_string
|
120
|
+
execute_emitter(Emitters::StringEmitter.new(@options))
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param [#call] emitter
|
124
|
+
def execute_emitter(emitter=nil, &block)
|
125
|
+
if emitter
|
126
|
+
emitter.call(@source_files.values, &block)
|
127
|
+
elsif block
|
128
|
+
block.call(@source_files.values)
|
129
|
+
else
|
130
|
+
raise ArgumentError, 'no emitter specified'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
class SourceFile
|
137
|
+
attr_reader :path, :lines, :sexp
|
138
|
+
attr_reader :insertion_points
|
139
|
+
|
140
|
+
def initialize(src, path, options)
|
141
|
+
@path = path
|
142
|
+
@lines = [nil] + src.lines
|
143
|
+
@sexp = Ruby23Parser.new.parse(src, path).value
|
144
|
+
@options = options
|
145
|
+
@insertion_points = InsertionPointSet.new
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.from_inline_source(src, virtual_path, options)
|
149
|
+
new(src, virtual_path, options)
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.from_path(path, options)
|
153
|
+
new(File.read(path), path, options)
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param [Thread::Backtrace::Location] caller_loc
|
157
|
+
# @param [Object] v value to show
|
158
|
+
# @param [bool] with_block whether Temill#show is called with block
|
159
|
+
def add_result(caller_loc, v, with_block)
|
160
|
+
caller_lineno = caller_loc.lineno
|
161
|
+
unless @insertion_points.at_caller_lineno(caller_lineno)
|
162
|
+
@insertion_points.add(make_insertion_point(caller_loc, with_block))
|
163
|
+
end
|
164
|
+
@insertion_points.at_caller_lineno(caller_lineno) << v
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
def each_source_line(&block)
|
169
|
+
return enum_for(__method__) unless block
|
170
|
+
1.upto(@lines.size-1){| i |
|
171
|
+
block.call(@lines[i], i)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
# @param [Thread::Backtrace::Location] caller_loc
|
178
|
+
def make_insertion_point(caller_loc, with_block=false)
|
179
|
+
caller_lineno = caller_loc.lineno
|
180
|
+
last_line = caller_lineno
|
181
|
+
inner_block_range = nil
|
182
|
+
#ParserUtils.add_line_ranges_to_sexp(@sexp) # DEBUG
|
183
|
+
@sexp.deep_each_with_self{| v |
|
184
|
+
if v.kind_of?(Sexp) and
|
185
|
+
[:iter, :call].include?(v.first) and
|
186
|
+
v.line_range.min == caller_lineno
|
187
|
+
last_line = [last_line, v.line_range.max].max
|
188
|
+
if with_block and v.first == :iter and block_sexp = v[3]
|
189
|
+
inner_block_range ||= block_sexp.line_range
|
190
|
+
if inner_block_range.max < block_sexp.line_range.max
|
191
|
+
inner_block_range = v[3].line_range
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
}
|
196
|
+
|
197
|
+
# We don't want to use the caller line to guess the indent level
|
198
|
+
# of the inner body of the block.
|
199
|
+
if inner_block_range
|
200
|
+
if inner_block_range.min == caller_lineno
|
201
|
+
if inner_block_range.max == caller_lineno
|
202
|
+
inner_block_range = nil
|
203
|
+
else
|
204
|
+
inner_block_range = (caller_lineno+1)..(inner_block_range.max)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if inner_block_range
|
210
|
+
emitter_lineno = inner_block_range.max
|
211
|
+
indent = guess_indent_in_block(inner_block_range)
|
212
|
+
elsif with_block and
|
213
|
+
end_line = @lines[last_line] and
|
214
|
+
/\A\s*(?:end|})\s*$/ =~ end_line
|
215
|
+
emitter_lineno = last_line - 1
|
216
|
+
indent = indent_at(last_line) + @options[:default_indent]
|
217
|
+
else
|
218
|
+
emitter_lineno = last_line
|
219
|
+
indent = indent_at(caller_lineno)
|
220
|
+
end
|
221
|
+
|
222
|
+
InsertionPoint.new(caller_loc, emitter_lineno, with_block, indent)
|
223
|
+
end
|
224
|
+
|
225
|
+
def indent_at(lineno)
|
226
|
+
@lines[lineno][/\A[ \t]*/]
|
227
|
+
end
|
228
|
+
|
229
|
+
def guess_indent_in_block(range)
|
230
|
+
@lines[range].map{| line |
|
231
|
+
line[/\A([ \t]*)\S/] && $1 # remove empty lines
|
232
|
+
}.compact.uniq.sort_by{| indent |
|
233
|
+
indent.gsub(/\t/, ' ' * @options[:tabstop]).size
|
234
|
+
}.first
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
# represent a set of insertion points in a single source file
|
239
|
+
class InsertionPointSet
|
240
|
+
include Enumerable
|
241
|
+
|
242
|
+
def initialize
|
243
|
+
@caller_lineno_to_ip = {}
|
244
|
+
@emitter_lineno_to_ips = {}
|
245
|
+
end
|
246
|
+
|
247
|
+
def add(ip)
|
248
|
+
if @caller_lineno_to_ip[ip.caller_lineno]
|
249
|
+
false
|
250
|
+
else
|
251
|
+
@caller_lineno_to_ip[ip.caller_lineno] = ip
|
252
|
+
((@emitter_lineno_to_ips[ip.emitter_lineno] ||= []) << ip).sort_by!{| ips |
|
253
|
+
ips.caller_lineno
|
254
|
+
}
|
255
|
+
true
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [InsertionPoint]
|
260
|
+
# @return [nil]
|
261
|
+
def at_caller_lineno(lineno)
|
262
|
+
@caller_lineno_to_ip[lineno]
|
263
|
+
end
|
264
|
+
|
265
|
+
# @return [Array<InsertionPoint>]
|
266
|
+
# @return [nil]
|
267
|
+
def at_emitter_lineno(lineno)
|
268
|
+
@emitter_lineno_to_ips[lineno]
|
269
|
+
end
|
270
|
+
|
271
|
+
def each(&block)
|
272
|
+
@caller_lineno_to_ip.values.each(&block)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class InsertionPoint
|
277
|
+
attr_reader :caller_loc
|
278
|
+
attr_reader :caller_lineno, :emitter_lineno
|
279
|
+
attr_reader :indent
|
280
|
+
attr_reader :results
|
281
|
+
|
282
|
+
# @param [Thread::Backtrace::Location] caller_loc
|
283
|
+
# @param [Integer] emitter_lineno line number after where to emit results
|
284
|
+
# @param [bool] with_block whether called with block
|
285
|
+
# @param [String] indent
|
286
|
+
def initialize(caller_loc, emitter_lineno, with_block, indent)
|
287
|
+
@caller_location = caller_loc
|
288
|
+
@caller_lineno = caller_loc.lineno
|
289
|
+
@emitter_lineno = emitter_lineno
|
290
|
+
@with_block = with_block
|
291
|
+
@indent = indent
|
292
|
+
@results = []
|
293
|
+
end
|
294
|
+
|
295
|
+
def <<(result)
|
296
|
+
@results << result
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
extend TemillImpl
|
302
|
+
include TemillImpl
|
303
|
+
|
304
|
+
_initialize
|
305
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Temill
|
5
|
+
module Emitters
|
6
|
+
class Base
|
7
|
+
def initialize(options)
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [Hash<String, SourceFile>] source_files
|
12
|
+
def execute(source_files)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Array<SourceFile>] source_files
|
16
|
+
def call(source_files)
|
17
|
+
execute(source_files)
|
18
|
+
end
|
19
|
+
|
20
|
+
def emit_for_source_file(sf, out_f)
|
21
|
+
lines_map = [] # map source lineno to output lineno
|
22
|
+
compact_ranges = sf.insertion_points.map{| ip | ip.caller_lineno .. ip.emitter_lineno }
|
23
|
+
printer = Printer.new(out_f, **@options)
|
24
|
+
sf.each_source_line{| line,i |
|
25
|
+
lines_map[i] = printer.lineno
|
26
|
+
printer.print_raw(line) if not @options[:compact] or compact_ranges.any?{| r | r.cover?(i) }
|
27
|
+
if ips = sf.insertion_points.at_emitter_lineno(i)
|
28
|
+
ips.each{| ip |
|
29
|
+
printer.indent = ip.indent
|
30
|
+
printer.print_str(annotation(ip, lines_map[ip.caller_lineno]))
|
31
|
+
ip.results.each{| obj |
|
32
|
+
printer.print(obj)
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def annotation(ip, output_line_for_caller)
|
40
|
+
"temill showing #{ip.results.size} results" +
|
41
|
+
" for line #{ip.caller_lineno}" +
|
42
|
+
" (line #{output_line_for_caller} in this output)"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class StdoutEmitter < Base
|
47
|
+
def initialize(io, options)
|
48
|
+
@io = io
|
49
|
+
super(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute(source_files)
|
53
|
+
source_files.each{| sf |
|
54
|
+
puts '#--------------------------------'
|
55
|
+
puts "\##{sf.path}"
|
56
|
+
puts '#--------------------------------'
|
57
|
+
emit_for_source_file(sf, @io)
|
58
|
+
}
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class StringEmitter < Base
|
64
|
+
def execute(source_files)
|
65
|
+
source_files.map{| sf |
|
66
|
+
sio = StringIO.new('', 'w')
|
67
|
+
emit_for_source_file(sf, sio)
|
68
|
+
[sf.path, sio.string]
|
69
|
+
}.to_h
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class DirectoryEmitter < Base
|
74
|
+
def initialize(dir_path, options)
|
75
|
+
@dir_path = Pathname.new(dir_path)
|
76
|
+
super(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def execute(source_files)
|
80
|
+
FileUtils.makedirs(@dir_path)
|
81
|
+
written = []
|
82
|
+
source_files.each{| sf |
|
83
|
+
fname = output_fname(sf.path, written)
|
84
|
+
File.open(fname, 'w'){| f |
|
85
|
+
emit_for_source_file(sf, f)
|
86
|
+
written << fname
|
87
|
+
}
|
88
|
+
}
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def output_fname(base_fname, written)
|
93
|
+
fname_base = (@dir_path + File.basename(base_fname)).to_s
|
94
|
+
current_fname = fname_base
|
95
|
+
suffix_n = 1
|
96
|
+
while written.include?(current_fname)
|
97
|
+
current_fname = fname_base + ".#{suffix_n}"
|
98
|
+
suffix_n += 1
|
99
|
+
end
|
100
|
+
current_fname
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Printer
|
105
|
+
attr_reader :output_lines
|
106
|
+
attr_accessor :indent
|
107
|
+
|
108
|
+
def initialize(io = $stdout, **options)
|
109
|
+
@options = options
|
110
|
+
@output_lines = 0
|
111
|
+
@indent = ''
|
112
|
+
@io = io
|
113
|
+
end
|
114
|
+
|
115
|
+
def lineno
|
116
|
+
@output_lines + 1
|
117
|
+
end
|
118
|
+
|
119
|
+
def obj_to_s(obj)
|
120
|
+
case f = @options[:inspect]
|
121
|
+
when Symbol
|
122
|
+
obj.__send__(f)
|
123
|
+
when nil
|
124
|
+
obj.pretty_inspect
|
125
|
+
else
|
126
|
+
f.call(obj)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def print(obj)
|
131
|
+
print_str(obj_to_s(obj))
|
132
|
+
end
|
133
|
+
|
134
|
+
def print_str(str)
|
135
|
+
str.each_line{| line |
|
136
|
+
out @indent
|
137
|
+
out '# '
|
138
|
+
out_nl line
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def print_empty_line
|
143
|
+
out @indent
|
144
|
+
out_nl '#'
|
145
|
+
end
|
146
|
+
|
147
|
+
def print_raw(str)
|
148
|
+
str.each_line{| line |
|
149
|
+
out_nl line
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def out(str)
|
154
|
+
@io.print(str)
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
def out_nl(str=nil)
|
159
|
+
@io.puts(str)
|
160
|
+
@output_lines += 1
|
161
|
+
self
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'ruby_parser'
|
2
|
+
|
3
|
+
class Temill
|
4
|
+
ParserResult = Struct.new(:value, :line_range, :sym)
|
5
|
+
|
6
|
+
# Override parser and scanner actions since we need the range of
|
7
|
+
# each expr in Ruby programs
|
8
|
+
module ParserHack
|
9
|
+
def before_reduce(val, *rest)
|
10
|
+
#ap val
|
11
|
+
val.map{| v |
|
12
|
+
if v.kind_of?(ParserResult)
|
13
|
+
v.value
|
14
|
+
else
|
15
|
+
v
|
16
|
+
end
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_reduce(val, _values, result)
|
21
|
+
min = max = nil
|
22
|
+
#debug_print_state_stack
|
23
|
+
val.each{| v |
|
24
|
+
if v.kind_of?(ParserResult) and v.sym != :tNL
|
25
|
+
min = [min, v.line_range.min].compact.min
|
26
|
+
max = [max, v.line_range.max].compact.max
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
if result.kind_of?(Sexp)
|
31
|
+
result.line_range = min..max
|
32
|
+
end
|
33
|
+
# sym is not specified, but OK as long as it is used to check whether
|
34
|
+
# the symbol is newline or not.
|
35
|
+
ParserResult.new(result, min..max, nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
# wrap the value of each token to ParserResult
|
39
|
+
def next_token
|
40
|
+
sym,val = super
|
41
|
+
lineno = lexer.lineno
|
42
|
+
[sym, ParserResult.new(val, lineno..lineno, sym)]
|
43
|
+
end
|
44
|
+
|
45
|
+
def debug_print_state_stack
|
46
|
+
if @racc_tstack
|
47
|
+
pp @racc_tstack.zip(@racc_vstack).map{| tok,v |
|
48
|
+
[token_to_str(tok), v]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module RaccHack
|
55
|
+
|
56
|
+
# override all actions and make them call before_reduce/after_reduce
|
57
|
+
# before/after each action is executed
|
58
|
+
def self.included(klass)
|
59
|
+
#racc_action_table,
|
60
|
+
#racc_action_check,
|
61
|
+
#racc_action_default,
|
62
|
+
#racc_action_pointer,
|
63
|
+
#racc_goto_table,
|
64
|
+
#racc_goto_check,
|
65
|
+
#racc_goto_default,
|
66
|
+
#racc_goto_pointer,
|
67
|
+
#racc_nt_base,
|
68
|
+
#racc_reduce_table,
|
69
|
+
#racc_token_table,
|
70
|
+
#racc_shift_n,
|
71
|
+
#racc_reduce_n,
|
72
|
+
#racc_use_result_var = klass::Racc_arg
|
73
|
+
|
74
|
+
racc_reduce_table = klass::Racc_arg[9]
|
75
|
+
|
76
|
+
racc_reduce_table.each_slice(3).map{| _,_,method_name |
|
77
|
+
method_name
|
78
|
+
}.uniq.each{| method_name |
|
79
|
+
begin
|
80
|
+
method = klass.instance_method(method_name)
|
81
|
+
rescue NameError
|
82
|
+
next
|
83
|
+
end
|
84
|
+
method.tap{| original_umethod |
|
85
|
+
klass.__send__(:define_method, method_name){| val,_values,result |
|
86
|
+
new_val = before_reduce(val, _values, result)
|
87
|
+
new_result = original_umethod.bind(self).call(new_val, _values, result)
|
88
|
+
after_reduce(val, _values, new_result)
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
# should return new val
|
95
|
+
def before_reduce(val, _values, result)
|
96
|
+
val
|
97
|
+
end
|
98
|
+
|
99
|
+
# should return new result
|
100
|
+
#
|
101
|
+
# Note that val is equal to the object passed to before_reduce,
|
102
|
+
# not new val returned by before_reduce
|
103
|
+
def after_reduce(val, _values, result)
|
104
|
+
result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module ParserUtils
|
109
|
+
module_function
|
110
|
+
|
111
|
+
def add_line_ranges_to_sexp(sexp)
|
112
|
+
if sexp.kind_of?(Sexp)
|
113
|
+
sexp.each{| elt |
|
114
|
+
add_line_ranges_to_sexp(elt)
|
115
|
+
}
|
116
|
+
sexp << sexp.line_range
|
117
|
+
end
|
118
|
+
sexp
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# XXX: only Ruby23Parser is supported
|
123
|
+
class Ruby23Parser < ::Ruby23Parser
|
124
|
+
include RaccHack
|
125
|
+
prepend ParserHack
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Sexp
|
130
|
+
attr_accessor :line_range
|
131
|
+
|
132
|
+
# same as deep_each, but pass self first
|
133
|
+
def deep_each_with_self(&block)
|
134
|
+
return enum_for(__method__) unless block
|
135
|
+
|
136
|
+
block.call(self)
|
137
|
+
deep_each(&block)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
data/lib/temill.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# FIXME:
|
2
|
+
# with cparser,
|
3
|
+
# Temill.show(
|
4
|
+
# 55
|
5
|
+
# )
|
6
|
+
# results in
|
7
|
+
# Temill.show(
|
8
|
+
# 55
|
9
|
+
# # temill showing 1 results for line 1 (line 1 in this output)
|
10
|
+
# # 55
|
11
|
+
# )
|
12
|
+
# ,which is not what we want.
|
13
|
+
Racc_No_Extensions = true
|
14
|
+
|
15
|
+
require "temill/version"
|
16
|
+
require 'temill/core'
|
17
|
+
require 'temill/parser'
|
18
|
+
require 'temill/emitter'
|
data/temill.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'temill/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "temill"
|
8
|
+
spec.version = Temill::VERSION
|
9
|
+
spec.authors = ["Yusuke Takeuchi"]
|
10
|
+
spec.email = ["v.takeuchi+gh@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Temill shows objects in embedded comments in source files.}
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = "https://github.com/YusukeTakeuchi/temill"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.required_ruby_version = '>= 2.3'
|
26
|
+
|
27
|
+
spec.add_dependency 'ruby_parser', '~> 3.8'
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
32
|
+
spec.add_development_dependency 'awesome_print', '~> 1.7'
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: temill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yusuke Takeuchi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby_parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: awesome_print
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.7'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.7'
|
83
|
+
description: Temill shows objects in embedded comments in source files.
|
84
|
+
email:
|
85
|
+
- v.takeuchi+gh@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/setup
|
98
|
+
- lib/temill.rb
|
99
|
+
- lib/temill/core.rb
|
100
|
+
- lib/temill/emitter.rb
|
101
|
+
- lib/temill/parser.rb
|
102
|
+
- lib/temill/version.rb
|
103
|
+
- temill.gemspec
|
104
|
+
homepage: https://github.com/YusukeTakeuchi/temill
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '2.3'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.5.1
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Temill shows objects in embedded comments in source files.
|
128
|
+
test_files: []
|