zui 0.0.9 → 0.0.10

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -1
  3. data/lib/zui/cli.rb +218 -35
  4. data/lib/zui.rb +1 -1
  5. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42bdb62a57c863cdc40283e45b894097dc811f23f3d8d081ae05bc1e6619aece
4
- data.tar.gz: 7da4bafa4b0efa12cc5c2b89f88210044154dec373e5f0b419f900dae471727d
3
+ metadata.gz: 708ca03d2896630151177ee0b6fe2d38a5a6e8da0ec03899a89342a881f431da
4
+ data.tar.gz: c2342386d280806246ce4e2dc6ef296b3e65474ff352bbca8e5085313c534886
5
5
  SHA512:
6
- metadata.gz: 5f8016e274b0cec1f2a8614327e7c7ab95762a836a7d55fe2a1e716d231fe6ef2c12a56f28de3df09b907471a6fe2156a42d25a897a80ab483f66337c3a47ad9
7
- data.tar.gz: 033e22d2907f8c0fa66badd46eaec2f1636e1388c272c16210b0ede3dad7a561fc185b7372b2952ee14273c97e36ccbbe85b364b664481448175e438d13f1956
6
+ metadata.gz: 8529e5aa1ea6f062b56dec92e568895781f14ad8c5e0f7e22032d763f9f4c3f086c272203843de62bb613b8aedc5b28ac4b32bd9ceffbe4f3ec98e611e462375
7
+ data.tar.gz: e8e6047c3ec7423a2b49a0cb70a83acdd5e8315d974ebe7203e3e92492a864f0d13717a504e2bed599a036dcb902bdc0fdeac490a445c098c4e4d7a8f9cf1bed
data/README.md CHANGED
@@ -188,7 +188,7 @@ uses a system Qt installation, or downloads an asset for a different platform. S
188
188
 
189
189
  ## Component catalog
190
190
 
191
- Zui 0.0.9 registers **241 built-in components**. Every entry has a named Ruby builder method,
191
+ Zui 0.0.10 registers **241 built-in components**. Every entry has a named Ruby builder method,
192
192
  property and event schema, native QML renderer, reactive patch support, and contract tests.
193
193
 
194
194
  | Category | Count | Representative components |
@@ -217,6 +217,8 @@ an explicit error.
217
217
 
218
218
  | Command | Purpose |
219
219
  | --- | --- |
220
+ | `zui --help` | List every command and global option |
221
+ | `zui help COMMAND` | Show detailed usage, options, and examples for one command |
220
222
  | `zui new NAME` | Generate a pure-Ruby application, `Gemfile`, distribution config, and reusable UI module |
221
223
  | `zui doctor` | Report Ruby, platform, native-client, run, and bundle readiness without changing anything |
222
224
  | `zui doctor --fix` | Download, verify, and install the missing native client and lite mruby runtime |
@@ -229,6 +231,9 @@ an explicit error.
229
231
  | `zui bundle --dist [DIRECTORY]` | Build release installers from the required project-root `config.rb` |
230
232
  | `zui version` | Print the installed framework version |
231
233
 
234
+ Every command also supports `-h` and `--help` directly, such as `zui bundle --help`.
235
+ Usage errors point to the relevant command help without starting downloads, builds, or applications.
236
+
232
237
  Zui has no separate validation command. Ruby, DSL, schema, resource, protocol, and renderer errors
233
238
  are reported by the operation that encounters them.
234
239
 
data/lib/zui/cli.rb CHANGED
@@ -1,11 +1,127 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "optparse"
4
5
  require "rbconfig"
5
6
 
6
7
  module Zui
7
8
  class CLI
8
- USAGE = "Usage: zui <new NAME|configure|doctor [--fix]|run FILE|bundle [--dist] [--lite|--full] [--no-tree-shake] [DIRECTORY]|version>"
9
+ class UsageError < ArgumentError; end
10
+
11
+ COMMANDS = {
12
+ "new" => "Create a new Zui application",
13
+ "configure" => "Install the native client and lite runtime",
14
+ "doctor" => "Check or repair the local Zui installation",
15
+ "run" => "Run a Ruby application with the native client",
16
+ "bundle" => "Build a standalone app or release installer",
17
+ "version" => "Print the installed Zui version"
18
+ }.freeze
19
+
20
+ GENERAL_HELP = <<~HELP.freeze
21
+ Zui #{VERSION} — native desktop applications in Ruby
22
+
23
+ Usage:
24
+ zui COMMAND [arguments] [options]
25
+
26
+ Commands:
27
+ new NAME #{COMMANDS.fetch("new")}
28
+ configure #{COMMANDS.fetch("configure")}
29
+ doctor #{COMMANDS.fetch("doctor")}
30
+ run FILE #{COMMANDS.fetch("run")}
31
+ bundle [DIR] #{COMMANDS.fetch("bundle")}
32
+ version #{COMMANDS.fetch("version")}
33
+
34
+ Global options:
35
+ -h, --help Show this help
36
+ -v, --version Print the installed Zui version
37
+
38
+ Run `zui help COMMAND` or `zui COMMAND --help` for command details.
39
+ HELP
40
+
41
+ COMMAND_HELP = {
42
+ "new" => <<~HELP,
43
+ Create a new Zui application.
44
+
45
+ Usage:
46
+ zui new NAME
47
+
48
+ NAME becomes both the project directory and display name. Underscores,
49
+ hyphens, and spaces are normalized; `smart_home` becomes “Smart Home”.
50
+ The project includes Ruby sources, distribution config, and release icons.
51
+
52
+ Example:
53
+ zui new smart_home
54
+ HELP
55
+ "configure" => <<~HELP,
56
+ Install the verified native client and lite mruby runtime.
57
+
58
+ Usage:
59
+ zui configure
60
+
61
+ Downloads the platform assets for this Zui version from GitHub Releases,
62
+ verifies their checksums and manifests, and installs them in the user cache.
63
+
64
+ Equivalent repair command:
65
+ zui doctor --fix
66
+ HELP
67
+ "doctor" => <<~HELP,
68
+ Check or repair the local Zui installation.
69
+
70
+ Usage:
71
+ zui doctor [options]
72
+
73
+ Options:
74
+ -f, --fix Download, verify, and install missing platform assets
75
+
76
+ Without --fix, doctor is read-only and exits nonzero when setup is incomplete.
77
+
78
+ Examples:
79
+ zui doctor
80
+ zui doctor --fix
81
+ HELP
82
+ "run" => <<~HELP,
83
+ Run a Ruby application with the private native client.
84
+
85
+ Usage:
86
+ zui run FILE
87
+
88
+ FILE is the Ruby entry point for the application.
89
+
90
+ Example:
91
+ zui run main.rb
92
+ HELP
93
+ "bundle" => <<~HELP,
94
+ Build a standalone application or platform release installer.
95
+
96
+ Usage:
97
+ zui bundle [DIRECTORY] [options]
98
+
99
+ Options:
100
+ -n, --name NAME Override the app name for a non-distribution bundle
101
+ -o, --output PATH Set the bundle destination or distribution directory
102
+ --lite Embed the portable mruby runtime (default)
103
+ --full Embed private CRuby and the project's locked gems
104
+ --dist Build platform installers using config.rb
105
+ --no-tree-shake Keep the complete component and Qt feature catalog
106
+
107
+ DIRECTORY defaults to the current directory. --lite and --full are mutually
108
+ exclusive. --dist reads the app name and release metadata from config.rb.
109
+
110
+ Examples:
111
+ zui bundle
112
+ zui bundle --full .
113
+ zui bundle --name "Smart Home" --output dist/SmartHome.app
114
+ zui bundle --dist --full .
115
+ HELP
116
+ "version" => <<~HELP
117
+ Print the installed Zui version.
118
+
119
+ Usage:
120
+ zui version
121
+ zui --version
122
+ zui -v
123
+ HELP
124
+ }.freeze
9
125
 
10
126
  def self.run(arguments, out: $stdout, err: $stderr)
11
127
  new(out:, err:).run(arguments.dup)
@@ -18,19 +134,26 @@ module Zui
18
134
 
19
135
  def run(arguments)
20
136
  command = arguments.shift
137
+ @active_command = command
138
+ return show_general_help if command.nil? || %w[-h --help].include?(command)
139
+ return show_command_help(arguments) if command == "help"
140
+ return show_named_help(command) if COMMANDS.key?(command) && help_requested?(arguments)
141
+
21
142
  case command
22
143
  when "new" then new_project(arguments)
23
144
  when "configure" then configure(arguments)
24
145
  when "run" then run_file(arguments)
25
146
  when "bundle" then bundle_project(arguments)
26
147
  when "doctor" then doctor(arguments)
27
- when "version", "--version", "-v" then @out.puts(VERSION); 0
28
- else
29
- @err.puts(USAGE)
30
- command.nil? ? 0 : 64
148
+ when "version", "--version", "-v" then print_version(arguments)
149
+ else unknown_command(command)
31
150
  end
32
151
  rescue Interrupt
33
152
  130
153
+ rescue OptionParser::ParseError, UsageError => error
154
+ @err.puts("zui: #{error.message}")
155
+ @err.puts(help_hint)
156
+ 1
34
157
  rescue ArgumentError, SystemCallError, JSON::ParserError => error
35
158
  @err.puts("zui: #{error.message}")
36
159
  1
@@ -39,8 +162,10 @@ module Zui
39
162
  private
40
163
 
41
164
  def new_project(arguments)
42
- name = arguments.shift || raise(ArgumentError, "new requires a project name")
43
- raise ArgumentError, "new accepts only a project name" unless arguments.empty?
165
+ parse_no_options!(arguments)
166
+ name = arguments.shift || raise(UsageError, "new requires a project name")
167
+ raise UsageError, "new accepts only one project name" unless arguments.empty?
168
+
44
169
  destination = File.expand_path(slug(name))
45
170
  Generator.new(path: destination, name:).create
46
171
  @out.puts("Created Zui application in #{destination}")
@@ -48,35 +173,46 @@ module Zui
48
173
  end
49
174
 
50
175
  def run_file(arguments)
51
- file = File.expand_path(arguments.shift || raise(ArgumentError, "run requires a Ruby file"))
52
- raise ArgumentError, "run accepts one Ruby file" unless arguments.empty?
176
+ parse_no_options!(arguments)
177
+ file = File.expand_path(arguments.shift || raise(UsageError, "run requires a Ruby file"))
178
+ raise UsageError, "run accepts only one Ruby file" unless arguments.empty?
179
+
53
180
  Runner.new.run(file)
54
181
  end
55
182
 
56
183
  def bundle_project(arguments)
57
- name = option_value(arguments, "--name")
58
- destination = option_value(arguments, "--output")
59
- create_installers = !arguments.delete("--dist").nil?
60
- tree_shake = arguments.delete("--no-tree-shake").nil?
61
- lite = !arguments.delete("--lite").nil?
62
- full = !arguments.delete("--full").nil?
63
- raise ArgumentError, "bundle accepts only one of --lite or --full" if lite && full
64
- runtime_mode = full ? :full : :lite
184
+ options = { tree_shake: true }
185
+ parser = OptionParser.new do |option|
186
+ option.on("-n NAME", "--name NAME") { |value| options[:name] = value }
187
+ option.on("-o PATH", "--output PATH") { |value| options[:destination] = value }
188
+ option.on("--lite") { options[:lite] = true }
189
+ option.on("--full") { options[:full] = true }
190
+ option.on("--dist") { options[:create_installers] = true }
191
+ option.on("--no-tree-shake") { options[:tree_shake] = false }
192
+ end
193
+ parser.parse!(arguments)
194
+ if options[:lite] && options[:full]
195
+ raise UsageError, "bundle accepts only one of --lite or --full"
196
+ end
197
+
198
+ runtime_mode = options[:full] ? :full : :lite
65
199
  source = File.expand_path(arguments.shift || Dir.pwd)
66
- raise ArgumentError, "bundle accepts one directory" unless arguments.empty?
200
+ raise UsageError, "bundle accepts only one directory" unless arguments.empty?
67
201
 
68
- if create_installers
69
- raise ArgumentError, "--name cannot be used with --dist; set name in config.rb" if name
202
+ if options[:create_installers]
203
+ if options[:name]
204
+ raise UsageError, "--name cannot be used with --dist; set name in config.rb"
205
+ end
70
206
 
71
- packager = DistPackager.new(tree_shake:, runtime_mode:)
72
- paths = packager.package(source, output: destination)
207
+ packager = DistPackager.new(tree_shake: options[:tree_shake], runtime_mode:)
208
+ paths = packager.package(source, output: options[:destination])
73
209
  paths.each { |path| @out.puts("Created distribution artifact #{path}") }
74
210
  report_tree_shaking(packager.tree_shake_report)
75
211
  return 0
76
212
  end
77
213
 
78
- distribution = Distribution.new(tree_shake:, runtime_mode:)
79
- path = distribution.bundle(source, name:, destination:)
214
+ distribution = Distribution.new(tree_shake: options[:tree_shake], runtime_mode:)
215
+ path = distribution.bundle(source, name: options[:name], destination: options[:destination])
80
216
  @out.puts("Bundled #{Platform.current.os} application in #{path}")
81
217
  report_tree_shaking(distribution.tree_shake_report)
82
218
  0
@@ -89,8 +225,10 @@ module Zui
89
225
  end
90
226
 
91
227
  def doctor(arguments)
92
- fix = arguments.delete("--fix")
93
- raise ArgumentError, "doctor accepts only --fix" unless arguments.empty?
228
+ fix = false
229
+ OptionParser.new { |option| option.on("-f", "--fix") { fix = true } }.parse!(arguments)
230
+ raise UsageError, "doctor accepts no positional arguments" unless arguments.empty?
231
+
94
232
  platform = Platform.current
95
233
  @out.puts("Zui #{VERSION}")
96
234
  @out.puts("Platform: #{platform.id}#{platform.supported? ? '' : ' (unsupported)'}")
@@ -124,7 +262,9 @@ module Zui
124
262
  end
125
263
 
126
264
  def configure(arguments)
127
- raise ArgumentError, "configure accepts no arguments" unless arguments.empty?
265
+ parse_no_options!(arguments)
266
+ raise UsageError, "configure accepts no arguments" unless arguments.empty?
267
+
128
268
  platform = Platform.current.assert_supported!
129
269
  client = Client.new(platform:)
130
270
  lite_runtime = LiteRuntime.new(platform:)
@@ -143,17 +283,60 @@ module Zui
143
283
  @out.puts("Bundle --full: ready (uses this Ruby and the project's locked gems)")
144
284
  end
145
285
 
146
- def option_value(arguments, name)
147
- index = arguments.index(name)
148
- return nil unless index
149
- raise ArgumentError, "#{name} requires a value" if index == arguments.length - 1
150
- arguments.delete_at(index)
151
- arguments.delete_at(index)
286
+ def show_general_help
287
+ @out.puts(GENERAL_HELP)
288
+ 0
289
+ end
290
+
291
+ def show_command_help(arguments)
292
+ return show_general_help if arguments.empty? || arguments == ["-h"] || arguments == ["--help"]
293
+ raise UsageError, "help accepts only one command" unless arguments.length == 1
294
+
295
+ show_named_help(arguments.first)
296
+ end
297
+
298
+ def show_named_help(command)
299
+ help = COMMAND_HELP[command]
300
+ return unknown_command(command) unless help
301
+
302
+ @out.puts(help)
303
+ 0
304
+ end
305
+
306
+ def help_requested?(arguments)
307
+ arguments.any? { |argument| %w[-h --help].include?(argument) }
308
+ end
309
+
310
+ def print_version(arguments)
311
+ parse_no_options!(arguments)
312
+ raise UsageError, "version accepts no arguments" unless arguments.empty?
313
+
314
+ @out.puts(VERSION)
315
+ 0
316
+ end
317
+
318
+ def unknown_command(command)
319
+ @err.puts("zui: unknown command #{command.inspect}")
320
+ @err.puts("Run `zui --help` to see available commands.")
321
+ 64
322
+ end
323
+
324
+ def help_hint
325
+ if COMMANDS.key?(@active_command)
326
+ "Run `zui help #{@active_command}` for usage."
327
+ else
328
+ "Run `zui --help` to see available commands."
329
+ end
330
+ end
331
+
332
+ def parse_no_options!(arguments)
333
+ OptionParser.new.parse!(arguments)
152
334
  end
153
335
 
154
336
  def slug(value)
155
- result = value.to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")
156
- raise ArgumentError, "name must contain letters or numbers" if result.empty?
337
+ result = value.to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\z/, "")
338
+ raise UsageError, "name must contain letters or numbers" if result.empty?
339
+
157
340
  result
158
341
  end
159
342
 
data/lib/zui.rb CHANGED
@@ -28,7 +28,7 @@ require_relative "zui/distribution"
28
28
  require_relative "zui/dist_packager"
29
29
 
30
30
  module Zui
31
- VERSION = "0.0.9"
31
+ VERSION = "0.0.10"
32
32
  FRAMEWORK_ROOT = File.expand_path("..", __dir__)
33
33
 
34
34
  def self.app(&definition)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Moussa Ali