~singpolyma/rld

ref: cc50f438a3754a266b87eec820cbf0ebd9ef08e8 rld/bin/rld -rwxr-xr-x 4.0 KiB
cc50f438Stephen Paul Weber Initial guix packaging 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
136
137
138
139
140
141
142
143
#!/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)
			begin
				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)
			rescue
				loc = node.location.expression
				warn "#{loc}: WARNING: ignoring non-string-literal #{loc.source}"
			end
		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

# Use public_send so we can link ourselves, otherwise this global is defined
# So it will "work" and the lines would be removed in linked output
$LOAD_PATH.public_send(:unshift, $options[:p])
$LOAD_PATH.public_send(: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