pure 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.
- data/CHANGES.rdoc +7 -0
- data/MANIFEST +30 -0
- data/README.rdoc +59 -0
- data/Rakefile +10 -0
- data/devel/jumpstart.rb +634 -0
- data/devel/jumpstart/lazy_attribute.rb +38 -0
- data/devel/jumpstart/ruby.rb +44 -0
- data/devel/jumpstart/simple_installer.rb +85 -0
- data/install.rb +3 -0
- data/lib/pure.rb +19 -0
- data/lib/pure/pure_private/creator.rb +27 -0
- data/lib/pure/pure_private/driver.rb +48 -0
- data/lib/pure/pure_private/error.rb +32 -0
- data/lib/pure/pure_private/extractor.rb +79 -0
- data/lib/pure/pure_private/extractor_ripper.rb +95 -0
- data/lib/pure/pure_private/extractor_ruby_parser.rb +47 -0
- data/lib/pure/pure_private/function_database.rb +10 -0
- data/lib/pure/pure_private/singleton_features.rb +67 -0
- data/lib/pure/pure_private/util.rb +23 -0
- data/spec/basic_spec.rb +38 -0
- data/spec/combine_spec.rb +62 -0
- data/spec/common.rb +44 -0
- data/spec/error_spec.rb +146 -0
- data/spec/fun_spec.rb +122 -0
- data/spec/lazy_spec.rb +22 -0
- data/spec/parser_spec.rb +36 -0
- data/spec/readme_spec.rb +35 -0
- data/spec/splat_spec.rb +16 -0
- data/spec/subseqent_spec.rb +42 -0
- data/spec/timed_spec.rb +30 -0
- metadata +135 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
class Jumpstart
|
3
|
+
#
|
4
|
+
# Mixin for lazily-evaluated attributes.
|
5
|
+
#
|
6
|
+
module LazyAttribute
|
7
|
+
#
|
8
|
+
# &block is evaluated when this attribute is requested. The same
|
9
|
+
# result is returned for subsequent calls until the attribute is
|
10
|
+
# assigned a different value.
|
11
|
+
#
|
12
|
+
def attribute(reader, &block)
|
13
|
+
writer = "#{reader}="
|
14
|
+
|
15
|
+
singleton = (class << self ; self ; end)
|
16
|
+
|
17
|
+
define_evaluated_reader = lambda { |value|
|
18
|
+
singleton.class_eval {
|
19
|
+
remove_method(reader)
|
20
|
+
define_method(reader) { value }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
singleton.class_eval {
|
25
|
+
define_method(reader) {
|
26
|
+
value = block.call
|
27
|
+
define_evaluated_reader.call(value)
|
28
|
+
value
|
29
|
+
}
|
30
|
+
|
31
|
+
define_method(writer) { |value|
|
32
|
+
define_evaluated_reader.call(value)
|
33
|
+
value
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
class Jumpstart
|
5
|
+
module Ruby
|
6
|
+
EXECUTABLE = lambda {
|
7
|
+
name = File.join(
|
8
|
+
Config::CONFIG["bindir"],
|
9
|
+
Config::CONFIG["RUBY_INSTALL_NAME"]
|
10
|
+
)
|
11
|
+
|
12
|
+
if Config::CONFIG["host"] =~ %r!(mswin|cygwin|mingw)! and
|
13
|
+
File.basename(name) !~ %r!\.(exe|com|bat|cmd)\Z!i
|
14
|
+
name + Config::CONFIG["EXEEXT"]
|
15
|
+
else
|
16
|
+
name
|
17
|
+
end
|
18
|
+
}.call
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def run(*args)
|
22
|
+
cmd = [EXECUTABLE, *args]
|
23
|
+
unless system(*cmd)
|
24
|
+
cmd_str = cmd.map { |t| "'#{t}'" }.join(", ")
|
25
|
+
raise "system(#{cmd_str}) failed with status #{$?.exitstatus}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_warnings(value = true)
|
30
|
+
previous = $VERBOSE
|
31
|
+
$VERBOSE = value
|
32
|
+
begin
|
33
|
+
yield
|
34
|
+
ensure
|
35
|
+
$VERBOSE = previous
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def no_warnings(&block)
|
40
|
+
with_warnings(false, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
require 'rbconfig'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
class Jumpstart
|
7
|
+
class SimpleInstaller
|
8
|
+
def initialize
|
9
|
+
dest_root = Config::CONFIG["sitelibdir"]
|
10
|
+
sources = []
|
11
|
+
Find.find("./lib") { |source|
|
12
|
+
if install_file?(source)
|
13
|
+
sources << source
|
14
|
+
end
|
15
|
+
}
|
16
|
+
@spec = sources.inject(Array.new) { |acc, source|
|
17
|
+
if source == "./lib"
|
18
|
+
acc
|
19
|
+
else
|
20
|
+
dest = File.join(dest_root, source.sub(%r!\A\./lib!, ""))
|
21
|
+
|
22
|
+
install = lambda {
|
23
|
+
if File.directory?(source)
|
24
|
+
unless File.directory?(dest)
|
25
|
+
puts "mkdir #{dest}"
|
26
|
+
FileUtils.mkdir(dest)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
puts "install #{source} --> #{dest}"
|
30
|
+
FileUtils.install(source, dest)
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
uninstall = lambda {
|
35
|
+
if File.directory?(source)
|
36
|
+
if File.directory?(dest)
|
37
|
+
puts "rmdir #{dest}"
|
38
|
+
FileUtils.rmdir(dest)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
if File.file?(dest)
|
42
|
+
puts "rm #{dest}"
|
43
|
+
FileUtils.rm(dest)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
acc << {
|
49
|
+
:source => source,
|
50
|
+
:dest => dest,
|
51
|
+
:install => install,
|
52
|
+
:uninstall => uninstall,
|
53
|
+
}
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def install_file?(source)
|
59
|
+
File.directory?(source) or
|
60
|
+
(File.file?(source) and File.extname(source) == ".rb")
|
61
|
+
end
|
62
|
+
|
63
|
+
def install
|
64
|
+
@spec.each { |entry|
|
65
|
+
entry[:install].call
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def uninstall
|
70
|
+
@spec.reverse.each { |entry|
|
71
|
+
entry[:uninstall].call
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def run(args = ARGV)
|
76
|
+
if args.empty?
|
77
|
+
install
|
78
|
+
elsif args.size == 1 and args.first == "--uninstall"
|
79
|
+
uninstall
|
80
|
+
else
|
81
|
+
raise "unrecognized arguments: #{args.inspect}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/install.rb
ADDED
data/lib/pure.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'pure/pure_private/creator'
|
3
|
+
require 'pure/pure_private/extractor'
|
4
|
+
|
5
|
+
module Pure
|
6
|
+
PURE_VERSION = "0.1.0"
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def pure(&block)
|
11
|
+
PurePrivate::Creator.create_module(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
[:parser, :parser=].each { |name|
|
16
|
+
define_method name, &PurePrivate::Extractor.method(name)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'pure/pure_private/util'
|
3
|
+
require 'pure/pure_private/singleton_features'
|
4
|
+
|
5
|
+
module Pure
|
6
|
+
module PurePrivate
|
7
|
+
module Creator
|
8
|
+
class << self
|
9
|
+
include Util
|
10
|
+
|
11
|
+
def create_module(&block)
|
12
|
+
mod = Module.new
|
13
|
+
fun_mod = Module.new
|
14
|
+
singleton_class_of(mod).module_eval {
|
15
|
+
include SingletonFeatures
|
16
|
+
@fun_mod = fun_mod
|
17
|
+
}
|
18
|
+
mod.module_eval(&block)
|
19
|
+
mod.module_eval {
|
20
|
+
include fun_mod
|
21
|
+
}
|
22
|
+
mod
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require 'pure/pure_private/function_database'
|
3
|
+
require 'pure/pure_private/error'
|
4
|
+
require 'comp_tree'
|
5
|
+
|
6
|
+
module Pure
|
7
|
+
module PurePrivate
|
8
|
+
module Driver
|
9
|
+
include FunctionDatabase
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def build_and_compute(mod, root, num_threads, &block)
|
14
|
+
begin
|
15
|
+
CompTree.build do |driver|
|
16
|
+
mod.ancestors.each { |ancestor|
|
17
|
+
if defs = FUNCTION_DATABASE[ancestor]
|
18
|
+
defs.each_pair { |function_name, spec|
|
19
|
+
existing_node = driver.nodes[function_name]
|
20
|
+
if existing_node.nil? or existing_node.function.nil?
|
21
|
+
final_spec = spec.merge(:module => ancestor)
|
22
|
+
node = driver.define(function_name, *spec[:args])
|
23
|
+
node.function = yield function_name, final_spec
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
}
|
28
|
+
driver.compute(root, num_threads)
|
29
|
+
end
|
30
|
+
rescue CompTree::NoFunctionError => exception
|
31
|
+
raise PurePrivate::NoFunctionError, exception.message
|
32
|
+
rescue CompTree::ArgumentError => exception
|
33
|
+
raise PurePrivate::ArgumentError, exception.message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_instance_and_compute(mod, root, opts)
|
38
|
+
num_threads = (opts.is_a?(Hash) ? opts[:threads] : opts).to_i
|
39
|
+
instance = Object.new.extend(mod)
|
40
|
+
build_and_compute(mod, root, num_threads) { |function_name, spec|
|
41
|
+
lambda { |*args|
|
42
|
+
instance.send(function_name, *args)
|
43
|
+
}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module Pure
|
3
|
+
module PurePrivate
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class NoFunctionError < Error
|
8
|
+
end
|
9
|
+
|
10
|
+
class ParseError < Error
|
11
|
+
end
|
12
|
+
|
13
|
+
class ArgumentError < Error
|
14
|
+
end
|
15
|
+
|
16
|
+
class SplatError < ParseError
|
17
|
+
def initialize(file, line)
|
18
|
+
super()
|
19
|
+
@file = file
|
20
|
+
@line = line
|
21
|
+
end
|
22
|
+
|
23
|
+
def message
|
24
|
+
"cannot use splat (*) argument in a pure function defined with `def' " +
|
25
|
+
"at #{@file}:#{@line}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class NotImplementedError < Error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
require 'pure/pure_private/error'
|
3
|
+
require 'pure/pure_private/util'
|
4
|
+
|
5
|
+
module Pure
|
6
|
+
module PurePrivate
|
7
|
+
module Extractor
|
8
|
+
HAS_METHOD_PARAMETERS = Method.instance_methods.include?(:parameters)
|
9
|
+
|
10
|
+
DEFAULT_PARSER = (
|
11
|
+
if HAS_METHOD_PARAMETERS
|
12
|
+
nil
|
13
|
+
elsif RUBY_VERSION >= "1.9"
|
14
|
+
"ripper"
|
15
|
+
else
|
16
|
+
"ruby_parser"
|
17
|
+
end
|
18
|
+
)
|
19
|
+
|
20
|
+
@parser = nil
|
21
|
+
@engine = nil
|
22
|
+
@cache = Hash.new { |hash, key| hash[key] = Hash.new }
|
23
|
+
|
24
|
+
class << self
|
25
|
+
include Util
|
26
|
+
|
27
|
+
def extract(mod, method_name, backtrace)
|
28
|
+
file, line = file_line(backtrace.first)
|
29
|
+
if @parser.nil? and HAS_METHOD_PARAMETERS
|
30
|
+
if method_name == :fun
|
31
|
+
Hash.new
|
32
|
+
else
|
33
|
+
{
|
34
|
+
:name => method_name,
|
35
|
+
:args => mod.instance_method(method_name).parameters.map {
|
36
|
+
|type, name|
|
37
|
+
raise SplatError.new(file, line) if type == :rest
|
38
|
+
name
|
39
|
+
},
|
40
|
+
}
|
41
|
+
end
|
42
|
+
else
|
43
|
+
if @parser.nil?
|
44
|
+
self.parser = DEFAULT_PARSER
|
45
|
+
end
|
46
|
+
defs = @cache[@parser][file] || (
|
47
|
+
@cache[@parser][file] = @engine.new(file).run
|
48
|
+
)
|
49
|
+
spec = defs[line]
|
50
|
+
unless spec and spec[:name] and spec[:name] == method_name
|
51
|
+
raise PurePrivate::ParseError,
|
52
|
+
"failure parsing `#{method_name}' at #{file}:#{line}"
|
53
|
+
end
|
54
|
+
spec.merge(:file => file, :line => line)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def parser=(parser_name)
|
59
|
+
if parser_name.nil?
|
60
|
+
@engine = nil
|
61
|
+
else
|
62
|
+
require parser_name
|
63
|
+
begin
|
64
|
+
engine_name = "extractor_#{parser_name}"
|
65
|
+
require "pure/pure_private/#{engine_name}"
|
66
|
+
@engine = PurePrivate.const_get(to_camel_case(engine_name))
|
67
|
+
rescue LoadError
|
68
|
+
raise PurePrivate::NotImplementedError,
|
69
|
+
"parser not supported: #{parser_name}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
@parser = parser_name
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :parser
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
require 'ripper'
|
3
|
+
|
4
|
+
module Pure
|
5
|
+
module PurePrivate
|
6
|
+
class ExtractorRipper
|
7
|
+
def initialize(file)
|
8
|
+
@file = file
|
9
|
+
@defs = Hash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
process(Ripper.sexp(File.read(@file)))
|
14
|
+
@defs
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_def(sexp)
|
18
|
+
if sexp[0] == :def
|
19
|
+
name = sexp[1][1].to_sym
|
20
|
+
line = sexp[1][2][0]
|
21
|
+
params = (
|
22
|
+
case sexp[2].first
|
23
|
+
when :params
|
24
|
+
sexp[2]
|
25
|
+
when :paren
|
26
|
+
sexp[2][1]
|
27
|
+
else
|
28
|
+
raise PurePrivate::ParseError,
|
29
|
+
"unforeseen `def' syntax at #{@file}:#{line}"
|
30
|
+
end
|
31
|
+
)
|
32
|
+
if params.any? { |t| t and t[0] == :rest_param }
|
33
|
+
raise SplatError.new(@file, line)
|
34
|
+
end
|
35
|
+
args = (
|
36
|
+
if params[1].nil?
|
37
|
+
[]
|
38
|
+
else
|
39
|
+
params[1].map { |t| t[1].to_sym }
|
40
|
+
end
|
41
|
+
)
|
42
|
+
@defs[line] = {
|
43
|
+
:name => name,
|
44
|
+
:args => args,
|
45
|
+
:sexp => sexp,
|
46
|
+
}
|
47
|
+
true
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_fun(sexp)
|
54
|
+
if sexp[0] == :method_add_block and sexp[1].is_a?(Array)
|
55
|
+
line = (
|
56
|
+
if sexp[1][0] == :command and
|
57
|
+
sexp[1][1].is_a?(Array) and
|
58
|
+
sexp[1][1][1] == "fun"
|
59
|
+
sexp[1][1][2][0]
|
60
|
+
elsif sexp[1][0] == :method_add_arg and
|
61
|
+
sexp[1][1].is_a?(Array) and
|
62
|
+
sexp[1][1][0] == :fcall and
|
63
|
+
sexp[1][1][1].is_a?(Array) and
|
64
|
+
sexp[1][1][1][1] == "fun"
|
65
|
+
sexp[1][1][1][2][0]
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
)
|
70
|
+
if line
|
71
|
+
@defs[line] = {
|
72
|
+
:name => :fun,
|
73
|
+
:sexp => sexp[2],
|
74
|
+
}
|
75
|
+
true
|
76
|
+
else
|
77
|
+
false
|
78
|
+
end
|
79
|
+
else
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def process(sexp)
|
85
|
+
if sexp.is_a? Array
|
86
|
+
process_def(sexp) or process_fun(sexp) or (
|
87
|
+
sexp.each { |sub_sexp|
|
88
|
+
process(sub_sexp)
|
89
|
+
}
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|