taksi 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +112 -0
- data/lib/taksi/component.rb +74 -0
- data/lib/taksi/components/field.rb +75 -0
- data/lib/taksi/components/skeleton.rb +33 -0
- data/lib/taksi/interface.rb +72 -0
- data/lib/taksi/interfaces/skeleton.rb +36 -0
- data/lib/taksi/registry.rb +14 -14
- data/lib/taksi/values/dynamic.rb +11 -7
- data/lib/taksi/values/static.rb +8 -4
- data/lib/taksi/version.rb +1 -1
- data/lib/taksi.rb +5 -5
- metadata +24 -10
- data/lib/taksi/screen.rb +0 -68
- data/lib/taksi/screens/skeleton.rb +0 -36
- data/lib/taksi/widget.rb +0 -69
- data/lib/taksi/widgets/content_key.rb +0 -67
- data/lib/taksi/widgets/skeleton.rb +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c176b6593b9e570169fa5ec6103bfd635c8bae751342ce7b8c986ec3fa43df3d
|
|
4
|
+
data.tar.gz: e84fe608e7b15263084b9f3f745eba7e9cf47d8460ecb02032ad930fe85e1c14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b6b421c3efe426c48c934ccfc9ba2ae385a74fd97cc63fc0c4fbca14070f547ab0003d09abc6fe724ea6dd064d8d49d00a08bf268193850337566b945760be7a
|
|
7
|
+
data.tar.gz: 45fa852395860e6b3ade5d413518d2cca268cefbd9faeb21aa83d269e8c74d5d3d5ed9ba2c7fc27906d9d55222acea5d6f53396f0945ae6e54374704a2c14e05
|
data/README.md
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Taksi [](https://badge.fury.io/rb/taksi) [](https://github.com/taksi-br/taksi-ruby/actions/workflows/ci.yml) [](https://app.codacy.com/gh/taksi-br/taksi-ruby/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
|
|
2
|
+
|
|
3
|
+
Application framework to build a backend driven UI in ruby.
|
|
4
|
+
|
|
5
|
+
## Considerations
|
|
6
|
+
|
|
7
|
+
This repository are in its **very early days** and **not ready for production** yet. If you want to help or understand what it is, get a look over our inspirations on the links below:
|
|
8
|
+
- https://medium.com/movile-tech/backend-driven-development-ios-d1c726f2913b
|
|
9
|
+
- https://engineering.q42.nl/server-driven-ui-at-primephonic/
|
|
10
|
+
- https://www.youtube.com/watch?v=vuCfKjOwZdU
|
|
11
|
+
|
|
12
|
+
Also, we're working in create a protocol documentation to explain the comunication details between frontend and backend.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
In Taksi, every interface are composed by 1 to many components, those components are feed by data provided from the interface definition.
|
|
17
|
+
|
|
18
|
+
Defining a new component:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
class Components::Users::ProfileResume
|
|
22
|
+
include Taksi::Component.new('users/profile_resume')
|
|
23
|
+
|
|
24
|
+
content do
|
|
25
|
+
name Taksi::Dynamic
|
|
26
|
+
|
|
27
|
+
details do
|
|
28
|
+
age Taksi::Dynamic
|
|
29
|
+
email Taksi::Dynamic
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Defining a new interface (in this example a interface interface):
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class Interfaces::UserProfile
|
|
39
|
+
include Taksi::Interface.new('user_profile')
|
|
40
|
+
|
|
41
|
+
add Components::Users::ProfileResume, with: :profile_data
|
|
42
|
+
|
|
43
|
+
attr_accessor :user
|
|
44
|
+
|
|
45
|
+
def profile_data
|
|
46
|
+
{
|
|
47
|
+
name: user.name,
|
|
48
|
+
details: {
|
|
49
|
+
age: user.age,
|
|
50
|
+
email: user.email,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
From those definitions you can set up the skeleton or strip the data:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
user_profile = Interfaces::UserProfile.new
|
|
61
|
+
user_profile.skeleton.as_json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Which provide us:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"components": [
|
|
69
|
+
{
|
|
70
|
+
"name": "users/profile_resume",
|
|
71
|
+
"identifier": "component$0",
|
|
72
|
+
"content": {
|
|
73
|
+
"name": null,
|
|
74
|
+
"details": {
|
|
75
|
+
"age": null,
|
|
76
|
+
"email": null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then, you can strip the data off:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
user_profile.user = User.find(logged_user_id)
|
|
88
|
+
user_profile.data.as_json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"interface_data": [
|
|
94
|
+
{
|
|
95
|
+
"identifier": "component$0",
|
|
96
|
+
"content": {
|
|
97
|
+
"name": "Israel Trindade",
|
|
98
|
+
"details": {
|
|
99
|
+
"age": 29,
|
|
100
|
+
"email": "irto@outlook.com",
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Supported Ruby versions
|
|
109
|
+
|
|
110
|
+
This library officially supports the following Ruby versions:
|
|
111
|
+
|
|
112
|
+
* MRI `>= 2.7`
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taksi
|
|
4
|
+
class Component < ::Module
|
|
5
|
+
attr_reader :identifier
|
|
6
|
+
|
|
7
|
+
def initialize(identifier)
|
|
8
|
+
@identifier = identifier
|
|
9
|
+
super()
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def included(klass)
|
|
13
|
+
klass.extend(ClassMethods)
|
|
14
|
+
klass.include(InstanceMethods)
|
|
15
|
+
|
|
16
|
+
klass.definition = self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ClassMethods
|
|
20
|
+
attr_reader :content_builder
|
|
21
|
+
|
|
22
|
+
def definition=(component_definition)
|
|
23
|
+
@component_definition = component_definition
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def identifier
|
|
27
|
+
@component_definition.identifier
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def content(&block)
|
|
31
|
+
@content_builder = block
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module InstanceMethods
|
|
37
|
+
attr_reader :interface_definition, :datasource, :skeleton
|
|
38
|
+
|
|
39
|
+
def initialize(interface_definition, with: nil)
|
|
40
|
+
@interface_definition = interface_definition
|
|
41
|
+
@datasource = with
|
|
42
|
+
@skeleton = @interface_definition.skeleton.create_component(self.class.identifier,
|
|
43
|
+
&self.class.content_builder)
|
|
44
|
+
super()
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def id
|
|
48
|
+
@skeleton.id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def content_for(interface)
|
|
52
|
+
data = interface.send(datasource)
|
|
53
|
+
|
|
54
|
+
skeleton.fields.each_with_object({}) do |field, obj|
|
|
55
|
+
load_data_from_key_to_object(data, field, obj)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def load_data_from_key_to_object(data, field, obj)
|
|
62
|
+
splitted_full_path = field.key.to_s.split('.')
|
|
63
|
+
setter_key = splitted_full_path.pop
|
|
64
|
+
splitted_full_path.shift # remove content root key, as it makes no sense in data object
|
|
65
|
+
|
|
66
|
+
relative_object = splitted_full_path.reduce(obj) do |memo, path_part|
|
|
67
|
+
memo[path_part.to_sym] ||= {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
relative_object[setter_key.to_sym] = field.fetch_from(data)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taksi
|
|
4
|
+
module Components
|
|
5
|
+
class Field
|
|
6
|
+
attr_reader :skeleton, :name, :value, :parent
|
|
7
|
+
|
|
8
|
+
def initialize(skeleton, name, *args, parent: nil, &block)
|
|
9
|
+
@skeleton = skeleton
|
|
10
|
+
@name = name.to_sym
|
|
11
|
+
@parent = parent
|
|
12
|
+
|
|
13
|
+
@value = args.shift.new(skeleton, name, *args) if args.size.positive?
|
|
14
|
+
@nested_fields = []
|
|
15
|
+
|
|
16
|
+
instance_exec(&block) if block_given?
|
|
17
|
+
@defined = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key
|
|
21
|
+
return name if parent.nil? || parent.root?
|
|
22
|
+
|
|
23
|
+
"#{parent.name}.#{name}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def fetch_from(data)
|
|
27
|
+
return value.as_json if value.static?
|
|
28
|
+
|
|
29
|
+
return data[name] if parent.nil? || parent.root?
|
|
30
|
+
|
|
31
|
+
parent.fetch_from(data)[name]
|
|
32
|
+
rescue NoMethodError
|
|
33
|
+
raise NameError, "Couldn't fetch #{key.inspect} from data: #{data.inspect}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def as_json
|
|
37
|
+
return {name => @nested_fields.map(&:as_json).inject({}, &:merge)} if nested?
|
|
38
|
+
|
|
39
|
+
{name => value.as_json}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def fields
|
|
43
|
+
Enumerator.new do |yielder|
|
|
44
|
+
@nested_fields.each do |field|
|
|
45
|
+
if field.nested?
|
|
46
|
+
field.fields.each(&yielder)
|
|
47
|
+
else
|
|
48
|
+
yielder << field
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def nested?
|
|
55
|
+
@value.nil?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def root?
|
|
59
|
+
@parent.nil?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def method_missing(name, *args, &block)
|
|
63
|
+
return super if @defined
|
|
64
|
+
|
|
65
|
+
@nested_fields << self.class.new(skeleton, name, *args, parent: self, &block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def respond_to_missing?(name)
|
|
69
|
+
return super if @defined
|
|
70
|
+
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taksi
|
|
4
|
+
module Components
|
|
5
|
+
class Skeleton
|
|
6
|
+
attr_reader :parent, :name, :content
|
|
7
|
+
|
|
8
|
+
def initialize(parent, name, &block)
|
|
9
|
+
@parent = parent
|
|
10
|
+
@name = name
|
|
11
|
+
|
|
12
|
+
@content = ::Taksi::Components::Field.new(self, :content, &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def id
|
|
16
|
+
parent.id_of(self)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fields
|
|
20
|
+
content.fields
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def as_json
|
|
24
|
+
{
|
|
25
|
+
name: name,
|
|
26
|
+
identifier: id
|
|
27
|
+
}.tap do |json|
|
|
28
|
+
json.merge!(content.as_json)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taksi
|
|
4
|
+
class Interface < ::Module
|
|
5
|
+
attr_reader :interface_name, :version_pattern, :alternatives
|
|
6
|
+
|
|
7
|
+
def self.find(name, version, alternative: nil)
|
|
8
|
+
::Taksi::Registry.find(name, version, alternative)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(name, version_pattern = nil, alternatives: nil)
|
|
12
|
+
@interface_name = name
|
|
13
|
+
@version_pattern = ::Gem::Requirement.new(version_pattern)
|
|
14
|
+
@alternatives = alternatives
|
|
15
|
+
super()
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def included(klass)
|
|
19
|
+
klass.extend(ClassMethods)
|
|
20
|
+
klass.include(InstanceMethods)
|
|
21
|
+
|
|
22
|
+
klass.initiate(self)
|
|
23
|
+
|
|
24
|
+
::Taksi::Registry.add(klass, interface_name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassMethods
|
|
28
|
+
attr_reader :skeleton
|
|
29
|
+
|
|
30
|
+
def find(version, alternative = nil)
|
|
31
|
+
::Taksi::Registry.find(interface_name, version, alternative)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initiate(interface_definition)
|
|
35
|
+
@components = []
|
|
36
|
+
@interface_definition = interface_definition
|
|
37
|
+
@skeleton = ::Taksi::Interfaces::Skeleton.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add(component_class, with: nil)
|
|
41
|
+
@components << component_class.new(self, with: with)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def components
|
|
45
|
+
@components.each
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def version_pattern
|
|
49
|
+
@interface_definition.version_pattern
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def alternatives
|
|
53
|
+
@interface_definition.alternatives
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module InstanceMethods
|
|
58
|
+
def skeleton
|
|
59
|
+
self.class.skeleton
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def data
|
|
63
|
+
self.class.components.map do |component|
|
|
64
|
+
{
|
|
65
|
+
identifier: component.id,
|
|
66
|
+
content: component.content_for(self)
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taksi
|
|
4
|
+
module Interfaces
|
|
5
|
+
class Skeleton
|
|
6
|
+
class ComponentNotFoundError < StandardError; end
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@component_skeletons = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create_component(identifier, &block)
|
|
13
|
+
::Taksi::Components::Skeleton.new(self, identifier, &block).tap do |component|
|
|
14
|
+
add(component)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add(component_skeleton)
|
|
19
|
+
@component_skeletons << component_skeleton
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def id_of(component)
|
|
24
|
+
index = @component_skeletons.index(component)
|
|
25
|
+
|
|
26
|
+
raise ComponentNotFoundError unless index
|
|
27
|
+
|
|
28
|
+
"component$#{index}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def as_json(*)
|
|
32
|
+
{components: @component_skeletons.map(&:as_json)}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/taksi/registry.rb
CHANGED
|
@@ -11,8 +11,8 @@ module Taksi
|
|
|
11
11
|
def_delegators :instance, :find, :add, :clear!
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
class
|
|
15
|
-
class
|
|
14
|
+
class InterfaceNotFoundError < ::StandardError; end
|
|
15
|
+
class InterfaceAltenativeNotFoundError < ::StandardError; end
|
|
16
16
|
|
|
17
17
|
def initialize
|
|
18
18
|
clear!
|
|
@@ -21,36 +21,36 @@ module Taksi
|
|
|
21
21
|
def add(klass, name)
|
|
22
22
|
sym_name = name.to_sym
|
|
23
23
|
|
|
24
|
-
@
|
|
25
|
-
@
|
|
24
|
+
@interfaces[sym_name] ||= []
|
|
25
|
+
@interfaces[sym_name] << klass
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def clear!
|
|
29
|
-
@
|
|
29
|
+
@interfaces = {}
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def find(name, version, alternative = nil)
|
|
33
|
-
|
|
33
|
+
interfaces_from_name = @interfaces[name.to_sym]
|
|
34
34
|
|
|
35
|
-
raise
|
|
35
|
+
raise InterfaceNotFoundError if interfaces_from_name.nil?
|
|
36
36
|
|
|
37
37
|
parsed_version = ::Gem::Version.new(version)
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
next false unless
|
|
39
|
+
found_interface = interfaces_from_name.find do |interface|
|
|
40
|
+
next false unless interface.version_pattern.satisfied_by?(parsed_version)
|
|
41
41
|
|
|
42
|
-
next true if alternative.
|
|
42
|
+
next true if alternative.nil?
|
|
43
43
|
|
|
44
|
-
next true if
|
|
44
|
+
next true if interface.alternatives.blank?
|
|
45
45
|
|
|
46
|
-
next true if
|
|
46
|
+
next true if interface.alternatives.include?(alternative)
|
|
47
47
|
|
|
48
48
|
false
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
raise
|
|
51
|
+
raise InterfaceNotFoundError if found_interface.nil?
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
found_interface
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
end
|
data/lib/taksi/values/dynamic.rb
CHANGED
|
@@ -3,27 +3,31 @@
|
|
|
3
3
|
module Taksi
|
|
4
4
|
module Values
|
|
5
5
|
class Dynamic
|
|
6
|
-
attr_reader :
|
|
6
|
+
attr_reader :component, :name, :field
|
|
7
7
|
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
8
|
+
def initialize(component, name, field = nil)
|
|
9
|
+
@component = component
|
|
10
10
|
@name = name
|
|
11
|
-
@
|
|
11
|
+
@field = field
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def path
|
|
15
|
-
return
|
|
15
|
+
return field if field
|
|
16
16
|
|
|
17
|
-
"#{
|
|
17
|
+
"#{component.id}.#{name}"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def as_json
|
|
21
|
-
|
|
21
|
+
nil
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def dynamic?
|
|
25
25
|
true
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
def static?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
|
data/lib/taksi/values/static.rb
CHANGED
|
@@ -3,21 +3,25 @@
|
|
|
3
3
|
module Taksi
|
|
4
4
|
module Values
|
|
5
5
|
class Static
|
|
6
|
-
attr_reader :
|
|
6
|
+
attr_reader :component, :name, :value
|
|
7
7
|
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
8
|
+
def initialize(component, name, value)
|
|
9
|
+
@component = component
|
|
10
10
|
@name = name
|
|
11
11
|
@value = value
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def as_json
|
|
15
|
-
|
|
15
|
+
value
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def dynamic?
|
|
19
19
|
false
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
def static?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
|
data/lib/taksi/version.rb
CHANGED
data/lib/taksi.rb
CHANGED
|
@@ -7,14 +7,14 @@ require 'taksi/version'
|
|
|
7
7
|
require 'taksi/values/dynamic'
|
|
8
8
|
require 'taksi/values/static'
|
|
9
9
|
|
|
10
|
-
require 'taksi/
|
|
11
|
-
require 'taksi/
|
|
12
|
-
require 'taksi/
|
|
10
|
+
require 'taksi/component'
|
|
11
|
+
require 'taksi/components/skeleton'
|
|
12
|
+
require 'taksi/components/field'
|
|
13
13
|
|
|
14
14
|
require 'taksi/registry'
|
|
15
15
|
|
|
16
|
-
require 'taksi/
|
|
17
|
-
require 'taksi/
|
|
16
|
+
require 'taksi/interface'
|
|
17
|
+
require 'taksi/interfaces/skeleton'
|
|
18
18
|
|
|
19
19
|
module Taksi
|
|
20
20
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: taksi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Israel Trindade
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-07-
|
|
11
|
+
date: 2023-07-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: rspec
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,8 +52,8 @@ dependencies:
|
|
|
38
52
|
- - ">="
|
|
39
53
|
- !ruby/object:Gem::Version
|
|
40
54
|
version: '0'
|
|
41
|
-
description: Useful
|
|
42
|
-
|
|
55
|
+
description: Useful toolset to build a backend over the concept of backend-driven
|
|
56
|
+
UI (aka. backend for frontend).
|
|
43
57
|
email:
|
|
44
58
|
- irto@outlook.com
|
|
45
59
|
executables: []
|
|
@@ -50,15 +64,15 @@ files:
|
|
|
50
64
|
- README.md
|
|
51
65
|
- Rakefile
|
|
52
66
|
- lib/taksi.rb
|
|
67
|
+
- lib/taksi/component.rb
|
|
68
|
+
- lib/taksi/components/field.rb
|
|
69
|
+
- lib/taksi/components/skeleton.rb
|
|
70
|
+
- lib/taksi/interface.rb
|
|
71
|
+
- lib/taksi/interfaces/skeleton.rb
|
|
53
72
|
- lib/taksi/registry.rb
|
|
54
|
-
- lib/taksi/screen.rb
|
|
55
|
-
- lib/taksi/screens/skeleton.rb
|
|
56
73
|
- lib/taksi/values/dynamic.rb
|
|
57
74
|
- lib/taksi/values/static.rb
|
|
58
75
|
- lib/taksi/version.rb
|
|
59
|
-
- lib/taksi/widget.rb
|
|
60
|
-
- lib/taksi/widgets/content_key.rb
|
|
61
|
-
- lib/taksi/widgets/skeleton.rb
|
|
62
76
|
homepage:
|
|
63
77
|
licenses: []
|
|
64
78
|
metadata:
|
|
@@ -84,5 +98,5 @@ requirements: []
|
|
|
84
98
|
rubygems_version: 3.3.26
|
|
85
99
|
signing_key:
|
|
86
100
|
specification_version: 4
|
|
87
|
-
summary:
|
|
101
|
+
summary: Application framework to build a backend driven UI in ruby
|
|
88
102
|
test_files: []
|
data/lib/taksi/screen.rb
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taksi
|
|
4
|
-
class Screen < ::Module
|
|
5
|
-
attr_reader :page_name, :version_pattern, :alternatives
|
|
6
|
-
|
|
7
|
-
def self.find(name, version, alternative: nil)
|
|
8
|
-
::Taksi::Registry.find(name, version, alternative)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def initialize(name, version_pattern = nil, alternatives: nil)
|
|
12
|
-
@page_name = name
|
|
13
|
-
@version_pattern = ::Gem::Requirement.new(version_pattern)
|
|
14
|
-
@alternatives = alternatives
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def included(klass)
|
|
18
|
-
klass.extend(ClassMethods)
|
|
19
|
-
klass.include(InstanceMethods)
|
|
20
|
-
|
|
21
|
-
klass.initiate(self)
|
|
22
|
-
|
|
23
|
-
::Taksi::Registry.add(klass, page_name)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
module ClassMethods
|
|
27
|
-
attr_reader :widgets, :skeleton
|
|
28
|
-
|
|
29
|
-
def find(version, alternative = nil)
|
|
30
|
-
::Taksi::Registry.find(page_name, version, alternative)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def initiate(screen_definition)
|
|
34
|
-
@widgets = []
|
|
35
|
-
@screen_definition = screen_definition
|
|
36
|
-
@skeleton = ::Taksi::Screens::Skeleton.new
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def add(widget_class, with: nil)
|
|
40
|
-
@widgets << widget_class.new(self, with: with)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def widgets
|
|
44
|
-
@widgets.each
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def version_pattern
|
|
48
|
-
@screen_definition.version_pattern
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def alternatives
|
|
52
|
-
@screen_definition.alternatives
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
module InstanceMethods
|
|
57
|
-
def skeleton
|
|
58
|
-
self.class.skeleton
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def data
|
|
62
|
-
self.class.widgets.each_with_object({}) do |widget, obj|
|
|
63
|
-
obj.merge!(widget.data_for(self).as_json)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taksi
|
|
4
|
-
module Screens
|
|
5
|
-
class Skeleton
|
|
6
|
-
class WidgetNotFoundError < StandardError; end
|
|
7
|
-
|
|
8
|
-
def initialize
|
|
9
|
-
@widget_skeletons = []
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def create_widget(identifier, &block)
|
|
13
|
-
::Taksi::Widgets::Skeleton.new(self, identifier, &block).tap do |widget|
|
|
14
|
-
add(widget)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def add(widget_skeleton)
|
|
19
|
-
@widget_skeletons << widget_skeleton
|
|
20
|
-
self
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def id_of(widget)
|
|
24
|
-
index = @widget_skeletons.index(widget)
|
|
25
|
-
|
|
26
|
-
raise WidgetNotFoundError unless index
|
|
27
|
-
|
|
28
|
-
"widget$#{index}"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def as_json(*)
|
|
32
|
-
{widgets: @widget_skeletons.map(&:as_json)}
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
data/lib/taksi/widget.rb
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taksi
|
|
4
|
-
class Widget < ::Module
|
|
5
|
-
attr_reader :identifier
|
|
6
|
-
|
|
7
|
-
def initialize(identifier)
|
|
8
|
-
@identifier = identifier
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def included(klass)
|
|
12
|
-
klass.extend(ClassMethods)
|
|
13
|
-
klass.include(InstanceMethods)
|
|
14
|
-
|
|
15
|
-
klass.definition = self
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
module ClassMethods
|
|
19
|
-
attr_reader :content_builder
|
|
20
|
-
|
|
21
|
-
def definition=(widget_definition)
|
|
22
|
-
@widget_definition = widget_definition
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def identifier
|
|
26
|
-
@widget_definition.identifier
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def content(&block)
|
|
30
|
-
@content_builder = block
|
|
31
|
-
self
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
module InstanceMethods
|
|
36
|
-
attr_reader :page_definition, :datasource, :skeleton
|
|
37
|
-
|
|
38
|
-
def initialize(page_definition, with: nil)
|
|
39
|
-
@page_definition = page_definition
|
|
40
|
-
@datasource = with
|
|
41
|
-
@skeleton = @page_definition.skeleton.create_widget(self.class.identifier,
|
|
42
|
-
&self.class.content_builder)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def data_for(page_instance)
|
|
46
|
-
data = page_instance.send(datasource)
|
|
47
|
-
|
|
48
|
-
skeleton.keys.each_with_object({}) do |content_key, obj|
|
|
49
|
-
next unless content_key.value.dynamic?
|
|
50
|
-
|
|
51
|
-
load_data_from_key_to_object(data, content_key, obj)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
def load_data_from_key_to_object(data, content_key, obj)
|
|
58
|
-
splitted_full_path = content_key.value.path.split('.')
|
|
59
|
-
setter_key = splitted_full_path.pop
|
|
60
|
-
|
|
61
|
-
relative_object = splitted_full_path.reduce(obj) do |memo, path_part|
|
|
62
|
-
memo[path_part] ||= {}
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
relative_object[setter_key] = content_key.fetch_from(data)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taksi
|
|
4
|
-
module Widgets
|
|
5
|
-
class ContentKey
|
|
6
|
-
attr_reader :skeleton, :key, :value, :parent
|
|
7
|
-
|
|
8
|
-
def initialize(skeleton, key, *args, parent: nil, &block)
|
|
9
|
-
@skeleton = skeleton
|
|
10
|
-
@key = key.to_sym
|
|
11
|
-
@parent = parent
|
|
12
|
-
|
|
13
|
-
@value = args.shift.new(skeleton, full_key, *args) if args.size.positive?
|
|
14
|
-
@nested_keys = []
|
|
15
|
-
|
|
16
|
-
instance_exec(&block) if block_given?
|
|
17
|
-
@defined = true
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def full_key
|
|
21
|
-
return key if parent.nil? || parent.root?
|
|
22
|
-
|
|
23
|
-
"#{parent.full_key}.#{key}"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def fetch_from(data)
|
|
27
|
-
return data[key] if parent.nil? || parent.root?
|
|
28
|
-
|
|
29
|
-
parent.fetch_from(data)[key]
|
|
30
|
-
rescue NoMethodError
|
|
31
|
-
raise NameError, "Couldn't fetch #{key.inspect} from data: #{data.inspect}"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def as_json
|
|
35
|
-
return {key => @nested_keys.map(&:as_json).inject({}, &:merge)} if nested?
|
|
36
|
-
|
|
37
|
-
{key => value.as_json}
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def keys
|
|
41
|
-
Enumerator.new do |yielder|
|
|
42
|
-
@nested_keys.each do |key|
|
|
43
|
-
if key.nested?
|
|
44
|
-
key.keys.each(&yielder)
|
|
45
|
-
else
|
|
46
|
-
yielder << key
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def nested?
|
|
53
|
-
@value.nil?
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def root?
|
|
57
|
-
@parent.nil?
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def method_missing(name, *args, &block)
|
|
61
|
-
return super if @defined
|
|
62
|
-
|
|
63
|
-
@nested_keys << self.class.new(skeleton, name, *args, parent: self, &block)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taksi
|
|
4
|
-
module Widgets
|
|
5
|
-
class Skeleton
|
|
6
|
-
attr_reader :parent, :identifier, :content
|
|
7
|
-
|
|
8
|
-
def initialize(parent, identifier, &block)
|
|
9
|
-
@parent = parent
|
|
10
|
-
@identifier = identifier
|
|
11
|
-
|
|
12
|
-
@content = ::Taksi::Widgets::ContentKey.new(self, :content, &block)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def id
|
|
16
|
-
parent.id_of(self)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def keys
|
|
20
|
-
content.keys
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def as_json
|
|
24
|
-
{identifier: identifier}.tap do |json|
|
|
25
|
-
json.merge!(content.as_json)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|