pipe_operator 0.0.1

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/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