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