ovto 0.5.0 → 0.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +5 -5
  4. data/book/SUMMARY.md +1 -0
  5. data/book/api/pure_component.md +3 -0
  6. data/docs/api/Ovto.html +4 -4
  7. data/docs/api/Ovto/Actions.html +3 -3
  8. data/docs/api/Ovto/App.html +3 -3
  9. data/docs/api/Ovto/Component.html +3 -3
  10. data/docs/api/Ovto/Component/MoreThanOneNode.html +3 -3
  11. data/docs/api/Ovto/PureComponent.html +3 -3
  12. data/docs/api/Ovto/PureComponent/StateIsNotAvailable.html +3 -3
  13. data/docs/api/Ovto/Runtime.html +3 -3
  14. data/docs/api/Ovto/State.html +3 -3
  15. data/docs/api/Ovto/State/MissingValue.html +3 -3
  16. data/docs/api/Ovto/State/UnknownKey.html +3 -3
  17. data/docs/api/Ovto/WiredActions.html +3 -3
  18. data/docs/api/_index.html +4 -4
  19. data/docs/api/actions.html +15 -2
  20. data/docs/api/app.html +15 -2
  21. data/docs/api/component.html +17 -4
  22. data/docs/api/fetch.html +17 -4
  23. data/docs/api/file.README.html +81 -77
  24. data/docs/api/frames.html +1 -1
  25. data/docs/api/index.html +81 -77
  26. data/docs/api/pure_component.html +415 -0
  27. data/docs/api/state.html +15 -2
  28. data/docs/api/top-level-namespace.html +3 -3
  29. data/docs/guides/debugging.html +15 -2
  30. data/docs/guides/development.html +15 -2
  31. data/docs/guides/install.html +15 -2
  32. data/docs/guides/tutorial.html +15 -2
  33. data/docs/index.html +15 -2
  34. data/docs/search_index.json +1 -1
  35. data/lib/ovto.rb +25 -0
  36. data/lib/ovto/actions.rb +11 -0
  37. data/lib/ovto/app.rb +37 -13
  38. data/lib/ovto/component.rb +127 -67
  39. data/lib/ovto/middleware.rb +121 -0
  40. data/lib/ovto/pure_component.rb +1 -1
  41. data/lib/ovto/state.rb +13 -5
  42. data/lib/ovto/version.rb +1 -1
  43. data/lib/ovto/wired_action_set.rb +40 -0
  44. data/lib/ovto/wired_actions.rb +49 -11
  45. metadata +7 -5
  46. data/docs/api/pure_component.md +0 -27
@@ -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
@@ -2,7 +2,7 @@ module Ovto
2
2
  class PureComponent < Component
3
3
  class StateIsNotAvailable < StandardError; end
4
4
 
5
- def initialize(wired_actions)
5
+ def initialize(*args)
6
6
  super
7
7
  @prev_props = nil
8
8
  @cache = nil
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Ovto
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0.rc1'
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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ovto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yutaka HARA
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-22 00:00:00.000000000 Z
11
+ date: 2020-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -117,7 +117,7 @@ files:
117
117
  - docs/api/js/full_list.js
118
118
  - docs/api/js/jquery.js
119
119
  - docs/api/method_list.html
120
- - docs/api/pure_component.md
120
+ - docs/api/pure_component.html
121
121
  - docs/api/state.html
122
122
  - docs/api/top-level-namespace.html
123
123
  - docs/gitbook/fonts/fontawesome/FontAwesome.otf
@@ -173,10 +173,12 @@ files:
173
173
  - lib/ovto/app.rb
174
174
  - lib/ovto/component.rb
175
175
  - lib/ovto/fetch.rb
176
+ - lib/ovto/middleware.rb
176
177
  - lib/ovto/pure_component.rb
177
178
  - lib/ovto/runtime.rb
178
179
  - lib/ovto/state.rb
179
180
  - lib/ovto/version.rb
181
+ - lib/ovto/wired_action_set.rb
180
182
  - lib/ovto/wired_actions.rb
181
183
  - ovto.gemspec
182
184
  - ovtologo.odg
@@ -198,9 +200,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
200
  version: '0'
199
201
  required_rubygems_version: !ruby/object:Gem::Requirement
200
202
  requirements:
201
- - - ">="
203
+ - - ">"
202
204
  - !ruby/object:Gem::Version
203
- version: '0'
205
+ version: 1.3.1
204
206
  requirements: []
205
207
  rubyforge_project:
206
208
  rubygems_version: 2.7.6.2
@@ -1,27 +0,0 @@
1
- # Ovto::PureComponent
2
-
3
- It almost the same as `Ovto::Component`, but it caches the `render` method calling with arguments of the method.
4
-
5
-
6
- ## Cache strategy
7
-
8
- It compares `render` method arguments and the previous arguments.
9
-
10
- ```rb
11
- def render
12
- o 'div' do
13
- o Pure, foo: state.foo
14
- o NotPure bar: state.bar
15
- end
16
- end
17
- ```
18
-
19
- In this case, `NotPure` component's render method is called even if `state.foo` is changed.
20
- Whereas `Pure` component's render method is called only if `state.foo` is changed.
21
-
22
-
23
- ## State
24
-
25
- `state` method is not available in `PureComponent`, because `PureComponent` does not treat state as the cache key.
26
- If you'd like to use state in `PureComponent`, pass the state from the parent component.
27
-