ovto 0.4.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.gitmodules +3 -0
  4. data/CHANGELOG.md +15 -2
  5. data/Gemfile +2 -2
  6. data/Gemfile.lock +23 -27
  7. data/README.md +2 -2
  8. data/book/SUMMARY.md +3 -1
  9. data/book/api/actions.md +1 -1
  10. data/book/api/fetch.md +3 -3
  11. data/book/api/middleware.md +99 -0
  12. data/book/api/pure_component.md +30 -0
  13. data/book/guides/install.md +76 -0
  14. data/book/guides/tutorial.md +3 -3
  15. data/book/ovtologo.png +0 -0
  16. data/docs/api/Array.html +190 -0
  17. data/docs/api/Hash.html +196 -0
  18. data/docs/api/Ovto/Actions.html +132 -40
  19. data/docs/api/Ovto/App.html +254 -40
  20. data/docs/api/Ovto/Component/MoreThanOneNode.html +6 -6
  21. data/docs/api/Ovto/Component.html +102 -27
  22. data/docs/api/Ovto/Middleware/Actions.html +428 -0
  23. data/docs/api/Ovto/Middleware/Base.html +606 -0
  24. data/docs/api/Ovto/Middleware/Component.html +355 -0
  25. data/docs/api/Ovto/Middleware.html +288 -0
  26. data/docs/api/Ovto/PureComponent/StateIsNotAvailable.html +124 -0
  27. data/docs/api/Ovto/PureComponent.html +368 -0
  28. data/docs/api/Ovto/Runtime.html +7 -7
  29. data/docs/api/Ovto/State/MissingValue.html +6 -6
  30. data/docs/api/Ovto/State/{UnknownKey.html → UnknownStateKey.html} +11 -11
  31. data/docs/api/Ovto/State.html +59 -43
  32. data/docs/api/Ovto/WiredActionSet.html +636 -0
  33. data/docs/api/Ovto/WiredActions.html +78 -35
  34. data/docs/api/Ovto.html +226 -29
  35. data/docs/api/_index.html +99 -11
  36. data/docs/api/actions.html +36 -10
  37. data/docs/api/app.html +35 -9
  38. data/docs/api/class_list.html +3 -3
  39. data/docs/api/component.html +37 -11
  40. data/docs/api/css/style.css +2 -2
  41. data/docs/api/fetch.html +40 -14
  42. data/docs/api/file.README.html +8 -8
  43. data/docs/api/file_list.html +2 -2
  44. data/docs/api/frames.html +2 -2
  45. data/docs/api/index.html +8 -8
  46. data/docs/api/js/app.js +14 -3
  47. data/docs/api/method_list.html +318 -22
  48. data/docs/api/middleware.html +498 -0
  49. data/docs/api/pure_component.html +428 -0
  50. data/docs/api/state.html +35 -9
  51. data/docs/api/top-level-namespace.html +9 -7
  52. data/docs/gitbook/gitbook.js +2 -2
  53. data/docs/gitbook/theme.js +1 -1
  54. data/docs/guides/debugging.html +35 -9
  55. data/docs/guides/development.html +35 -9
  56. data/docs/guides/install.html +452 -0
  57. data/docs/guides/tutorial.html +101 -66
  58. data/docs/index.html +37 -17
  59. data/docs/ovtologo.png +0 -0
  60. data/docs/search_index.json +1 -1
  61. data/examples/sinatra/Gemfile +2 -0
  62. data/examples/sinatra/Gemfile.lock +33 -31
  63. data/examples/sinatra/app.rb +1 -4
  64. data/examples/sinatra/config.ru +6 -6
  65. data/examples/static/Gemfile.lock +10 -14
  66. data/lib/ovto/actions.rb +11 -0
  67. data/lib/ovto/app.rb +37 -13
  68. data/lib/ovto/component.rb +149 -73
  69. data/lib/ovto/fetch.rb +1 -1
  70. data/lib/ovto/middleware.rb +121 -0
  71. data/lib/ovto/pure_component.rb +22 -0
  72. data/lib/ovto/state.rb +13 -5
  73. data/lib/ovto/version.rb +1 -1
  74. data/lib/ovto/wired_action_set.rb +40 -0
  75. data/lib/ovto/wired_actions.rb +49 -11
  76. data/lib/ovto.rb +27 -0
  77. data/ovto.gemspec +1 -1
  78. metadata +35 -9
@@ -0,0 +1,121 @@
1
+ module Ovto
2
+ VALID_NAME_REXP = /\A[a-zA-Z0-9_]+\z/
3
+ # Create an ancestor of middleware class
4
+ # Example:
5
+ # class MiddlewareExample < Ovto::Middleware("middleware_example")
6
+ def self.Middleware(name)
7
+ unless VALID_NAME_REXP =~ name
8
+ raise "invalid middleware name: #{name}"
9
+ end
10
+ return Class.new(Ovto::Middleware::Base){
11
+ const_set(:OVTO_MIDDLEWARE_NAME, name)
12
+ const_set(:State, Ovto::State)
13
+ const_set(:Actions, Class.new(Ovto::Middleware::Actions))
14
+ const_get(:Actions).const_set(:OVTO_MIDDLEWARE_NAME, name)
15
+ const_set(:Component, Class.new(Ovto::Middleware::Component))
16
+ const_get(:Component).const_set(:OVTO_MIDDLEWARE_NAME, name)
17
+ }
18
+ end
19
+
20
+ module Middleware
21
+ # (internal) Create a subclass of Ovto::State that handles
22
+ # states of `middlewares`
23
+ # Called from App#initialize
24
+ def self.create_middleware_states_class(middlewares)
25
+ return Class.new(Ovto::State){
26
+ middlewares.each do |m|
27
+ item m.name, default_proc: ->{
28
+ mw_state_class = m.const_get('State')
29
+ mw_state_class.item :_middlewares, default_proc: -> {
30
+ Ovto::Middleware.create_middleware_states_class(m.middlewares).new
31
+ }
32
+ mw_state_class.new
33
+ }
34
+ end
35
+ }
36
+ end
37
+
38
+ # Get the state of the middleware specified with `middleware_path`
39
+ def self.dig_middleware_state(app_state, middleware_path)
40
+ return middleware_path.inject(app_state){|state, middleware_name|
41
+ state._middlewares.__send__(middleware_name)
42
+ }
43
+ end
44
+ end
45
+
46
+ # Base class of a middleware class
47
+ # Note: this is not the direct superclass of a middleware.
48
+ # `SomeMiddleware < (anonymous class) < Middleware::Base`
49
+ class Middleware::Base
50
+ # Nested middlewares
51
+ def self.middlewares
52
+ @middlewares ||= []
53
+ end
54
+
55
+ # Install a nested middleware
56
+ def self.use(middleware_class)
57
+ self.middlewares.push(middleware_class)
58
+ end
59
+
60
+ # Middleware name (set by Ovto.Middleware)
61
+ def self.name
62
+ const_get(:OVTO_MIDDLEWARE_NAME)
63
+ end
64
+
65
+ def self._run_setup(wired_action_set)
66
+ mw = new(wired_action_set[WiredActionSet::THE_MIDDLEWARE_ITSELF])
67
+ mw.setup
68
+ self.middlewares.each do |m|
69
+ m._run_setup(wired_action_set[m.name])
70
+ end
71
+ end
72
+
73
+ def initialize(wired_actions)
74
+ @wired_actions = wired_actions
75
+ end
76
+
77
+ # Override this if needed
78
+ def setup
79
+ end
80
+
81
+ def actions
82
+ @wired_actions
83
+ end
84
+ end
85
+
86
+ class Middleware::Actions < Ovto::Actions
87
+ def initialize(middleware_path)
88
+ @middleware_path = middleware_path
89
+ end
90
+ attr_reader :middleware_path
91
+
92
+ # The name of the middleware this Actions belongs to
93
+ def middleware_name
94
+ self.class::OVTO_MIDDLEWARE_NAME
95
+ end
96
+
97
+ def state
98
+ app_state = super
99
+ return Ovto::Middleware.dig_middleware_state(app_state, @middleware_path)
100
+ end
101
+ end
102
+
103
+ # Base class of middleware component
104
+ # Basically the same as Ovto::Component but `actions` is wired to
105
+ # middleware actions.
106
+ class Middleware::Component < Ovto::Component
107
+ def self.middleware_name
108
+ self::OVTO_MIDDLEWARE_NAME
109
+ end
110
+
111
+ # The name of the middleware this component belongs to
112
+ def middleware_name
113
+ self.class::OVTO_MIDDLEWARE_NAME
114
+ end
115
+
116
+ def state
117
+ app_state = super
118
+ return Ovto::Middleware.dig_middleware_state(app_state, @middleware_path)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,22 @@
1
+ module Ovto
2
+ class PureComponent < Component
3
+ class StateIsNotAvailable < StandardError; end
4
+
5
+ def initialize(*args)
6
+ super
7
+ @prev_props = nil
8
+ @cache = nil
9
+ end
10
+
11
+ def do_render(args, state)
12
+ return @cache if args == @prev_props
13
+
14
+ @prev_props = args
15
+ @cache = super
16
+ end
17
+
18
+ def state
19
+ raise StateIsNotAvailable, "Cannot use state in PureComponent"
20
+ end
21
+ end
22
+ end
data/lib/ovto/state.rb CHANGED
@@ -3,7 +3,7 @@ module Ovto
3
3
  # Mandatory value is omitted in the argument of State.new
4
4
  class MissingValue < StandardError; end
5
5
  # Unknown key is given
6
- class UnknownKey < StandardError; end
6
+ class UnknownStateKey < StandardError; end
7
7
 
8
8
  # (internal) initialize subclass
9
9
  def self.inherited(subclass)
@@ -28,15 +28,23 @@ module Ovto
28
28
  def initialize(hash = {})
29
29
  unknown_keys = hash.keys - self.class.item_specs.map(&:first)
30
30
  if unknown_keys.any?
31
- raise UnknownKey, "unknown key(s): #{unknown_keys.inspect}"
31
+ raise UnknownStateKey, "unknown key(s): #{unknown_keys.inspect}"
32
32
  end
33
33
 
34
34
  @values = self.class.item_specs.map{|name, options|
35
- if !hash.key?(name) && !options.key?(:default)
35
+ if !hash.key?(name) && !options.key?(:default) && !options.key?(:default_proc)
36
36
  raise MissingValue, ":#{name} is mandatory for #{self.class.name}.new"
37
37
  end
38
38
  # Note that `hash[key]` may be false or nil
39
- value = hash.key?(name) ? hash[name] : options[:default]
39
+ value = if hash.key?(name)
40
+ hash[name]
41
+ elsif options.key?(:default)
42
+ options[:default]
43
+ elsif options.key?(:default_proc)
44
+ options[:default_proc].call
45
+ else
46
+ raise "must not happen"
47
+ end
40
48
  [name, value]
41
49
  }.to_h
42
50
  end
@@ -46,7 +54,7 @@ module Ovto
46
54
  def merge(hash)
47
55
  unknown_keys = hash.keys - self.class.item_specs.map(&:first)
48
56
  if unknown_keys.any?
49
- raise UnknownKey, "unknown key(s): #{unknown_keys.inspect}"
57
+ raise UnknownStateKey, "unknown key(s): #{unknown_keys.inspect}"
50
58
  end
51
59
  self.class.new(@values.merge(hash))
52
60
  end
data/lib/ovto/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ovto
2
- VERSION = '0.4.1'
2
+ VERSION = '0.6.1'
3
3
  end
@@ -0,0 +1,40 @@
1
+ require 'set'
2
+
3
+ module Ovto
4
+ # Set of WiredActions (One for the app, zero or more for middlewares)
5
+ class WiredActionSet
6
+ # Special key for @hash
7
+ I_AM_APP_NOT_A_MIDDLEWARE = ''
8
+ THE_MIDDLEWARE_ITSELF = ''
9
+
10
+ # For testing
11
+ def self.dummy()
12
+ new(nil, nil, [], [], nil)
13
+ end
14
+
15
+ def initialize(app, actions, middleware_path, middlewares, runtime)
16
+ @app = app
17
+ @hash = {}
18
+ @hash[THE_MIDDLEWARE_ITSELF] = WiredActions.new(actions, app, runtime, self)
19
+ middlewares.each do |m|
20
+ mw_path = middleware_path + [m.name]
21
+ mw_actions = m.const_get('Actions').new(mw_path)
22
+ mw_wired_action_set = WiredActionSet.new(app, mw_actions, mw_path, m.middlewares, runtime)
23
+ @hash[m.name] = mw_wired_action_set
24
+ mw_actions.wired_actions = mw_wired_action_set[THE_MIDDLEWARE_ITSELF]
25
+ end
26
+ @middleware_names = middlewares.map(&:name).to_set
27
+ end
28
+ attr_reader :app, :middleware_names
29
+
30
+ # Return the WiredActions of the app
31
+ def app_wired_actions
32
+ @hash[I_AM_APP_NOT_A_MIDDLEWARE]
33
+ end
34
+
35
+ # Return the WiredActions of a middleware
36
+ def [](name)
37
+ @hash.fetch(name)
38
+ end
39
+ end
40
+ end
@@ -3,18 +3,30 @@ require 'promise'
3
3
 
4
4
  module Ovto
5
5
  class WiredActions
6
- def initialize(actions, app, runtime)
7
- @actions, @app, @runtime = actions, app, runtime
6
+ # Create a WiredActions
7
+ #
8
+ # - actions: Ovto::Actions
9
+ # - app: Ovto::App
10
+ # - runtime: Ovto::Runtime (to call scheduleRender after state change)
11
+ # - parent: WiredActionSet
12
+ def initialize(actions, app, runtime, parent)
13
+ @actions, @app, @runtime, @parent = actions, app, runtime, parent
8
14
  end
9
15
 
10
16
  def method_missing(name, args_hash={})
11
- Ovto.log_error {
12
- invoke_action(name, args_hash)
13
- }
17
+ raise NoMethodError, "undefined method `#{name}' on #{self}" unless respond_to?(name)
18
+ if @actions.respond_to?(name)
19
+ Ovto.log_error {
20
+ Ovto.debug_trace_log("invoke action \"#{name}\" on #{@actions.class}")
21
+ invoke_action(name, args_hash)
22
+ }
23
+ else
24
+ @parent[name][WiredActionSet::THE_MIDDLEWARE_ITSELF] # WiredActions of a middleware named `name`
25
+ end
14
26
  end
15
27
 
16
28
  def respond_to?(name)
17
- @actions.respond_to?(name)
29
+ @actions.respond_to?(name) || @parent.middleware_names.include?(name)
18
30
  end
19
31
 
20
32
  # internal
@@ -26,8 +38,7 @@ module Ovto
26
38
 
27
39
  # Call action and schedule rendering
28
40
  def invoke_action(name, args_hash)
29
- kwargs = {state: @app.state}.merge(args_hash)
30
- state_diff = @actions.__send__(name, **kwargs)
41
+ state_diff = Ovto.send_args_with_state(@actions, name, args_hash, current_state)
31
42
  return if state_diff.nil? ||
32
43
  state_diff.is_a?(Promise) || `!!state_diff.then` ||
33
44
  # eg.
@@ -42,12 +53,39 @@ module Ovto
42
53
  unless state_diff.is_a?(Hash)
43
54
  raise "action `#{name}' must return hash but got #{state_diff.inspect}"
44
55
  end
45
- new_state = @app.state.merge(state_diff)
46
- if new_state != @app.state
56
+ new_state = current_state.merge(state_diff)
57
+ if new_state != current_state
47
58
  @runtime.scheduleRender
48
- @app._set_state(new_state)
59
+ update_state(new_state)
49
60
  end
50
61
  return new_state
51
62
  end
63
+
64
+ def middleware_name
65
+ @actions.middleware_name
66
+ end
67
+
68
+ def current_state
69
+ @actions.state
70
+ end
71
+
72
+ def update_state(new_item)
73
+ new_app_state = _new_app_state(@actions.middleware_path, @app.state, new_item)
74
+ @app._set_state(new_app_state)
75
+ end
76
+
77
+ def _new_app_state(middleware_path, old_state, new_item)
78
+ if middleware_path.empty?
79
+ return new_item
80
+ else
81
+ first, *rest = *middleware_path
82
+ orig_state = old_state._middlewares.__send__(first)
83
+ return old_state.merge(
84
+ _middlewares: old_state._middlewares.merge(
85
+ first => _new_app_state(rest, orig_state, new_item)
86
+ )
87
+ )
88
+ end
89
+ end
52
90
  end
53
91
  end
data/lib/ovto.rb CHANGED
@@ -1,13 +1,17 @@
1
1
  if RUBY_ENGINE == 'opal'
2
+ require 'opal'
2
3
  require 'console'; def console; $console; end
3
4
  require_relative 'ovto/actions'
4
5
  require_relative 'ovto/app'
5
6
  require_relative 'ovto/component'
7
+ require_relative 'ovto/pure_component'
6
8
  require_relative 'ovto/fetch'
7
9
  require_relative 'ovto/runtime'
8
10
  require_relative 'ovto/state'
9
11
  require_relative 'ovto/version'
10
12
  require_relative 'ovto/wired_actions'
13
+ require_relative 'ovto/wired_action_set'
14
+ require_relative 'ovto/middleware'
11
15
  else
12
16
  require 'ovto/version'
13
17
  require 'opal'; Opal.append_path(__dir__)
@@ -55,4 +59,27 @@ module Ovto
55
59
  end
56
60
  raise ex
57
61
  end
62
+
63
+ # Something like `obj.meth(state: state, **args, &block)`
64
+ # Safe even if `obj.meth` does not have `state:`
65
+ def self.send_args_with_state(obj, meth, args, state, &block)
66
+ parameters = obj.method(meth).parameters
67
+ accepts_state = `!parameters` || parameters.nil? || parameters.any?{|item|
68
+ item == [:key, :state] ||
69
+ item == [:keyreq, :state] ||
70
+ item[0] == :keyrest
71
+ }
72
+ if accepts_state
73
+ # We can pass `state:` safely
74
+ args_with_state = {state: state}.merge(args)
75
+ return obj.__send__(meth, **args_with_state, &block)
76
+ else
77
+ # Check it is empty (see https://github.com/opal/opal/issues/1872)
78
+ if args.empty?
79
+ return obj.__send__(meth, &block)
80
+ else
81
+ return obj.__send__(meth, **args, &block)
82
+ end
83
+ end
84
+ end
58
85
  end
data/ovto.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "opal", '~> 0.11'
21
+ spec.add_dependency "opal", '>= 0.11', '< 2'
22
22
  spec.add_dependency "thor", '~> 0.20'
23
23
  spec.add_dependency "rack", '~> 2.0'
24
24
  end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ovto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yutaka HARA
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-19 00:00:00.000000000 Z
11
+ date: 2021-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0.11'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0.11'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: thor
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -74,20 +80,33 @@ files:
74
80
  - book/api/app.md
75
81
  - book/api/component.md
76
82
  - book/api/fetch.md
83
+ - book/api/middleware.md
84
+ - book/api/pure_component.md
77
85
  - book/api/state.md
78
86
  - book/guides/debugging.md
79
87
  - book/guides/development.md
88
+ - book/guides/install.md
80
89
  - book/guides/tutorial.md
90
+ - book/ovtologo.png
81
91
  - book/screenshot.png
92
+ - docs/api/Array.html
93
+ - docs/api/Hash.html
82
94
  - docs/api/Ovto.html
83
95
  - docs/api/Ovto/Actions.html
84
96
  - docs/api/Ovto/App.html
85
97
  - docs/api/Ovto/Component.html
86
98
  - docs/api/Ovto/Component/MoreThanOneNode.html
99
+ - docs/api/Ovto/Middleware.html
100
+ - docs/api/Ovto/Middleware/Actions.html
101
+ - docs/api/Ovto/Middleware/Base.html
102
+ - docs/api/Ovto/Middleware/Component.html
103
+ - docs/api/Ovto/PureComponent.html
104
+ - docs/api/Ovto/PureComponent/StateIsNotAvailable.html
87
105
  - docs/api/Ovto/Runtime.html
88
106
  - docs/api/Ovto/State.html
89
107
  - docs/api/Ovto/State/MissingValue.html
90
- - docs/api/Ovto/State/UnknownKey.html
108
+ - docs/api/Ovto/State/UnknownStateKey.html
109
+ - docs/api/Ovto/WiredActionSet.html
91
110
  - docs/api/Ovto/WiredActions.html
92
111
  - docs/api/_index.html
93
112
  - docs/api/actions.html
@@ -106,6 +125,8 @@ files:
106
125
  - docs/api/js/full_list.js
107
126
  - docs/api/js/jquery.js
108
127
  - docs/api/method_list.html
128
+ - docs/api/middleware.html
129
+ - docs/api/pure_component.html
109
130
  - docs/api/state.html
110
131
  - docs/api/top-level-namespace.html
111
132
  - docs/gitbook/fonts/fontawesome/FontAwesome.otf
@@ -132,8 +153,10 @@ files:
132
153
  - docs/gitbook/theme.js
133
154
  - docs/guides/debugging.html
134
155
  - docs/guides/development.html
156
+ - docs/guides/install.html
135
157
  - docs/guides/tutorial.html
136
158
  - docs/index.html
159
+ - docs/ovtologo.png
137
160
  - docs/screenshot.png
138
161
  - docs/search_index.json
139
162
  - examples/sinatra/Gemfile
@@ -159,9 +182,12 @@ files:
159
182
  - lib/ovto/app.rb
160
183
  - lib/ovto/component.rb
161
184
  - lib/ovto/fetch.rb
185
+ - lib/ovto/middleware.rb
186
+ - lib/ovto/pure_component.rb
162
187
  - lib/ovto/runtime.rb
163
188
  - lib/ovto/state.rb
164
189
  - lib/ovto/version.rb
190
+ - lib/ovto/wired_action_set.rb
165
191
  - lib/ovto/wired_actions.rb
166
192
  - ovto.gemspec
167
193
  - ovtologo.odg
@@ -172,7 +198,7 @@ homepage: https://github.com/yhara/ovto
172
198
  licenses:
173
199
  - MIT
174
200
  metadata: {}
175
- post_install_message:
201
+ post_install_message:
176
202
  rdoc_options: []
177
203
  require_paths:
178
204
  - lib
@@ -187,8 +213,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
213
  - !ruby/object:Gem::Version
188
214
  version: '0'
189
215
  requirements: []
190
- rubygems_version: 3.0.3
191
- signing_key:
216
+ rubygems_version: 3.2.22
217
+ signing_key:
192
218
  specification_version: 4
193
219
  summary: Simple client-side framework for Opal
194
220
  test_files: []