~singpolyma/rld

ref: 8b7e5b2afc53abe6e5379d882ac36f01ac00fb14 rld/bin/rld -rwxr-xr-x 3.7 KiB
8b7e5b2aStephen Paul Weber Don't need pry 11 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/ruby

require "pathname"
require "optparse"
require "parser/current"

# Always use the rubygems version of require
require "rubygems"

$options = { o: STDOUT, l: [] }

if method(:require) == method(:gem_original_require)
	# Bundler
	module Kernel
		def require(path)
			$LOAD_PATH.resolve_feature_path(path).last
		rescue LoadError => e
			raise e unless $options[:w]
			warn e
			path
		end
	end
else
	# Rubygems
	module Kernel
		def gem_original_require(path)
			$LOAD_PATH.resolve_feature_path(path).last
		rescue LoadError => e
			raise e unless $options[:w]
			warn e
			path
		end
	end
end

# opt-in to most recent AST format:
Parser::Builders::Default.emit_lambda              = true
Parser::Builders::Default.emit_procarg0            = true
Parser::Builders::Default.emit_encoding            = true
Parser::Builders::Default.emit_index               = true
Parser::Builders::Default.emit_arg_inside_procarg0 = true
Parser::Builders::Default.emit_forward_arg         = true
Parser::Builders::Default.emit_kwargs              = true
Parser::Builders::Default.emit_match_pattern       = true

op = OptionParser.new do |opts|
	opts.banner = "Usage: rld [options] file.rb ..."

	opts.accept(Pathname) do |path|
		Pathname.new(path)
	end

	opts.on("-p PATH", Pathname, "Project root") do |v|
		$options[:p] = v.realdirpath
	end

	opts.on("-L PATH", "-I PATH", Pathname, "Search directory") do |v|
		$LOAD_PATH.unshift(v)
	end

	opts.on("-o FILE", Pathname, "Output file") do |v|
		$options[:o] = v.open("w+")
	end

	opts.on("-l FILE", "-r FILE", Pathname, "Require this file in output") do |v|
		$options[:l] << "require #{require(v).inspect}"
	end

	opts.on("-i", "Write output in-place") do
		$options[:i] = true
	end

	opts.on("-w", "Only warn on LoadError") do
		$options[:w] = true
	end
end
op.parse!

if ARGV.empty?
	puts op
	exit 1
end

class Linker < Parser::TreeRewriter
	def on_send(node)
		super
		case node
		in [:send, [:gvar, :$: | :$LOAD_PATH], (:<< | :push | :unshift) => m, arg]
			loc = arg.location.expression
			file = Pathname.new(loc.source_buffer.name)
			path = eval(loc.source, binding, file.to_s, loc.line)
			$LOAD_PATH.public_send(m, Pathname.new(path).relative? ? "#{file.dirname}/#{path}" : path)
			remove(node.location.expression)
		in [:send, nil, :require, [:str, arg] => argnode]
			path = require(arg)
			realpath = Pathname.new(path).realdirpath
			dir = Pathname.new(node.location.expression.source_buffer.name).dirname.expand_path
			if path.start_with?($options[:p].to_s)
				path = Pathname.new(path).relative_path_from(dir).to_s
				replace(node.location.expression, "require_relative " + path.inspect)
			elsif realpath.to_s.start_with?($options[:p].to_s)
				path = realpath.relative_path_from(dir).to_s
				replace(node.location.expression, "require_relative " + path.inspect)
			else
				replace(argnode.location.expression, path.inspect)
			end
		in [:send, nil, :load, [:str, arg] => argnode]
			return if arg.match?(/\A\.\.?\//)
			path = require(arg)
			replace(argnode.location.expression, path.inspect)
		in [:send, nil, :require | :load, *args]
			loc = node.location.expression
			warn "#{loc}: WARNING: ignoring non-string-literal #{loc.source}"
		else
		end
	end
end

$options[:p] ||= Pathname.new(ARGV[0]).dirname.realdirpath
$LOAD_PATH.unshift($options[:p])
$LOAD_PATH.unshift("#{$options[:p]}/lib")

$options[:l].each(&$options[:o].method(:puts)) unless $options[:i]

ARGV.each do |file|
	file = Pathname.new(file)
	source = file.read
	if $options[:i]
		$options[:o] = file.open("w+")
		$options[:l].each(&$options[:o].method(:puts))
	end
	buffer = Parser::Source::Buffer.new(file, source: source)
	ast = Parser::CurrentRuby.new.parse(buffer)
	$options[:o].write Linker.new.rewrite(buffer, ast)
end