mayu 0.1.0.beta1

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.
@@ -0,0 +1,168 @@
1
+ require 'mayu/association'
2
+ require 'mayu/user'
3
+ require 'mayu/device'
4
+ require 'mayu/map'
5
+ require 'mayu/ap'
6
+
7
+ require 'mayu/user_completer'
8
+
9
+ module Mayu
10
+ class Loader
11
+ def initialize(store:, user_completer: nil)
12
+ @store = store
13
+ @cached_user_completer = user_completer
14
+ end
15
+
16
+ attr_reader :store
17
+
18
+ def load
19
+ objects
20
+ self
21
+ end
22
+
23
+ def objects
24
+ @objects ||= store.get
25
+ end
26
+
27
+ ###
28
+
29
+ def associations
30
+ @associations ||= objects.fetch(:associations).map do |_|
31
+ Association.load(_).tap do |association|
32
+ association._ap_finder = method(:find_ap)
33
+ association._user_finder = method(:find_user)
34
+ association._device_finder = method(:find_device_by_mac)
35
+ association._ensure_relation_finders_satisfied!
36
+ end
37
+ end
38
+ end
39
+
40
+ def association_by_mac
41
+ @association_by_mac = associations.map do |_|
42
+ [_.mac, _]
43
+ end.to_h
44
+ end
45
+
46
+ def association_by_ip
47
+ @association_by_ip = associations.map do |_|
48
+ [_.ip, _]
49
+ end.to_h
50
+ end
51
+
52
+ def associations_by_ap
53
+ @association_by_ap = associations.group_by do |_|
54
+ _.ap_key.to_s
55
+ end.to_h
56
+ end
57
+
58
+ def find_association_by_mac(mac)
59
+ association_by_mac[mac]
60
+ end
61
+
62
+ def find_association_by_ip(ip)
63
+ association_by_ip[ip]
64
+ end
65
+
66
+ def find_associations_by_ap(k)
67
+ associations_by_ap.fetch(k.to_s, [])
68
+ end
69
+
70
+ ###
71
+
72
+ def users
73
+ @users ||= objects.fetch(:users).map do |_|
74
+ User.load(_).tap do |user|
75
+ user._devices_finder = method(:find_devices_by_user)
76
+ user._ensure_relation_finders_satisfied!
77
+ end
78
+ end
79
+ end
80
+
81
+ def user_by_key
82
+ @user_by_key ||= users.map do |_|
83
+ [_.key.to_sym, _]
84
+ end.to_h
85
+ end
86
+
87
+ def find_user(k)
88
+ user_by_key[k.to_sym]
89
+ end
90
+
91
+ def user_completer
92
+ @user_completer ||= @cached_user_completer ? @cached_user_completer.update(users) : UserCompleter.new(users)
93
+ end
94
+
95
+ def suggest_users(query)
96
+ user_completer.query(query)
97
+ end
98
+
99
+ ###
100
+
101
+ def devices
102
+ @devices ||= objects.fetch(:devices).map do |_|
103
+ Device.load(_).tap do |device|
104
+ device._user_finder = method(:find_user)
105
+ device._association_finder = proc { find_association_by_mac(device.mac) }
106
+ device._ensure_relation_finders_satisfied!
107
+ end
108
+ end
109
+ end
110
+
111
+ def device_by_mac
112
+ @device_by_mac ||= devices.reverse_each.map do |device|
113
+ [device.mac, device]
114
+ end.to_h
115
+ end
116
+
117
+ def devices_by_user
118
+ @devices_by_user ||= devices.reverse_each.group_by do |device|
119
+ device.user_key
120
+ end
121
+ end
122
+
123
+ def find_device_by_mac(k)
124
+ device_by_mac[k]
125
+ end
126
+
127
+ def find_devices_by_user(k)
128
+ devices_by_user.fetch(k, [])
129
+ end
130
+
131
+ ###
132
+
133
+ def maps
134
+ @maps ||= objects.fetch(:maps).map do |k, v|
135
+ [k, Map.load(v).tap do |map|
136
+ map.key = k
137
+ map._aps_finder = proc { find_aps_by_map(k).values }
138
+ map._ensure_relation_finders_satisfied!
139
+ end]
140
+ end.to_h
141
+ end
142
+
143
+ def find_map(k)
144
+ maps[k.to_sym]
145
+ end
146
+
147
+ ###
148
+
149
+ def aps
150
+ @aps ||= objects.fetch(:aps).map do |k,v|
151
+ [k, Ap.load(v).tap do |ap|
152
+ ap.key = k
153
+ ap._map_finder = method(:find_map)
154
+ ap._associations_finder = proc { find_associations_by_ap(k) }
155
+ ap._ensure_relation_finders_satisfied!
156
+ end]
157
+ end.to_h
158
+ end
159
+
160
+ def find_ap(k)
161
+ aps[k.to_sym]
162
+ end
163
+
164
+ def find_aps_by_map(key)
165
+ aps.select { |k,v| v.map_key.to_s == key.to_s }
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,42 @@
1
+ module Mayu
2
+ Map = Struct.new(:key, :name, :url, :fg_color, :bg_color, :highlight_color, keyword_init: true) do
3
+ include Mayu::Relation
4
+
5
+ def self.load(obj)
6
+ new(**obj)
7
+ end
8
+
9
+ relates :aps
10
+
11
+ def associations
12
+ @associations ||= aps.flat_map(&:associations)
13
+ end
14
+ def devices
15
+ @devices ||= aps.flat_map(&:devices)
16
+ end
17
+ def users
18
+ @users ||= aps.flat_map(&:users)
19
+ end
20
+
21
+ def associations_count
22
+ associations.size
23
+ end
24
+ def devices_count
25
+ devices.size
26
+ end
27
+ def users_count
28
+ users.size
29
+ end
30
+
31
+ def as_json
32
+ {
33
+ key: key,
34
+ name: name,
35
+ url: url,
36
+ fg_color: fg_color,
37
+ bg_color: bg_color,
38
+ highlight_color: highlight_color,
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ require 'mayu/loader'
2
+ require 'thread'
3
+
4
+ module Mayu
5
+ class PeriodicLoader
6
+ SHUTDOWN_KEY = :mayu_periodic_loader_shutdown
7
+ def initialize(interval:, **options)
8
+ @loaded_at = nil
9
+ @loader = nil
10
+ @interval = interval
11
+ @options = options
12
+ @lock = Mutex.new
13
+ @thread = nil
14
+ end
15
+
16
+ attr_reader :loaded_at, :loader
17
+
18
+ def start
19
+ return if @thread
20
+ @lock.synchronize do
21
+ return if @thread
22
+ reload
23
+ @thread = Thread.new(&method(:thread))
24
+ end
25
+ end
26
+
27
+ def shutdown
28
+ @lock.synchronize do
29
+ if @thread
30
+ @thread[SHUTDOWN_KEY] = true
31
+ end
32
+ @thread = nil
33
+ end
34
+ end
35
+
36
+ def reload
37
+ @loaded_at = Time.now
38
+ new_loader = Loader.new(user_completer: @loader&.user_completer, **@options).load
39
+ @loader = new_loader
40
+ end
41
+
42
+ def thread
43
+ loop do
44
+ return if @thread[SHUTDOWN_KEY]
45
+ @lock.synchronize do
46
+ reload
47
+ end
48
+ sleep @interval
49
+ rescue Exception => e
50
+ $stderr.puts e.full_message
51
+ sleep @interval
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ module Mayu
2
+ module Relation
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ end
6
+
7
+ def _ensure_relation_finders_satisfied!
8
+ self.class.relations.map do |k|
9
+ unless instance_variable_get(:"@_#{k}_finder")
10
+ raise "#{k.class}: #{k.inspect} not satisfied"
11
+ end
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def relations
17
+ @_relations ||= []
18
+ end
19
+ def relates(name)
20
+ self.relations << name
21
+
22
+ key_exist = self.instance_methods.include?(:"#{name}_key")
23
+ self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
24
+ def _#{name}_finder=(x)
25
+ @_#{name}_finder = x
26
+ end
27
+
28
+ def _#{name}
29
+ @_#{name}
30
+ end
31
+
32
+ def _#{name}=(x)
33
+ key = #{key_exist ? "#{name}_key" : 'nil'}
34
+ @_#{name}_key = key
35
+ @_#{name} = x
36
+ end
37
+
38
+ def #{name}
39
+ raise "#{name} finder not set" unless @_#{name}_finder
40
+ key = #{key_exist ? "#{name}_key" : 'nil'}
41
+ if @_#{name}_key && @_#{name}_key != key
42
+ @_#{name} = nil
43
+ end
44
+ self._#{name} ||= @_#{name}_finder[key]
45
+ end
46
+ EOF
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+
3
+ module Mayu
4
+
5
+ class Renderer
6
+ def initialize(spec)
7
+ @spec = spec
8
+ end
9
+
10
+ def render(obj, spec: @spec)
11
+ r = nil
12
+ case obj
13
+ when nil
14
+ return nil
15
+ when Array
16
+ return obj.map { |_| render(_, spec: spec) }
17
+ when Hash
18
+ r = obj
19
+ else
20
+ case
21
+ when obj.respond_to?(:as_json)
22
+ r = obj.as_json
23
+ when !spec.empty?
24
+ r = {}
25
+ else
26
+ return obj
27
+ end
28
+ end
29
+ next_specs, keys = [spec].flatten.partition { |_| Hash === _ }
30
+ aref = Hash === obj
31
+ r2 = r.transform_values do |v|
32
+ render(v, spec: {})
33
+ end
34
+ keys.each do |k|
35
+ r[k] = render(r.fetch(k) { aref ? obj.fetch(k) { obj.send(k) } : obj.send(k) }, spec: {})
36
+ end
37
+ (next_specs.inject(&:merge) || {}).each do |k, ns|
38
+ r[k] = render(r.fetch(k) { aref ? obj.fetch(k) { obj.send(k) } : obj.send(k) }, spec: ns)
39
+ end
40
+ r2.merge(r)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ require 'time'
2
+ module Mayu
3
+ module Stores
4
+ class Base
5
+ def initialize(**options)
6
+ @options = options
7
+ end
8
+
9
+ def put(obj)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def get
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def for_json(obj)
18
+ case obj
19
+ when Array
20
+ obj.map{ |_| for_json(_) }
21
+ when Hash
22
+ obj.transform_values { |v| v.is_a?(Time) ? v.xmlschema : for_json(v) }
23
+ else
24
+ obj
25
+ end
26
+ end
27
+
28
+ def from_json(obj)
29
+ case obj
30
+ when Array
31
+ obj.map do |v|
32
+ from_json(v)
33
+ end
34
+ when Hash
35
+ obj.map do |k,v|
36
+ [k, k.to_s.end_with?('_at') ? (v && Time.xmlschema(v)) : from_json(v)]
37
+ end.to_h
38
+ else
39
+ obj
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ require 'mayu/stores/base'
2
+
3
+ module Mayu
4
+ module Stores
5
+ class Concat < Base
6
+ def initialize(stores:)
7
+ @stores = stores
8
+ end
9
+
10
+ attr_reader :stores
11
+
12
+ def put(obj)
13
+ raise NotImplementedError, "this store is read only"
14
+ end
15
+
16
+ def get
17
+ objs = @stores.map(&:get)
18
+ keys = objs.flat_map(&:keys).uniq
19
+
20
+ r = {}
21
+ objs.each do |i|
22
+ keys.each do |k|
23
+ next unless i[k]
24
+ case r[k]
25
+ when Array
26
+ r[k] ||= []
27
+ r[k] += i[k]
28
+ when Hash
29
+ r[k] ||= {}
30
+ r[k] = r[k].merge(i[k])
31
+ else
32
+ r[k] = i[k]
33
+ end
34
+ end
35
+ end
36
+ r
37
+ end
38
+ end
39
+ end
40
+ end
41
+