omarchy-ui 0.0.1-x86_64-linux

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,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmarchyUI
4
+ class Task
5
+ attr_reader :kind, :interval, :block
6
+
7
+ def initialize(kind:, interval:, immediate:, block:)
8
+ @kind = kind
9
+ @interval = interval
10
+ @immediate = immediate
11
+ @block = block
12
+ @mutex = Mutex.new
13
+ @condition = ConditionVariable.new
14
+ @cancelled = false
15
+ @thread = nil
16
+ end
17
+
18
+ def start(evaluator, on_error)
19
+ @mutex.synchronize do
20
+ return self if @cancelled || @thread
21
+ if Object.const_defined?(:Thread)
22
+ @thread = Thread.new { run(evaluator, on_error) }
23
+ @thread.name = "omarchy-ui-#{kind}" if @thread.respond_to?(:name=)
24
+ else
25
+ @thread = :cooperative
26
+ @evaluator = evaluator
27
+ @on_error = on_error
28
+ @next_due = Time.now.to_f + ((kind == :async || @immediate) ? 0 : interval)
29
+ end
30
+ end
31
+ self
32
+ end
33
+
34
+ def cancel
35
+ @mutex.synchronize do
36
+ @cancelled = true
37
+ @condition.broadcast
38
+ end
39
+ self
40
+ end
41
+
42
+ def join(timeout = nil)
43
+ thread = @mutex.synchronize { @thread }
44
+ thread.join(timeout) if thread && thread != :cooperative
45
+ self
46
+ end
47
+
48
+ def tick(now = Time.now.to_f)
49
+ return unless @thread == :cooperative || !Object.const_defined?(:Thread)
50
+ return if cancelled? || now < @next_due
51
+ invoke(@evaluator, @on_error)
52
+ if kind == :every
53
+ @next_due = now + interval
54
+ else
55
+ cancel
56
+ end
57
+ end
58
+
59
+ def cancelled?
60
+ @mutex.synchronize { @cancelled }
61
+ end
62
+
63
+ private
64
+
65
+ def run(evaluator, on_error)
66
+ case kind
67
+ when :async then invoke(evaluator, on_error)
68
+ when :after then invoke(evaluator, on_error) if wait(interval)
69
+ when :every
70
+ invoke(evaluator, on_error) if @immediate && !cancelled?
71
+ while wait(interval)
72
+ invoke(evaluator, on_error)
73
+ end
74
+ end
75
+ end
76
+
77
+ def wait(seconds)
78
+ @mutex.synchronize do
79
+ @condition.wait(@mutex, seconds) unless @cancelled
80
+ !@cancelled
81
+ end
82
+ end
83
+
84
+ def invoke(evaluator, on_error)
85
+ evaluator.call(block) unless cancelled?
86
+ rescue StandardError => error
87
+ on_error.call(error)
88
+ end
89
+ end
90
+
91
+ class Scheduler
92
+ def initialize(evaluator:, on_error:)
93
+ @evaluator = evaluator
94
+ @on_error = on_error
95
+ @tasks = []
96
+ @lock = Mutex.new
97
+ @started = false
98
+ end
99
+
100
+ def schedule(kind, interval: 0, immediate: false, &block)
101
+ raise ArgumentError, "task requires a block" unless block
102
+ seconds = Float(interval)
103
+ raise ArgumentError, "task interval must be positive" if kind != :async && !seconds.positive?
104
+ task = Task.new(kind:, interval: seconds, immediate:, block:)
105
+ @lock.synchronize do
106
+ @tasks << task
107
+ task.start(@evaluator, @on_error) if @started
108
+ end
109
+ task
110
+ end
111
+
112
+ def start
113
+ @lock.synchronize do
114
+ @started = true
115
+ @tasks.each { |task| task.start(@evaluator, @on_error) }
116
+ end
117
+ self
118
+ end
119
+
120
+ def stop
121
+ tasks = @lock.synchronize do
122
+ @started = false
123
+ @tasks.dup
124
+ end
125
+ tasks.each(&:cancel)
126
+ tasks.each { |task| task.join(1) }
127
+ self
128
+ end
129
+
130
+ def tick(now = Time.now.to_f)
131
+ tasks = @lock.synchronize { @tasks.dup }
132
+ tasks.each { |task| task.tick(now) }
133
+ self
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmarchyUI
4
+ class StateStore
5
+ def initialize(on_change)
6
+ @values = {}
7
+ @on_change = on_change
8
+ @lock = Mutex.new
9
+ @transaction_depth = 0
10
+ @pending = {}
11
+ end
12
+
13
+ def define(name, initial)
14
+ key = name.to_sym
15
+ @lock.synchronize do
16
+ raise ArgumentError, "state already defined: #{key}" if @values.key?(key)
17
+ @values[key] = initial
18
+ end
19
+ end
20
+
21
+ def [](name)
22
+ @lock.synchronize { @values.fetch(name.to_sym) }
23
+ end
24
+
25
+ def []=(name, value)
26
+ write(name.to_sym, value)
27
+ end
28
+
29
+ def update(name)
30
+ raise ArgumentError, "update requires a block" unless block_given?
31
+ key = name.to_sym
32
+ change, value = @lock.synchronize do
33
+ raise NoMethodError, "unknown state: #{key}" unless @values.key?(key)
34
+ next_value = yield(@values[key])
35
+ [store_locked(key, next_value), next_value]
36
+ end
37
+ @on_change.call(*change) if change
38
+ value
39
+ end
40
+
41
+ def method_missing(name, *arguments)
42
+ raw = name.to_s
43
+ if raw.end_with?("=")
44
+ raise ArgumentError, "expected one value" unless arguments.length == 1
45
+
46
+ return write(raw.delete_suffix("=").to_sym, arguments.first)
47
+ end
48
+ if arguments.empty?
49
+ found, value = @lock.synchronize { [@values.key?(name), @values[name]] }
50
+ return value if found
51
+ end
52
+
53
+ super
54
+ end
55
+
56
+ def respond_to_missing?(name, include_private = false)
57
+ key = name.to_s.delete_suffix("=").to_sym
58
+ @lock.synchronize { @values.key?(key) } || super
59
+ end
60
+
61
+ def transaction
62
+ raise ArgumentError, "transaction requires a block" unless block_given?
63
+ @lock.synchronize { @transaction_depth += 1 }
64
+ yield self
65
+ ensure
66
+ changes = @lock.synchronize do
67
+ @transaction_depth -= 1
68
+ next [] unless @transaction_depth.zero?
69
+ flushed = @pending.values
70
+ @pending.clear
71
+ flushed
72
+ end
73
+ changes.each { |change| @on_change.call(*change) }
74
+ end
75
+
76
+ private
77
+
78
+ def write(key, value)
79
+ change = @lock.synchronize do
80
+ raise NoMethodError, "unknown state: #{key}" unless @values.key?(key)
81
+ store_locked(key, value)
82
+ end
83
+ @on_change.call(*change) if change
84
+ value
85
+ end
86
+
87
+ def store_locked(key, value)
88
+ return nil if @values[key] == value
89
+ previous = @values[key]
90
+ @values[key] = value
91
+ if @transaction_depth.positive?
92
+ @pending[key] ||= [key, previous, value]
93
+ @pending[key][2] = value
94
+ nil
95
+ else
96
+ [key, previous, value]
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmarchyUI
4
+ module Value
5
+ MAX_DEPTH = 32
6
+ MAX_ITEMS = 10_000
7
+ def normalize(value, property: nil, depth: 0, seen: {}, count: [0])
8
+ raise ArgumentError, "property value exceeds #{MAX_DEPTH} levels: #{property}" if depth > MAX_DEPTH
9
+ count[0] += 1
10
+ raise ArgumentError, "property value exceeds #{MAX_ITEMS} items: #{property}" if count[0] > MAX_ITEMS
11
+
12
+ case value
13
+ when Symbol then value.to_s
14
+ when String, TrueClass, FalseClass, NilClass, Integer then value
15
+ when Float
16
+ raise ArgumentError, "property number must be finite: #{property}" unless value.finite?
17
+ value
18
+ when Array
19
+ guard_cycle(value, property, seen) do
20
+ value.map { |item| normalize(item, property:, depth: depth + 1, seen:, count:) }
21
+ end
22
+ when Hash
23
+ guard_cycle(value, property, seen) do
24
+ value.each_with_object({}) do |(key, item), result|
25
+ result[key.to_s] = normalize(item, property:, depth: depth + 1, seen:, count:)
26
+ end
27
+ end
28
+ else
29
+ raise ArgumentError, "unsupported property value for #{property}: #{value.class}"
30
+ end
31
+ end
32
+
33
+ def guard_cycle(value, property, seen)
34
+ identity = value.object_id
35
+ raise ArgumentError, "cyclic property value: #{property}" if seen[identity]
36
+ seen[identity] = true
37
+ yield
38
+ ensure
39
+ seen.delete(identity)
40
+ end
41
+
42
+ module_function :normalize, :guard_cycle
43
+ end
44
+ end
data/lib/omarchy_ui.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "omarchy_ui/protocol"
4
+ require_relative "omarchy_ui/value"
5
+ require_relative "omarchy_ui/state_store"
6
+ require_relative "omarchy_ui/node"
7
+ require_relative "omarchy_ui/animation"
8
+ require_relative "omarchy_ui/scheduler"
9
+ require_relative "omarchy_ui/command"
10
+ require_relative "omarchy_ui/component_registry"
11
+ require_relative "omarchy_ui/components"
12
+ require_relative "omarchy_ui/builder"
13
+ require_relative "omarchy_ui/application"
14
+ require_relative "omarchy_ui/project"
15
+ require_relative "omarchy_ui/runtime"
16
+
17
+ module OmarchyUI
18
+ VERSION = "0.0.1"
19
+ FRAMEWORK_ROOT = File.expand_path("..", __dir__)
20
+
21
+ def self.plugin(&definition)
22
+ Application.new(&definition).run
23
+ end
24
+
25
+ class << self
26
+ alias application plugin
27
+ alias app plugin
28
+ end
29
+ end
data/manifest.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "izeesoft.omarchy-ui-poc",
4
+ "name": "Omarchy UI Ruby Framework",
5
+ "version": "0.3.0",
6
+ "author": "Adam Moussa Ali",
7
+ "license": "MIT",
8
+ "description": "Persistent Ruby state and event handlers rendered by a generic Omarchy QML bridge.",
9
+ "kinds": [
10
+ "service",
11
+ "bar-widget",
12
+ "panel"
13
+ ],
14
+ "keepLoaded": true,
15
+ "entryPoints": {
16
+ "service": "Service.qml",
17
+ "barWidget": "BarWidget.qml",
18
+ "panel": "Panel.qml"
19
+ },
20
+ "barWidget": {
21
+ "displayName": "Ruby UI Framework",
22
+ "description": "Open the Ruby-driven counter panel.",
23
+ "category": "Development",
24
+ "allowMultiple": false,
25
+ "defaultSection": "right"
26
+ }
27
+ }
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omarchy-ui
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: x86_64-linux
6
+ authors:
7
+ - Adam Moussa Ali
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A persistent Ruby runtime, reactive UI model, and safe QML renderer for
13
+ Omarchy.
14
+ executables:
15
+ - omarchy_ui
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - App.qml
20
+ - BarWidget.qml
21
+ - Components/README.md
22
+ - Components/Sparkline.qml
23
+ - ControlNode.qml
24
+ - LICENSE
25
+ - Panel.qml
26
+ - README.md
27
+ - Service.qml
28
+ - bin/omarchy_ui
29
+ - lib/omarchy_ui.rb
30
+ - lib/omarchy_ui/animation.rb
31
+ - lib/omarchy_ui/application.rb
32
+ - lib/omarchy_ui/builder.rb
33
+ - lib/omarchy_ui/cli.rb
34
+ - lib/omarchy_ui/command.rb
35
+ - lib/omarchy_ui/component_registry.rb
36
+ - lib/omarchy_ui/components.rb
37
+ - lib/omarchy_ui/node.rb
38
+ - lib/omarchy_ui/project.rb
39
+ - lib/omarchy_ui/protocol.rb
40
+ - lib/omarchy_ui/runtime.rb
41
+ - lib/omarchy_ui/scheduler.rb
42
+ - lib/omarchy_ui/state_store.rb
43
+ - lib/omarchy_ui/value.rb
44
+ - manifest.json
45
+ - vendor/runtime/x86_64-linux/omarchy-ui-runtime
46
+ homepage: https://github.com/AdamMusa/omarchy-ui
47
+ licenses:
48
+ - MIT
49
+ metadata:
50
+ source_code_uri: https://github.com/AdamMusa/omarchy-ui
51
+ documentation_uri: https://github.com/AdamMusa/omarchy-ui#readme
52
+ release_status: experimental
53
+ rubygems_mfa_required: 'true'
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.1'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 4.0.18
69
+ specification_version: 4
70
+ summary: Build native Omarchy plugins and applications in Ruby
71
+ test_files: []