railway-rb 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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/railway.rb +121 -0
  3. metadata +43 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8698c98d0e081c760f0c652d7875bf1ad9050f36989de032187156e68c6c6a90
4
+ data.tar.gz: 79a12dd5fd91fb69afb56e10f737939659ea2a5937f3abc83debc8c9d5cc380c
5
+ SHA512:
6
+ metadata.gz: 859724aff46f8909025681cb3a48eb01b5f7543c5ec00ec736ac6aa07ed4709fe732e205e869a0e837bb828dbebf4c3e4aece4d0433b348f934cbe9328a41dd1
7
+ data.tar.gz: 6b2dd0ca7b4e48e692dd324958905818b53e22879dbbbea39ed6ebff8c0f9178af38af8dc06dffae239c9180541e284f887de28d5778933f2a3c6ad61073bbe2
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Include this module to enable Railway syntactic expression.
4
+ # Example:
5
+ #
6
+ # class Lorem
7
+ # include Railway
8
+ #
9
+ # def greeting(person)
10
+ # with { person }.as var(:pax).when { pax.is_a? String }
11
+ # .then { "Hello, #{pax}" }.as var(:message) do
12
+ # pp message
13
+ # end.run
14
+ # end
15
+ # end
16
+ #
17
+ module Railway
18
+ def var(sym, setter: nil)
19
+ block = BuildingBlock.new(name: sym, setter_name: setter)
20
+ Builder.new(block)
21
+ end
22
+
23
+ def with(&assign_block)
24
+ block = BuildingBlock.new(assign_block: assign_block)
25
+ Builder.new(block, self)
26
+ end
27
+
28
+ BuildingBlock = Struct.new(
29
+ :name, :setter_name, :assign_block, :guard_block, :extra_blocks,
30
+ keyword_init: true
31
+ )
32
+
33
+ # This class is used to combine and execute building block properly
34
+ #
35
+ class Builder
36
+ attr_reader :blocks
37
+
38
+ def initialize(starting_block, ctx = nil)
39
+ @blocks = [starting_block]
40
+ @context = ctx
41
+ end
42
+
43
+ def when(&block)
44
+ blocks.first.guard_block = block
45
+ self
46
+ end
47
+
48
+ def then(&block)
49
+ blocks.last.extra_blocks = [*blocks.last.extra_blocks, next_assign_block] if next_assign_block
50
+
51
+ @next_assign_block = block
52
+ self
53
+ end
54
+
55
+ def as(next_builder, &block) # rubocop:disable Metrics/MethodLength
56
+ if block
57
+ @accept_block = block
58
+ assign_block = blocks.first.assign_block
59
+ @blocks = next_builder.blocks.clone
60
+ blocks.first.assign_block = assign_block
61
+ else
62
+ next_blocks = next_builder.blocks.clone
63
+ next_blocks.first.assign_block = next_assign_block
64
+ @blocks += next_blocks
65
+ @next_assign_block = nil
66
+ end
67
+ self
68
+ end
69
+
70
+ def otherwise(&block)
71
+ @reject_block = block if block
72
+ self
73
+ end
74
+
75
+ def run
76
+ @result = Struct.new(*blocks.map(&:name)).new
77
+
78
+ blocks.each do |block|
79
+ apply_block(block)
80
+ return reject(block) unless continue_block?(block)
81
+
82
+ [*block.extra_blocks].each { |b| instance_exec(&b) }
83
+ end
84
+
85
+ instance_exec(&accept_block)
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :context, :next_assign_block, :result, :accept_block, :reject_block
91
+
92
+ def apply_block(block)
93
+ sym = block.name
94
+ result[sym] = instance_exec(&block.assign_block)
95
+ return if !context || !block.setter_name
96
+
97
+ context.instance_variable_set("@#{block.setter_name}".to_sym, result[sym])
98
+ end
99
+
100
+ def continue_block?(block)
101
+ !block.guard_block || instance_exec(&block.guard_block)
102
+ end
103
+
104
+ def reject(block)
105
+ sym = block.name
106
+ reject_block && instance_exec(sym, result[sym], &reject_block)
107
+ end
108
+
109
+ def method_missing(a_method, *args, &block)
110
+ return result[a_method] if result&.members&.include?(a_method)
111
+
112
+ return context.send(a_method, *args, &block) if context.respond_to?(a_method, true)
113
+
114
+ super(a_method, *args, &block)
115
+ end
116
+
117
+ def respond_to_missing?(a_method, *)
118
+ result&.members&.include?(a_method) || context.respond_to?(a_method)
119
+ end
120
+ end
121
+ end
metadata ADDED
@@ -0,0 +1,43 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: railway-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Hoan Nguyen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-12-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A functional approach to error handling. Inspired by Elixir `with` statement.
14
+ email: hoan006@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/railway.rb
20
+ homepage: http://github.com/hoan006
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.4'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubygems_version: 3.1.2
40
+ signing_key:
41
+ specification_version: 4
42
+ summary: Railway-Oriented Programming!
43
+ test_files: []