pipe_operator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,98 @@
1
+ require "rdoc/task"
2
+
3
+ task default: :ci
4
+
5
+ desc "scratchpad"
6
+ task :scratch do
7
+ require "json"
8
+ require "net/http"
9
+ require_relative "lib/pipe_operator/autoload"
10
+
11
+ puts "abc".pipe { reverse } #=> "cba"
12
+ puts "abc".pipe { reverse.upcase } #=> "CBA"
13
+
14
+ # puts [9, 64].map(&Math.|.sqrt.to_i)
15
+ # puts "single"
16
+ # puts 256.pipe { Math.sqrt.to_i.to_s }.inspect
17
+ # puts
18
+ # puts "multiple"
19
+ # puts [16, 256].map(&Math.|.sqrt.to_i.to_s).inspect
20
+
21
+ # "https://api.github.com/repos/ruby/ruby".| do
22
+ # URI.parse
23
+ # Net::HTTP.get
24
+ # JSON.parse.fetch("stargazers_count")
25
+ # yield_self { |n| "Ruby has #{n} stars" }
26
+ # Kernel.puts
27
+ # end
28
+ # => Ruby has 15115 stars
29
+
30
+ # p = ["256", "-16"].pipe do
31
+ # map(&:to_i)
32
+ # sort
33
+ # first
34
+ # abs
35
+ # Math.sqrt
36
+ # to_i
37
+ # end
38
+ #
39
+ # puts p.inspect
40
+ end
41
+ task s: :scratch
42
+
43
+ desc "run tests, validate styleguide, and generate rdoc"
44
+ task :ci do
45
+ %w[lint test doc].each do |task|
46
+ command = "bundle exec rake #{task} --trace"
47
+ system(command) || raise("#{task} failed")
48
+ puts "\n"
49
+ end
50
+ end
51
+
52
+ desc "validate styleguide"
53
+ task :lint do
54
+ %w[fasterer rubocop].each do |task|
55
+ command = "bundle exec #{task}"
56
+ system(command) || exit(1)
57
+ end
58
+ end
59
+ task l: :lint
60
+
61
+ desc "run tests"
62
+ task :test do
63
+ exec "bundle exec rspec"
64
+ end
65
+ task t: :test
66
+
67
+
68
+ RDoc::Task.new :doc do |rdoc|
69
+ rdoc.title = "pipe_operator"
70
+
71
+ rdoc.main = "README.md"
72
+ rdoc.rdoc_dir = "doc"
73
+
74
+ rdoc.options << "--all"
75
+ rdoc.options << "--hyperlink-all"
76
+ rdoc.options << "--line-numbers"
77
+
78
+ rdoc.rdoc_files.include(
79
+ "LICENSE",
80
+ "README.md",
81
+ "lib/**/*.rb",
82
+ "lib/*.rb"
83
+ )
84
+ end
85
+ task d: :doc
86
+
87
+ desc "pry console"
88
+ task :console do
89
+ require "base64"
90
+ require "json"
91
+ require "net/http"
92
+ require "pry"
93
+ require "pry-byebug"
94
+ require_relative "lib/pipe_operator/autoload"
95
+
96
+ PipeOperator.pry
97
+ end
98
+ task c: :console
@@ -0,0 +1,49 @@
1
+ require "fiddle"
2
+ require "forwardable"
3
+ require "pathname"
4
+ require "set"
5
+
6
+ require_relative "pipe_operator/closure"
7
+ require_relative "pipe_operator/observer"
8
+ require_relative "pipe_operator/pipe"
9
+ require_relative "pipe_operator/proxy"
10
+ require_relative "pipe_operator/proxy_resolver"
11
+
12
+ module PipeOperator
13
+ def __pipe__(*args, &block)
14
+ Pipe.new(self, *args, &block)
15
+ end
16
+ alias | __pipe__
17
+ alias pipe __pipe__
18
+
19
+ refine(::BasicObject) { include PipeOperator }
20
+
21
+ class << self
22
+ def gem
23
+ @gem ||= ::Gem::Specification.load("#{root}/pipe_operator.gemspec")
24
+ end
25
+
26
+ def inspect(object)
27
+ object.inspect
28
+ rescue ::NoMethodError
29
+ singleton = singleton(object)
30
+ name = singleton.name || singleton.superclass.name
31
+ id = "0x0000%x" % (object.__id__ << 1)
32
+ "#<#{name}:#{id}>"
33
+ end
34
+
35
+ def root
36
+ @root ||= ::Pathname.new(__dir__).join("..")
37
+ end
38
+
39
+ def singleton(object)
40
+ (class << object; self end)
41
+ rescue ::TypeError
42
+ object.class
43
+ end
44
+
45
+ def version
46
+ @version ||= gem.version.to_s
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ require_relative "../pipe_operator"
2
+
3
+ BasicObject.send(:include, PipeOperator)
@@ -0,0 +1,66 @@
1
+ module PipeOperator
2
+ class Closure < ::Proc
3
+ RESERVED = %i[
4
+ ==
5
+ []
6
+ __send__
7
+ call
8
+ class
9
+ kind_of?
10
+ ].freeze
11
+
12
+ (::Proc.instance_methods - RESERVED).each(&method(:private))
13
+
14
+ def self.curry(curry, search, args)
15
+ index = curry.index(search)
16
+ prefix = index ? curry[0...index] : curry
17
+ suffix = index ? curry[index - 1..-1] : []
18
+
19
+ (prefix + args + suffix).map do |object|
20
+ self === object ? object.call : object
21
+ end
22
+ end
23
+
24
+ def self.new(pipe = nil, method = nil, *curry, &block)
25
+ return super(&block) unless pipe && method
26
+
27
+ search = Pipe.open || pipe
28
+
29
+ closure = super() do |*args, &code|
30
+ code ||= block
31
+ curried = curry(curry, search, args)
32
+ value = pipe.__call__.__send__(method, *curried, &code)
33
+ closure.__chain__(value)
34
+ end
35
+ end
36
+
37
+ def initialize(*) # :nodoc:
38
+ @__chain__ ||= []
39
+ super
40
+ end
41
+
42
+ def __chain__(*args)
43
+ return @__chain__ if args.empty?
44
+
45
+ @__chain__.reduce(args[0]) do |object, chain|
46
+ method, args, block = chain
47
+ object.__send__(method, *args, &block)
48
+ end
49
+ end
50
+
51
+ def __shift__
52
+ closure = self.class.new do |*args, &block|
53
+ args.shift
54
+ value = call(*args, &block)
55
+ closure.__chain__(value)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def method_missing(method, *args, &block)
62
+ __chain__ << [method, args, block]
63
+ self
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,13 @@
1
+ module PipeOperator
2
+ module Observer
3
+ def singleton_method_added(method)
4
+ ProxyResolver.new(self).proxy.define(method)
5
+ super
6
+ end
7
+
8
+ def singleton_method_removed(method)
9
+ ProxyResolver.new(self).proxy.undefine(method)
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ module PipeOperator
2
+ class Pipe < ::BasicObject
3
+ undef :equal?
4
+ undef :instance_eval
5
+ undef :singleton_method_added
6
+ undef :singleton_method_removed
7
+ undef :singleton_method_undefined
8
+
9
+ def self.new(object, *args)
10
+ if block_given?
11
+ super.__call__
12
+ elsif args.none? || Closure === args[0]
13
+ super
14
+ else
15
+ super(object).__send__(*args)
16
+ end
17
+ end
18
+
19
+ def self.open(pipe = nil)
20
+ @pipeline ||= []
21
+ @pipeline << pipe if pipe
22
+ block_given? ? yield : @pipeline.last
23
+ ensure
24
+ @pipeline.pop if pipe
25
+ end
26
+
27
+ def initialize(object, *args, &block)
28
+ @args = args
29
+ @block = block
30
+ @object = object
31
+ @pipeline = []
32
+ end
33
+
34
+ def __call__
35
+ if defined?(@pipe)
36
+ return @pipe
37
+ elsif @block
38
+ ProxyResolver.new(::Object).proxy
39
+ @args.each { |arg| ProxyResolver.new(arg).proxy }
40
+ Pipe.open(self) { instance_exec(*@args, &@block) }
41
+ end
42
+
43
+ @pipe = @object
44
+ @pipeline.each { |closure| @pipe = closure.call(@pipe) }
45
+ @pipe
46
+ end
47
+
48
+ def inspect
49
+ return method_missing(__method__) if Pipe.open
50
+ inspect = ::PipeOperator.inspect(@object)
51
+ "#<#{Pipe.name}:#{inspect}>"
52
+ end
53
+
54
+ protected
55
+
56
+ def __pop__(pipe)
57
+ index = @pipeline.rindex(pipe)
58
+ @pipeline.delete_at(index) if index
59
+ end
60
+
61
+ def __push__(pipe)
62
+ @pipeline << pipe
63
+ pipe
64
+ end
65
+
66
+ def |(*)
67
+ self
68
+ end
69
+
70
+ private
71
+
72
+ def method_missing(method, *curry, &block)
73
+ closure = Closure.new(self, method, *curry, &block)
74
+
75
+ pipe = Pipe.open
76
+ pipe && [*curry, block].each { |o| pipe.__pop__(o) }
77
+
78
+ if pipe == self
79
+ __push__(closure.__shift__)
80
+ elsif pipe
81
+ pipe.__push__(closure)
82
+ else
83
+ closure
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,58 @@
1
+ module PipeOperator
2
+ class Proxy < ::Module
3
+ def initialize(object, singleton)
4
+ @object = object if singleton.singleton_class?
5
+ @singleton = singleton
6
+ super()
7
+ end
8
+
9
+ def define(method)
10
+ if ::Proc == @object && method == :new
11
+ return method
12
+ elsif ::Symbol == @singleton && method == :to_proc
13
+ return method
14
+ elsif ::Module === @object
15
+ namespace = @object.name.to_s.split("::").first
16
+ return method if namespace == "PipeOperator"
17
+ end
18
+
19
+ define_method(method) do |*args, &block|
20
+ if Pipe.open
21
+ Pipe.new(self).__send__(method, *args, &block)
22
+ else
23
+ super(*args, &block)
24
+ end
25
+ end
26
+ end
27
+
28
+ def definitions
29
+ instance_methods(false).sort
30
+ end
31
+
32
+ def inspect
33
+ inspect =
34
+ if @singleton.singleton_class?
35
+ ::PipeOperator.inspect(@object)
36
+ else
37
+ "#<#{@singleton.name}>"
38
+ end
39
+
40
+ "#<#{self.class.name}:#{inspect}>"
41
+ end
42
+
43
+ def prepended(*)
44
+ if is_a?(Proxy)
45
+ methods = @singleton.instance_methods(false)
46
+ methods.each { |method| define(method) }
47
+ end
48
+
49
+ super
50
+ end
51
+
52
+ def undefine(method)
53
+ remove_method(method)
54
+ rescue ::NameError # ignore
55
+ method
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ module PipeOperator
2
+ class ProxyResolver
3
+ AUTOLOAD = ENV["PIPE_OPERATOR_AUTOLOAD"] == "1"
4
+ FROZEN = ENV["PIPE_OPERATOR_FROZEN"] == "1"
5
+ REBIND = ENV["PIPE_OPERATOR_REBIND"] == "1"
6
+
7
+ def initialize(object, resolved = ::Set.new)
8
+ @object = object
9
+ @resolved = resolved
10
+ @singleton = ::PipeOperator.singleton(object)
11
+ end
12
+
13
+ def proxy
14
+ proxy = find_existing_proxy
15
+ return proxy if proxy && !REBIND
16
+ proxy ||= create_proxy
17
+ rebind_nested_constants
18
+ proxy
19
+ end
20
+
21
+ private
22
+
23
+ def find_existing_proxy
24
+ @singleton.ancestors.each do |existing|
25
+ break if @singleton == existing
26
+ return existing if Proxy === existing
27
+ end
28
+ end
29
+
30
+ def create_proxy
31
+ Proxy.new(@object, @singleton).tap do |proxy|
32
+ @resolved.add(proxy)
33
+
34
+ if !@singleton.frozen?
35
+ @singleton.prepend(Observer).prepend(proxy)
36
+ elsif FROZEN
37
+ id = @singleton.__id__ * 2
38
+ unfreeze = ~(1 << 3)
39
+ ::Fiddle::Pointer.new(id)[1] &= unfreeze
40
+ @singleton.prepend(Observer).prepend(proxy)
41
+ @singleton.freeze
42
+ end
43
+ end
44
+ end
45
+
46
+ def rebind_nested_constants
47
+ context = ::Module === @object ? @object : @singleton
48
+
49
+ context.constants.map do |constant|
50
+ next unless context.const_defined?(constant, AUTOLOAD)
51
+
52
+ constant = silence_deprecations do
53
+ context.const_get(constant, false) rescue next
54
+ end
55
+
56
+ next if constant.eql?(@object) # recursion
57
+ next unless @resolved.add?(constant)
58
+
59
+ self.class.new(constant, @resolved).proxy
60
+ end
61
+ end
62
+
63
+ def silence_deprecations
64
+ stderr = $stderr
65
+ $stderr = ::StringIO.new
66
+ yield
67
+ ensure
68
+ $stderr = stderr
69
+ end
70
+ end
71
+ end