ruflet_rails 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58ab2a08d2694662952c449f89a6c539e01ad118ea38db22eb90b9eb13b4c16a
4
- data.tar.gz: bd95c4b9e1c9794a263ca29d429e035492de1a99148e6f5b5574d085493a3aa7
3
+ metadata.gz: 8ac570f3b6d92f8fc6d21723cc2814c658861c20e8b38d884e389185916d559f
4
+ data.tar.gz: 02cbfbdc2da5e487af2cd07b2d176066c6a5cd0487a141245ffc84ca0a23ff3f
5
5
  SHA512:
6
- metadata.gz: d0275388d9faf332d9e6bb84ab629f9fab542133c94c6231e9518bf4b1ffdeb6fdc73519ca9ef861fe1224d5e4e34dafa0ac750d333942ee7b75e9a502d92f70
7
- data.tar.gz: 2653405372a6c783e7996390d8254ae17e7dce914a2066839a1fb924f81c282fca75074a72cdf7094bca2269627b5933d1151149ee9a4218931b4ca0ce2e9b1a
6
+ metadata.gz: de4d8d0a43bda6f2ebbab447c8c25209d44cfabccae8fb584024a355cf1ba464577200737d3cfae692d2e2c52aca294ae13d556de95875dd07d8a407b5a1c8d3
7
+ data.tar.gz: 1b3a8dd5f338a32c9e91f30a1b0d85cd9618019b2903f931cd27de45cee4717830daeabc44dc6dff01daf9dcf1e503a7bfacff7f8f7f813e4efb06ca6dbb9b02
data/README.md CHANGED
@@ -9,26 +9,62 @@ No separate protocol gem is required.
9
9
 
10
10
  ```ruby
11
11
  # Gemfile
12
- gem "ruflet_rails", ">= 0.0.4"
12
+ gem "ruflet_rails", ">= 0.0.5"
13
13
  ```
14
14
 
15
- ```ruby
16
- # app/mobile/main.rb
17
- require "ruflet"
15
+ ## Install into Rails
18
16
 
19
- class MyApp < Ruflet::App
20
- def view(page)
21
- page.title = "Hello"
22
- page.add(text(value: "Hello Ruflet"))
23
- end
24
- end
17
+ ```bash
18
+ bin/rails generate ruflet:install
19
+ ```
25
20
 
26
- MyApp.new.run
21
+ This generator will:
22
+ - create `app/mobile/main.rb`
23
+ - create `ruflet.yaml`
24
+ - add the Ruflet mount route to `config/routes.rb`
25
+ - copy/configure `ruflet_client` when the template is available locally
26
+
27
+ Generated `ruflet.yaml`:
28
+
29
+ ```yaml
30
+ app:
31
+ name: My App
32
+ ruflet_client_url: ""
33
+
34
+ services: []
35
+
36
+ assets:
37
+ splash_screen: assets/splash.png
38
+ icon_launcher: assets/icon.png
27
39
  ```
28
40
 
41
+ For Rails apps, those asset paths are resolved from `app/assets/` during build.
42
+
43
+ ## Build client from Rails
44
+
45
+ Uses the same build pipeline as `ruflet build`:
46
+
47
+ ```bash
48
+ bundle exec rake ruflet:build[web]
49
+ bundle exec rake ruflet:build[macos]
50
+ bundle exec rake ruflet:build[windows]
51
+ bundle exec rake ruflet:build[linux]
52
+ bundle exec rake ruflet:build[apk]
53
+ bundle exec rake ruflet:build[android]
54
+ bundle exec rake ruflet:build[ios]
55
+ bundle exec rake ruflet:build[aab]
56
+ ```
57
+
58
+ ## Manual usage
59
+
29
60
  ```ruby
30
- # config/routes.rb
31
- mount Ruflet::Rails.mobile(Rails.root.join("app/mobile/main.rb")), at: "/ws"
61
+ # app/mobile/main.rb
62
+ require "ruflet"
63
+
64
+ Ruflet.run do |page|
65
+ page.title = "Hello"
66
+ page.add(text("Hello Ruflet"))
67
+ end
32
68
  ```
33
69
 
34
70
  Connect mobile to Rails base URL, e.g. `http://10.0.2.2:3000`.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "ruflet/rails/install_support"
5
+
6
+ module Ruflet
7
+ module Generators
8
+ class InstallGenerator < ::Rails::Generators::Base
9
+ desc "Install Ruflet into a Rails app."
10
+
11
+ def create_mobile_entrypoint
12
+ target = File.join(destination_root, "app/mobile/main.rb")
13
+ return if File.exist?(target)
14
+
15
+ FileUtils.mkdir_p(File.dirname(target))
16
+ File.write(
17
+ target,
18
+ Ruflet::Rails::InstallSupport.default_mobile_app_template(app_title: app_name)
19
+ )
20
+ end
21
+
22
+ def create_ruflet_yaml
23
+ target = File.join(destination_root, "ruflet.yaml")
24
+ return if File.exist?(target)
25
+
26
+ File.write(
27
+ target,
28
+ Ruflet::Rails::InstallSupport.default_ruflet_yaml(app_name: app_name)
29
+ )
30
+ end
31
+
32
+ def copy_client_template
33
+ copied = Ruflet::Rails::InstallSupport.copy_ruflet_client_template(destination_root)
34
+ say_status(:warn, "ruflet_client template not found; add it manually before building", :yellow) unless copied
35
+ end
36
+
37
+ def configure_client_template
38
+ Ruflet::Rails::InstallSupport.configure_ruflet_client(destination_root)
39
+ end
40
+
41
+ def add_routes
42
+ target = File.join(destination_root, "config/routes.rb")
43
+ return unless File.file?(target)
44
+
45
+ route = Ruflet::Rails::InstallSupport.route_snippet
46
+ return if File.read(target).include?(route)
47
+
48
+ insert_into_file target, " #{route}\n", after: /Rails\.application\.routes\.draw do\s*\n/
49
+ end
50
+
51
+ def print_install_status
52
+ say "ruflet.yaml generated"
53
+ end
54
+
55
+ private
56
+
57
+ def app_name
58
+ File.basename(destination_root).gsub(/[_-]+/, " ").split.map(&:capitalize).join(" ")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "yaml"
5
+
6
+ module Ruflet
7
+ module Rails
8
+ module InstallSupport
9
+ CLIENT_EXTENSION_MAP = {
10
+ "ads" => { package: "flet_ads", alias: "ruflet_ads" },
11
+ "audio" => { package: "flet_audio", alias: "ruflet_audio" },
12
+ "audio_recorder" => { package: "flet_audio_recorder", alias: "ruflet_audio_recorder" },
13
+ "camera" => { package: "flet_camera", alias: "ruflet_camera" },
14
+ "charts" => { package: "flet_charts", alias: "ruflet_charts" },
15
+ "code_editor" => { package: "flet_code_editor", alias: "ruflet_code_editor" },
16
+ "color_pickers" => { package: "flet_color_pickers", alias: "ruflet_color_picker" },
17
+ "datatable2" => { package: "flet_datatable2", alias: "ruflet_datatable2" },
18
+ "flashlight" => { package: "flet_flashlight", alias: "ruflet_flashlight" },
19
+ "geolocator" => { package: "flet_geolocator", alias: "ruflet_geolocator" },
20
+ "lottie" => { package: "flet_lottie", alias: "ruflet_lottie" },
21
+ "map" => { package: "flet_map", alias: "ruflet_map" },
22
+ "permission_handler" => { package: "flet_permission_handler", alias: "ruflet_permission_handler" },
23
+ "secure_storage" => { package: "flet_secure_storage", alias: "ruflet_secure_storage" },
24
+ "video" => { package: "flet_video", alias: "ruflet_video" },
25
+ "webview" => { package: "flet_webview", alias: "ruflet_webview" }
26
+ }.freeze
27
+
28
+ module_function
29
+
30
+ def default_mobile_app_template(app_title:)
31
+ <<~RUBY
32
+ require "ruflet"
33
+
34
+ Ruflet.run do |page|
35
+ page.title = #{app_title.inspect}
36
+ count = 0
37
+ count_text = text(count.to_s, size: 40)
38
+
39
+ page.add(
40
+ container(
41
+ expand: true,
42
+ alignment: Ruflet::MainAxisAlignment::CENTER,
43
+ content: column(
44
+ alignment: Ruflet::MainAxisAlignment::CENTER,
45
+ horizontal_alignment: Ruflet::CrossAxisAlignment::CENTER,
46
+ children: [
47
+ text("You have pushed the button this many times:"),
48
+ count_text
49
+ ]
50
+ )
51
+ ),
52
+ floating_action_button: fab(
53
+ icon: Ruflet::MaterialIcons::ADD,
54
+ on_click: ->(_e) do
55
+ count += 1
56
+ page.update(count_text, value: count.to_s)
57
+ end
58
+ )
59
+ )
60
+ end
61
+ RUBY
62
+ end
63
+
64
+ def default_ruflet_yaml(app_name:)
65
+ <<~YAML
66
+ app:
67
+ name: #{app_name}
68
+ ruflet_client_url: ""
69
+
70
+ services: []
71
+
72
+ assets:
73
+ splash_screen: assets/splash.png
74
+ icon_launcher: assets/icon.png
75
+ YAML
76
+ end
77
+
78
+ def route_snippet(entrypoint: "app/mobile/main.rb", mount_path: "/ws")
79
+ %(mount Ruflet::Rails.mobile(Rails.root.join("#{entrypoint}")), at: "#{mount_path}")
80
+ end
81
+
82
+ def client_template_root
83
+ env = ENV["RUFLET_CLIENT_TEMPLATE_DIR"].to_s.strip
84
+ return env if !env.empty? && Dir.exist?(env)
85
+
86
+ candidates = [
87
+ File.expand_path("../../../../../ruflet_client", __dir__),
88
+ File.expand_path("../../../../../templates/ruflet_flutter_template", __dir__)
89
+ ]
90
+ candidates.find { |path| Dir.exist?(path) }
91
+ end
92
+
93
+ def copy_ruflet_client_template(root)
94
+ template_root = client_template_root
95
+ return false unless template_root
96
+
97
+ target = File.join(root, "ruflet_client")
98
+ return true if Dir.exist?(target)
99
+
100
+ FileUtils.cp_r(template_root, target)
101
+ prune_client_template(target)
102
+ true
103
+ end
104
+
105
+ def prune_client_template(target)
106
+ %w[
107
+ .dart_tool
108
+ .idea
109
+ build
110
+ ios/Pods
111
+ ios/.symlinks
112
+ ios/Podfile.lock
113
+ macos/Pods
114
+ macos/Podfile.lock
115
+ android/.gradle
116
+ android/.kotlin
117
+ android/local.properties
118
+ ].each do |path|
119
+ full = File.join(target, path)
120
+ FileUtils.rm_rf(full) if File.exist?(full)
121
+ end
122
+ end
123
+
124
+ def configure_ruflet_client(root)
125
+ config_path = File.join(root, "ruflet.yaml")
126
+ return unless File.file?(config_path)
127
+
128
+ config = YAML.safe_load(File.read(config_path), aliases: true) || {}
129
+ extension_keys = Array(config["services"]).map { |v| normalize_extension_key(v) }.compact.uniq
130
+ extension_packages = extension_keys.filter_map { |key| CLIENT_EXTENSION_MAP[key]&.fetch(:package) }.uniq
131
+ extension_aliases = extension_keys.filter_map { |key| CLIENT_EXTENSION_MAP[key]&.fetch(:alias) }.uniq
132
+
133
+ client_dir = File.join(root, "ruflet_client")
134
+ return unless Dir.exist?(client_dir)
135
+
136
+ prune_client_pubspec(File.join(client_dir, "pubspec.yaml"), extension_packages)
137
+ prune_client_main(File.join(client_dir, "lib", "main.dart"), extension_aliases)
138
+ end
139
+
140
+ def normalize_extension_key(value)
141
+ key = value.to_s.strip.downcase
142
+ return nil if key.empty?
143
+
144
+ key.tr!("-", "_")
145
+ key.gsub!(/\A(flet_)+/, "")
146
+ key.gsub!(/\Aservice_/, "")
147
+ key.gsub!(/\Acontrol_/, "")
148
+ key = "file_picker" if key == "filepicker"
149
+ key
150
+ end
151
+
152
+ def prune_client_pubspec(path, selected_packages)
153
+ return unless File.file?(path)
154
+
155
+ data = YAML.safe_load(File.read(path), aliases: true) || {}
156
+ deps = (data["dependencies"] || {}).dup
157
+ deps.keys.each do |name|
158
+ next unless name.start_with?("flet_")
159
+ next if name == "flet"
160
+ next if selected_packages.include?(name)
161
+
162
+ deps.delete(name)
163
+ end
164
+ data["dependencies"] = deps
165
+ File.write(path, YAML.dump(data))
166
+ end
167
+
168
+ def prune_client_main(path, selected_aliases)
169
+ return unless File.file?(path)
170
+
171
+ lines = File.readlines(path)
172
+ alias_to_package = {}
173
+
174
+ lines.each do |line|
175
+ match = line.match(%r{\Aimport 'package:(flet_[^/]+)/\1\.dart' as ([a-zA-Z0-9_]+);})
176
+ alias_to_package[match[2]] = match[1] if match
177
+ end
178
+
179
+ kept = lines.select do |line|
180
+ import_match = line.match(%r{\Aimport 'package:(flet_[^/]+)/\1\.dart' as ([a-zA-Z0-9_]+);})
181
+ if import_match
182
+ package_name = import_match[1]
183
+ next true if package_name == "flet"
184
+ next true if selected_aliases.include?(import_match[2])
185
+ next false
186
+ end
187
+
188
+ extension_match = line.match(/\A\s*([a-zA-Z0-9_]+)\.Extension\(\),\s*\z/)
189
+ if extension_match
190
+ extension_alias = extension_match[1]
191
+ package_name = alias_to_package[extension_alias]
192
+ next true if package_name.nil?
193
+ next true if selected_aliases.include?(extension_alias)
194
+ next false
195
+ end
196
+
197
+ true
198
+ end
199
+
200
+ File.write(path, kept.join)
201
+ end
202
+ end
203
+ end
204
+ end
@@ -130,10 +130,23 @@ module Ruflet
130
130
  when 0xd9 then reader.read_string(reader.read_u8)
131
131
  when 0xda then reader.read_string(reader.read_u16)
132
132
  when 0xdb then reader.read_string(reader.read_u32)
133
+ when 0xc4 then reader.read_binary(reader.read_u8)
134
+ when 0xc5 then reader.read_binary(reader.read_u16)
135
+ when 0xc6 then reader.read_binary(reader.read_u32)
133
136
  when 0xdc then read_array(reader, reader.read_u16)
134
137
  when 0xdd then read_array(reader, reader.read_u32)
135
138
  when 0xde then read_map(reader, reader.read_u16)
136
139
  when 0xdf then read_map(reader, reader.read_u32)
140
+ when 0xd4
141
+ read_ext(reader, 1)
142
+ when 0xd5
143
+ read_ext(reader, 2)
144
+ when 0xd6
145
+ read_ext(reader, 4)
146
+ when 0xd7
147
+ read_ext(reader, 8)
148
+ when 0xd8
149
+ read_ext(reader, 16)
137
150
  when 0xc7
138
151
  read_ext(reader, reader.read_u8)
139
152
  when 0xc8
@@ -204,6 +217,7 @@ module Ruflet
204
217
  def read_f32 = read_exact(4).unpack1("g")
205
218
  def read_f64 = read_exact(8).unpack1("G")
206
219
  def read_string(size) = read_exact(size).force_encoding("UTF-8")
220
+ def read_binary(size) = read_exact(size)
207
221
  end
208
222
  end
209
223
  end
@@ -6,6 +6,25 @@ module Ruflet
6
6
  initializer "ruflet_rails.middleware" do |app|
7
7
  app.middleware.use Ruflet::Rails::Protocol::Middleware
8
8
  end
9
+
10
+ rake_tasks do
11
+ namespace :ruflet do
12
+ desc "Build Ruflet client for this Rails app. Usage: rake ruflet:build[web]"
13
+ task :build, [:platform] do |_task, args|
14
+ platform = args[:platform].to_s.strip
15
+ if platform.empty?
16
+ warn "Usage: rake ruflet:build[apk|android|ios|aab|web|macos|windows|linux]"
17
+ next
18
+ end
19
+
20
+ require "ruflet/cli"
21
+ exit_code = Dir.chdir(::Rails.root) do
22
+ Ruflet::CLI.command_build([platform])
23
+ end
24
+ raise SystemExit, exit_code unless exit_code.to_i.zero?
25
+ end
26
+ end
27
+ end
9
28
  end
10
29
  end
11
30
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.5" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.6" unless const_defined?(:VERSION)
5
5
  end
data/lib/ruflet_rails.rb CHANGED
@@ -5,8 +5,9 @@ if File.directory?(local_ruflet_lib) && !$LOAD_PATH.include?(local_ruflet_lib)
5
5
  $LOAD_PATH.unshift(local_ruflet_lib)
6
6
  end
7
7
 
8
- require "ruflet"
8
+ require "ruflet_core"
9
9
  require_relative "ruflet/rails/protocol"
10
+ require_relative "ruflet/rails/install_support"
10
11
  require_relative "ruflet/rails"
11
12
 
12
13
  if defined?(::Rails::Railtie)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruflet_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - AdamMusa
@@ -24,19 +24,19 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: ruflet
27
+ name: ruflet_core
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - '='
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.0.5
32
+ version: 0.0.11
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - '='
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 0.0.5
39
+ version: 0.0.11
40
40
  description: Rails-first integration package for mounting Ruflet mobile apps in Rails
41
41
  routes.
42
42
  email:
@@ -46,7 +46,9 @@ extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
48
  - README.md
49
+ - lib/generators/ruflet/install/install_generator.rb
49
50
  - lib/ruflet/rails.rb
51
+ - lib/ruflet/rails/install_support.rb
50
52
  - lib/ruflet/rails/protocol.rb
51
53
  - lib/ruflet/rails/protocol/context.rb
52
54
  - lib/ruflet/rails/protocol/endpoint.rb