scarpe 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.yardopts +11 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +112 -0
- data/README.md +31 -24
- data/Rakefile +13 -1
- data/docs/yard/catscradle.md +44 -0
- data/docs/yard/template/default/fulldoc/html/setup.rb +13 -0
- data/docs/yard/template/default/layout/html/setup.rb +9 -0
- data/examples/background_with_image.rb +16 -0
- data/examples/bloopsaphone/working/bronx_army_knife.rb +66 -0
- data/examples/bloopsaphone/working/morning_serenity.rb +21 -0
- data/examples/bloopsaphone/working/simpsons_theme_song_by_why.rb +6 -4
- data/examples/button_go_away.rb +1 -1
- data/examples/check.rb +18 -0
- data/examples/clear_and_append.rb +24 -0
- data/examples/download_and_show_image.rb +28 -0
- data/examples/edit_box.rb +3 -5
- data/examples/fonts.rb +2 -2
- data/examples/get_headers.rb +10 -0
- data/examples/highlander.rb +2 -0
- data/examples/link.rb +2 -2
- data/examples/local_fonts.rb +4 -0
- data/examples/local_images.rb +4 -0
- data/examples/motion_events.rb +20 -0
- data/examples/parse_xl_funnies.rb +58 -0
- data/examples/radio/radio.rb +16 -0
- data/examples/radio/radio_groups.rb +18 -0
- data/examples/radio/radio_same_slot.rb +6 -0
- data/examples/ruby_racer.rb +13 -15
- data/examples/selfitude.rb +18 -0
- data/examples/shapes/shapes_fill.rb +4 -3
- data/examples/shoes_school.rb +2 -4
- data/examples/show_hide.rb +6 -0
- data/examples/skip_ci/change_my_audio_source.rb +21 -0
- data/examples/skip_ci/guitar_fretboard.rb +137 -0
- data/examples/video.rb +10 -0
- data/exe/scarpe +42 -66
- data/fonts/Pacifico.ttf +0 -0
- data/lacci/Gemfile +22 -0
- data/lacci/Gemfile.lock +72 -0
- data/lacci/Rakefile +12 -0
- data/lacci/lacci.gemspec +37 -0
- data/lacci/lib/lacci/scarpe_cli.rb +70 -0
- data/lacci/lib/lacci/scarpe_core.rb +21 -0
- data/lacci/lib/lacci/version.rb +13 -0
- data/lacci/lib/shoes/app.rb +264 -0
- data/{lib/scarpe → lacci/lib/shoes}/background.rb +1 -1
- data/{lib/scarpe → lacci/lib/shoes}/border.rb +1 -1
- data/{lib/scarpe → lacci/lib/shoes}/colors.rb +1 -1
- data/lacci/lib/shoes/constants.rb +29 -0
- data/{lib/scarpe → lacci/lib/shoes}/display_service.rb +40 -45
- data/lacci/lib/shoes/download.rb +123 -0
- data/lacci/lib/shoes/log.rb +71 -0
- data/lacci/lib/shoes/spacing.rb +9 -0
- data/{lib/scarpe → lacci/lib/shoes}/widget.rb +63 -43
- data/{lib/scarpe → lacci/lib/shoes/widgets}/alert.rb +3 -3
- data/{lib/scarpe → lacci/lib/shoes/widgets}/arc.rb +7 -5
- data/{lib/scarpe → lacci/lib/shoes/widgets}/button.rb +3 -3
- data/lacci/lib/shoes/widgets/check.rb +28 -0
- data/lacci/lib/shoes/widgets/document_root.rb +20 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_box.rb +10 -5
- data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_line.rb +2 -2
- data/lacci/lib/shoes/widgets/flow.rb +22 -0
- data/lacci/lib/shoes/widgets/font.rb +14 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/image.rb +3 -7
- data/lacci/lib/shoes/widgets/line.rb +18 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/link.rb +2 -2
- data/{lib/scarpe → lacci/lib/shoes/widgets}/list_box.rb +2 -2
- data/{lib/scarpe → lacci/lib/shoes/widgets}/para.rb +4 -26
- data/lacci/lib/shoes/widgets/radio.rb +35 -0
- data/lacci/lib/shoes/widgets/shape.rb +37 -0
- data/lacci/lib/shoes/widgets/slot.rb +75 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/span.rb +2 -2
- data/lacci/lib/shoes/widgets/stack.rb +24 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/star.rb +6 -9
- data/lacci/lib/shoes/widgets/subscription_item.rb +60 -0
- data/lacci/lib/shoes/widgets/text_widget.rb +51 -0
- data/lacci/lib/shoes/widgets/video.rb +15 -0
- data/lacci/lib/shoes/widgets.rb +29 -0
- data/lacci/lib/shoes.rb +127 -0
- data/lacci/test/test_colors.rb +39 -0
- data/lacci/test/test_helper.rb +9 -0
- data/lacci/test/test_lacci.rb +9 -0
- data/lib/scarpe/cats_cradle.rb +249 -0
- data/lib/scarpe/evented_assertions.rb +88 -0
- data/lib/scarpe/version.rb +1 -1
- data/lib/scarpe/wv/alert.rb +3 -2
- data/lib/scarpe/wv/app.rb +30 -8
- data/lib/scarpe/wv/arc.rb +5 -6
- data/lib/scarpe/wv/background.rb +10 -1
- data/lib/scarpe/wv/border.rb +5 -3
- data/lib/scarpe/wv/button.rb +11 -9
- data/lib/scarpe/wv/check.rb +29 -0
- data/lib/scarpe/wv/control_interface.rb +14 -20
- data/lib/scarpe/wv/control_interface_test.rb +13 -28
- data/lib/scarpe/wv/document_root.rb +3 -45
- data/lib/scarpe/wv/edit_box.rb +5 -7
- data/lib/scarpe/wv/edit_line.rb +2 -2
- data/lib/scarpe/wv/flow.rb +10 -20
- data/lib/scarpe/wv/font.rb +36 -0
- data/lib/scarpe/wv/html.rb +3 -2
- data/lib/scarpe/wv/image.rb +7 -2
- data/lib/scarpe/wv/line.rb +4 -7
- data/lib/scarpe/wv/link.rb +1 -0
- data/lib/scarpe/wv/list_box.rb +3 -3
- data/lib/scarpe/wv/para.rb +16 -14
- data/lib/scarpe/wv/radio.rb +34 -0
- data/lib/scarpe/wv/shape.rb +44 -8
- data/lib/scarpe/wv/slot.rb +81 -0
- data/lib/scarpe/wv/spacing.rb +1 -1
- data/lib/scarpe/wv/span.rb +10 -8
- data/lib/scarpe/wv/stack.rb +10 -30
- data/lib/scarpe/wv/star.rb +11 -12
- data/lib/scarpe/wv/subscription_item.rb +50 -0
- data/lib/scarpe/wv/video.rb +34 -0
- data/lib/scarpe/wv/web_wrangler.rb +238 -58
- data/lib/scarpe/wv/webview_local_display.rb +27 -5
- data/lib/scarpe/wv/webview_relay_display.rb +18 -119
- data/lib/scarpe/wv/webview_relay_util.rb +143 -0
- data/lib/scarpe/wv/widget.rb +80 -11
- data/lib/scarpe/wv/wv_display_worker.rb +17 -4
- data/lib/scarpe/wv.rb +33 -4
- data/lib/scarpe/wv_local.rb +1 -1
- data/lib/scarpe/wv_relay.rb +1 -1
- data/lib/scarpe.rb +3 -32
- data/scarpe-components/.gitignore +1 -0
- data/scarpe-components/Gemfile +22 -0
- data/scarpe-components/README.md +35 -0
- data/scarpe-components/Rakefile +12 -0
- data/scarpe-components/lib/scarpe/components/base64.rb +29 -0
- data/scarpe-components/lib/scarpe/components/file_helpers.rb +65 -0
- data/scarpe-components/lib/scarpe/components/modular_logger.rb +113 -0
- data/scarpe-components/lib/scarpe/components/print_logger.rb +43 -0
- data/{lib/scarpe → scarpe-components/lib/scarpe/components}/promises.rb +102 -35
- data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +170 -0
- data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +217 -0
- data/scarpe-components/lib/scarpe/components/version.rb +7 -0
- data/scarpe-components/scarpe-components.gemspec +38 -0
- data/scarpe-components/test/test_components.rb +9 -0
- data/scarpe-components/test/test_helper.rb +23 -0
- data/scarpe-components/test/test_promises.rb +260 -0
- data/scarpe-components/test/test_segmented_app_files.rb +182 -0
- data/{lib/scarpe → spikes}/glibui/widget.rb +2 -2
- data/{lib/scarpe → spikes}/glibui.rb +1 -1
- data/templates/basic_class_template.erb +1 -1
- data/templates/class_template_with_event_bind.erb +1 -1
- data/templates/class_template_with_shapes.erb +1 -1
- data/templates/webview_template.erb +0 -3
- metadata +151 -118
- data/examples/fill.rb +0 -25
- data/examples/legacy/not_checked/shoes-contrib/basic/class-book.yaml +0 -387
- data/examples/legacy/not_checked/shoes-contrib/good/good-clock.rb +0 -51
- data/examples/legacy/not_checked/shoes-contrib/good/good-follow.rb +0 -26
- data/examples/legacy/not_checked/shoes-contrib/good/good-reminder.rb +0 -174
- data/examples/legacy/not_checked/shoes-contrib/good/good-vjot.rb +0 -56
- data/examples/legacy/not_checked/shoes-contrib/simple/simple-timer.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/good-clock.rb +0 -51
- data/examples/legacy/not_checked/shoes-dep-samples/good-follow.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/good-reminder.rb +0 -174
- data/examples/legacy/not_checked/shoes-dep-samples/good-vjot.rb +0 -56
- data/examples/legacy/not_checked/shoes-dep-samples/simple-accordion.rb +0 -75
- data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-shapes.rb +0 -17
- data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-text.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-arc.rb +0 -23
- data/examples/legacy/not_checked/shoes-dep-samples/simple-bounce.rb +0 -24
- data/examples/legacy/not_checked/shoes-dep-samples/simple-calc.rb +0 -70
- data/examples/legacy/not_checked/shoes-dep-samples/simple-chipmunk.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/simple-control-sizes.rb +0 -24
- data/examples/legacy/not_checked/shoes-dep-samples/simple-curve.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/simple-dialogs.rb +0 -29
- data/examples/legacy/not_checked/shoes-dep-samples/simple-draw.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-editor.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-form.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-form.shy +0 -0
- data/examples/legacy/not_checked/shoes-dep-samples/simple-mask.rb +0 -21
- data/examples/legacy/not_checked/shoes-dep-samples/simple-menu.rb +0 -31
- data/examples/legacy/not_checked/shoes-dep-samples/simple-menu1.rb +0 -35
- data/examples/legacy/not_checked/shoes-dep-samples/simple-rubygems.rb +0 -29
- data/examples/legacy/not_checked/shoes-dep-samples/simple-slide.rb +0 -45
- data/examples/legacy/not_checked/shoes-dep-samples/simple-sphere.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-sqlite3.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-timer.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-video.rb +0 -13
- data/examples/legacy/not_checked/simple/anim-text.rb +0 -13
- data/examples/legacy/not_checked/simple/arc.rb +0 -23
- data/examples/legacy/not_checked/simple/bounce.rb +0 -24
- data/examples/legacy/not_checked/simple/chipmunk.rb +0 -26
- data/examples/legacy/not_checked/simple/curve.rb +0 -26
- data/examples/legacy/not_checked/simple/dialogs.rb +0 -29
- data/examples/legacy/not_checked/simple/downloader.rb +0 -40
- data/examples/legacy/not_checked/simple/draw.rb +0 -13
- data/examples/legacy/not_checked/simple/mask.rb +0 -21
- data/examples/legacy/not_checked/simple/slide.rb +0 -45
- data/examples/legacy/not_checked/simple/sphere.rb +0 -28
- data/lib/constants.rb +0 -5
- data/lib/scarpe/app.rb +0 -78
- data/lib/scarpe/document_root.rb +0 -20
- data/lib/scarpe/fill.rb +0 -23
- data/lib/scarpe/flow.rb +0 -19
- data/lib/scarpe/line.rb +0 -25
- data/lib/scarpe/logger.rb +0 -155
- data/lib/scarpe/shape.rb +0 -19
- data/lib/scarpe/spacing.rb +0 -9
- data/lib/scarpe/stack.rb +0 -70
- data/lib/scarpe/text_widget.rb +0 -42
- data/lib/scarpe/unit_test_helpers.rb +0 -163
- data/lib/scarpe/widgets.rb +0 -30
- data/lib/scarpe/wv/fill.rb +0 -30
- data/lib/scarpe/wv/shape_helper.rb +0 -44
- data/scarpe-0.2.0.gem +0 -0
- /data/{lib/scarpe → spikes}/glibui/README.md +0 -0
- /data/{lib/scarpe → spikes}/glibui/alert.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/app.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/background.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/border.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/button.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/dimensions.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/document_root.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/edit_box.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/edit_line.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/flow.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/html.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/image.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/link.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/local_display.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/para.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/spacing.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/stack.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/text_widget.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/alert.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/button.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/colors.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/core.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/flow.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/libui.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/notepad.md +0 -0
- /data/{lib/scarpe → spikes}/libui/para.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/stack.rb +0 -0
data/lib/scarpe.rb
CHANGED
@@ -1,42 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "scarpe/logger"
|
4
|
-
|
5
|
-
# This will never be triggered -- we use the (...) feature below, which means this
|
6
|
-
# file won't even parse in old Rubies.
|
7
3
|
if RUBY_VERSION[0..2] < "3.2"
|
8
|
-
|
4
|
+
Shoes::Log.logger("Scarpe").error("Scarpe requires Ruby 3.2 or higher!")
|
9
5
|
exit(-1)
|
10
6
|
end
|
11
7
|
|
12
|
-
require "
|
13
|
-
require "
|
14
|
-
|
15
|
-
require_relative "constants"
|
16
|
-
|
17
|
-
class Scarpe::Error < StandardError; end
|
18
|
-
|
19
|
-
require_relative "scarpe/version"
|
20
|
-
require_relative "scarpe/promises"
|
21
|
-
require_relative "scarpe/display_service"
|
22
|
-
require_relative "scarpe/widgets"
|
23
|
-
|
24
|
-
require "bloops"
|
8
|
+
require "shoes"
|
9
|
+
require "lacci/scarpe_core"
|
25
10
|
|
26
11
|
d_s = ENV["SCARPE_DISPLAY_SERVICE"] || "wv_local"
|
27
12
|
# This is require, not require_relative, to allow gems to supply a new display service
|
28
13
|
require "scarpe/#{d_s}"
|
29
|
-
|
30
|
-
#Constants Module
|
31
|
-
include Constants
|
32
|
-
|
33
|
-
class Scarpe
|
34
|
-
class << self
|
35
|
-
def app(...)
|
36
|
-
app = Scarpe::App.new(...)
|
37
|
-
app.init
|
38
|
-
app.run
|
39
|
-
app.destroy
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -0,0 +1 @@
|
|
1
|
+
scarpe-components-*.gem
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
gem "lacci", path: "../lacci"
|
8
|
+
|
9
|
+
gem "rake", "~> 13.0"
|
10
|
+
|
11
|
+
gem "nokogiri"
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem "minitest", "~> 5.0"
|
15
|
+
gem "minitest-reporters"
|
16
|
+
end
|
17
|
+
|
18
|
+
group :development do
|
19
|
+
gem "debug"
|
20
|
+
gem "rubocop", "~> 1.21"
|
21
|
+
gem "rubocop-shopify"
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Scarpe-Components
|
2
|
+
|
3
|
+
Scarpe is several things. We package up the Shoes API with as few implementation details as possible (called Lacci.) We have a Webview-based display library, in both local and over-a-socket (relay) versions. We have a Wasm display library (Scarpe-Wasm) in a separate gem.
|
4
|
+
|
5
|
+
And we have various default implementations of different reusable components. Scarpe's hierarchical logger isn't particularly specific to your display library or underlying GUI library, but we'd like it to be usable by multiple display libs. CatsCradle is only useful if you're trying to fix up an impedance mismatch between Ruby and an evented under-layer, but it's not really specific to Webview. Scarpe's default downloader, using Ruby's built-in HTTP libraries, is good in many cases but you might want to replace it (e.g. with Typhoeus for better parallel downloads or Hystrix for robustness to bad network connections) in some cases.
|
6
|
+
|
7
|
+
Part of our solution is the Scarpe-Components gem, which lives in the same repository as Scarpe (for now?). These components can live there. Any specific display library can pick and choose what it wants or needs and handle its dependencies as it sees fit.
|
8
|
+
|
9
|
+
## Dependency Hell
|
10
|
+
|
11
|
+
A "gem full of optional reusable components" presents an awkward challenge for Rubygems. What are the gem's dependencies? Do you make it depend on every possible library, requiring FastImage and Nokogiri and SQLite and whatever else any component might ever require? That would be as bad as making ActiveRecord need MySQL and SQLite and Postgres and Oracle and... But if you don't give it *any* dependencies, you're going to have a harsh surprise at runtime when Bundler can't find any of the gems you need.
|
12
|
+
|
13
|
+
You can break everything up into tiny pieces, like ActiveRecord having a separate adapter for certain databases. You could do the same with an activerecord-mysql and activerecord-postgres and activerecord-sqlite gem to complete the set. Or, like, ActiveRecord, you can say "declare activerecord as a dependency, and also one or more other database gems, and we'll figure out what's available at runtime." That approach can be pretty fragile and it adds extra complexity -- how do you make sure everything is required at the right time and ActiveRecord can find everything that's available? How do you balance gem dependencies that are built into ActiveRecord with those for unusual databases that get added by other gems, later? Your plugin system (like ActiveRecord's) can get extensive and fragile.
|
14
|
+
|
15
|
+
Scarpe-Components kicks that problem down the road to the display libraries. Would a particular display library like to use the FastImage-based image implementation? Great! It can declare a dependency on the FastImage gem. Would it like to optionally allow the built-in Ruby downloader, but also have an optional robustified version using RestClient? Lovely. It can either create multiple gems (e.g. scarpe-gtk-rcdownload and scarpe-gtk-plaindownload) or look for RestClient being available at runtime, as it pleases. The FastImage implementation lives in Scarpe-Components, but the dependency is declared by the display library or the app.
|
16
|
+
|
17
|
+
## Using an Implementation
|
18
|
+
|
19
|
+
Components in Scarpe-Components are designed to be used individually. They can require each other, but they should only require components they will actually use. The whole library is designed to be used a la carte, and normally no display service will use every component.
|
20
|
+
|
21
|
+
A display library will declare scarpe-components as a dependency. Then, usually from its named require file (e.g. wv_local.rb, wv_relay.rb, wasm_local.rb) it will set up those dependencies by requiring components that it wants and if necessary creating and configuring them. That way a specific display service (e.g. wv_local) can require a specific component (e.g. Logging-gem-based hierarchical logging) and configure it how it wants (e.g. ENV var pointing to a local JSON config file.)
|
22
|
+
|
23
|
+
## How is this Different from Lacci?
|
24
|
+
|
25
|
+
Scarpe already has a gem full of reusable components, one that every Scarpe-based application already has to use. Lacci declares the Shoes API, and is 100% required for every Scarpe-based Shoes application everywhere.
|
26
|
+
|
27
|
+
So how do you tell what goes into Scarpe-Components vs what goes into Lacci?
|
28
|
+
|
29
|
+
Lacci is, at heart, an API with as little implementation as possible. It declares the Shoes GUI objects, but not how to display them. Ordinarily a Shoes application will require (in the sense of Kernel#require) every part of Lacci -- it will load the entire library. Even if we add optional components (e.g. a Lacci-based Bloops API with no implementation behind it), those components will be loaded by every Shoes app that uses that part of the API.
|
30
|
+
|
31
|
+
As a result, Lacci should have minimal (preferably zero) dependencies. Any code doing "real work" should be removed from Lacci completely, as soon as possible. Since *every* Shoes app is going to require Lacci, it should be possible to use it with essentially no dependencies, minimal memory footprint, minimal load time. It is a skeleton to hang functionality on, not a thing that functions for itself.
|
32
|
+
|
33
|
+
Scarpe-Components is a grab bag of default implementations, intended to be replaceable. But each of them does something. Most of them have dependencies. It's fine for a Scarpe-Component implementation to depend on the Logging gem, provided it says it does. Nokogiri? 100% fair. Does it do something with a nontrivial amount of computation, like rendering HTML output? No problem. This is very different from Lacci.
|
34
|
+
|
35
|
+
If a component should be reused, it's probably fine to put it into Scarpe-Components. It *might* be fine to put it in Lacci. For Scarpe-Components, ask yourself, "will more than one Scarpe display service possibly want to use this?" For Lacci, the test is more strict: "will *every* Scarpe display service want to use this? Does it have no dependencies at all, and do very little computation and take very little memory?"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
class Scarpe; end
|
7
|
+
module Scarpe::Components; end
|
8
|
+
class Scarpe
|
9
|
+
module Components::Base64
|
10
|
+
def valid_url?(string)
|
11
|
+
uri = URI.parse(string)
|
12
|
+
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
13
|
+
rescue URI::InvalidURIError, URI::BadURIError
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def encode_file_to_base64(image_filename)
|
18
|
+
directory_path = File.dirname(__FILE__, 5)
|
19
|
+
|
20
|
+
image_path = File.join(directory_path, image_filename)
|
21
|
+
|
22
|
+
image_data = File.binread(image_path)
|
23
|
+
|
24
|
+
encoded_data = ::Base64.strict_encode64(image_data)
|
25
|
+
|
26
|
+
encoded_data
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tempfile"
|
4
|
+
|
5
|
+
# These can be used for unit tests, but also more generally.
|
6
|
+
|
7
|
+
module Scarpe::Components::FileHelpers
|
8
|
+
# Create a temporary file with the given prefix and contents.
|
9
|
+
# Execute the block of code with it in place. Make sure
|
10
|
+
# it gets cleaned up afterward.
|
11
|
+
#
|
12
|
+
# @param prefix [String] the prefix passed to Tempfile to identify this file on disk
|
13
|
+
# @param contents [String] the file contents that should be written to Tempfile
|
14
|
+
# @param dir [String] the directory to create the tempfile in
|
15
|
+
# @yield The code to execute with the tempfile present
|
16
|
+
# @yieldparam the path of the new tempfile
|
17
|
+
def with_tempfile(prefix, contents, dir: Dir.tmpdir)
|
18
|
+
t = Tempfile.new(prefix, dir)
|
19
|
+
t.write(contents)
|
20
|
+
t.flush # Make sure the contents are written out
|
21
|
+
|
22
|
+
yield(t.path)
|
23
|
+
ensure
|
24
|
+
t.close
|
25
|
+
t.unlink
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create multiple tempfiles, with given contents, in given
|
29
|
+
# directories, and execute the block in that context.
|
30
|
+
# When the block is finished, make sure all tempfiles are
|
31
|
+
# deleted.
|
32
|
+
#
|
33
|
+
# Pass an array of arrays, where each array is of the form:
|
34
|
+
# [prefix, contents, (optional)dir]
|
35
|
+
#
|
36
|
+
# I don't love inlining with_tempfile's contents into here.
|
37
|
+
# But calling it iteratively or recursively was difficult
|
38
|
+
# when I tried it the obvious ways.
|
39
|
+
#
|
40
|
+
# This method should be equivalent to calling with_tempfile
|
41
|
+
# once for each entry in the array, in a set of nested
|
42
|
+
# blocks.
|
43
|
+
#
|
44
|
+
# @param tf_specs [Array<Array>] The array of tempfile prefixes, contents and directories
|
45
|
+
# @yield The code to execute with those tempfiles present
|
46
|
+
# @yieldparam An array of paths to tempfiles, in the same order as tf_specs
|
47
|
+
def with_tempfiles(tf_specs, &block)
|
48
|
+
tempfiles = []
|
49
|
+
tf_specs.each do |prefix, contents, dir|
|
50
|
+
dir ||= Dir.tmpdir
|
51
|
+
t = Tempfile.new(prefix, dir)
|
52
|
+
tempfiles << t
|
53
|
+
t.write(contents)
|
54
|
+
t.flush # Make sure the contents are written out
|
55
|
+
end
|
56
|
+
|
57
|
+
args = tempfiles.map(&:path)
|
58
|
+
yield(args)
|
59
|
+
ensure
|
60
|
+
tempfiles.each do |t|
|
61
|
+
t.close
|
62
|
+
t.unlink
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logging"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
require "shoes/log"
|
7
|
+
|
8
|
+
# Requires the logging gem
|
9
|
+
|
10
|
+
class Scarpe; end
|
11
|
+
module Scarpe::Components; end
|
12
|
+
class Scarpe
|
13
|
+
class Components::ModularLogImpl
|
14
|
+
include Shoes::Log # for constants
|
15
|
+
|
16
|
+
def logger_for_component(component)
|
17
|
+
Logging.logger[component]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def name_to_severity(data)
|
23
|
+
case data
|
24
|
+
when "debug"
|
25
|
+
:debug
|
26
|
+
when "info"
|
27
|
+
:info
|
28
|
+
when "warn"
|
29
|
+
:warn
|
30
|
+
when "err", "error"
|
31
|
+
:error
|
32
|
+
when "fatal"
|
33
|
+
:fatal
|
34
|
+
else
|
35
|
+
raise "Don't know how to treat #{data.inspect} as a logger severity!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def json_to_appender(data)
|
40
|
+
case data.downcase
|
41
|
+
when "stdout"
|
42
|
+
Logging.appenders.stdout layout: @custom_log_layout
|
43
|
+
when "stderr"
|
44
|
+
Logging.appenders.stderr layout: @custom_log_layout
|
45
|
+
when String
|
46
|
+
Logging.appenders.file data, layout: @custom_log_layout
|
47
|
+
else
|
48
|
+
raise "Don't know how to convert #{data.inspect} to an appender!"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def json_configure_logger(logger, data)
|
53
|
+
case data
|
54
|
+
in String
|
55
|
+
sev = name_to_severity(data)
|
56
|
+
logger.level = sev
|
57
|
+
in [level, *locations]
|
58
|
+
if logger.name != "root"
|
59
|
+
# The Logging gem doesn't have an additive property on the root logger
|
60
|
+
logger.additive = false # Don't also log to parent/root loggers
|
61
|
+
end
|
62
|
+
|
63
|
+
logger.appenders = locations.map { |where| json_to_appender(where) }
|
64
|
+
|
65
|
+
logger.level = name_to_severity(level)
|
66
|
+
else
|
67
|
+
raise "Don't know how to use #{data.inspect} to specify a logger!"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def freeze_log_config(log_config)
|
72
|
+
log_config.each do |k, v|
|
73
|
+
k.freeze
|
74
|
+
v.freeze
|
75
|
+
v.each(&:freeze) if v.is_a?(Array)
|
76
|
+
end
|
77
|
+
log_config.freeze
|
78
|
+
end
|
79
|
+
|
80
|
+
public
|
81
|
+
|
82
|
+
def configure_logger(log_config)
|
83
|
+
# TODO: custom coloring? https://github.com/TwP/logging/blob/master/examples/colorization.rb
|
84
|
+
@custom_log_layout = Logging.layouts.pattern pattern: '[%r] %-5l %c: %m\n'
|
85
|
+
|
86
|
+
if log_config.is_a?(String) && File.exist?(log_config)
|
87
|
+
log_config = JSON.load_file(log_config)
|
88
|
+
end
|
89
|
+
|
90
|
+
log_config = freeze_log_config(log_config) unless log_config.nil?
|
91
|
+
@current_log_config = log_config # Save a copy for later
|
92
|
+
|
93
|
+
Logging.reset # Reset all Logging settings to defaults
|
94
|
+
Logging.reopen # For log-reconfig (e.g. test failures), often important to *not* store an open handle to a moved file
|
95
|
+
return if log_config.nil?
|
96
|
+
|
97
|
+
Logging.logger.root.appenders = [Logging.appenders.stdout]
|
98
|
+
|
99
|
+
default_logger = log_config[DEFAULT_COMPONENT] || "info"
|
100
|
+
json_configure_logger(Logging.logger.root, default_logger)
|
101
|
+
|
102
|
+
log_config.each do |component, logger_data|
|
103
|
+
next if component == DEFAULT_COMPONENT
|
104
|
+
|
105
|
+
sublogger = Logging.logger[component]
|
106
|
+
json_configure_logger(sublogger, logger_data)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#Shoes::Log.instance = Scarpe::Components::ModularLogImpl.new
|
113
|
+
#Shoes::Log.configure_logger(Shoes::Log::DEFAULT_LOG_CONFIG)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shoes/log"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
class Scarpe; end
|
7
|
+
module Scarpe::Components; end
|
8
|
+
class Scarpe::Components::PrintLogImpl
|
9
|
+
include Shoes::Log # for constants
|
10
|
+
|
11
|
+
class PrintLogger
|
12
|
+
def initialize(component_name)
|
13
|
+
@comp_name = component_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def error(msg)
|
17
|
+
puts "#{@comp_name} error: #{msg}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(msg)
|
21
|
+
puts "#{@comp_name} warn: #{msg}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug(msg)
|
25
|
+
puts "#{@comp_name} debug: #{msg}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def info(msg)
|
29
|
+
puts "#{@comp_name} info: #{msg}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger_for_component(component)
|
34
|
+
PrintLogger.new(component.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
def configure_logger(log_config)
|
38
|
+
# For now, ignore
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#Shoes::Log.instance = Scarpe::PrintLogImpl.new
|
43
|
+
#Shoes::Log.configure_logger(Shoes::Log::DEFAULT_LOG_CONFIG)
|
@@ -1,42 +1,56 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
# But there's not really a Ruby implementation of Promises *without* an
|
6
|
-
# attached form of concurrency. So here we are, writing our own :-/
|
7
|
-
#
|
8
|
-
# In theory you could probably write some kind of "no-op thread pool"
|
9
|
-
# for the ruby-concurrency gem, pass it manually to every promise we
|
10
|
-
# created and then raise an exception any time we tried to do something
|
11
|
-
# in the background. That's probably more code than writing our own, though,
|
12
|
-
# and we'd be fighting it constantly.
|
13
|
-
#
|
14
|
-
# This class is inspired by concurrent-ruby [Promise](https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Promise.html)
|
15
|
-
# which is inspired by Javascript Promises, which is what we actually need
|
16
|
-
# for our use case. We can't easily tell when our WebView begins processing
|
17
|
-
# our request, which removes the :processing state. This can be used for
|
18
|
-
# executing JS, but also generally waiting on events.
|
19
|
-
#
|
20
|
-
# We don't fully control ordering here, so it *is* conceivable that a
|
21
|
-
# child waiting on a parent can be randomly fulfilled, even if we didn't
|
22
|
-
# expect it. We don't consider that an error. Similarly, we'll call
|
23
|
-
# on_scheduled callbacks if a promise is fulfilled, even though we
|
24
|
-
# never explicitly scheduled it. If a promise is *rejected* without
|
25
|
-
# ever being scheduled, we won't call those callbacks.
|
26
|
-
|
3
|
+
class Scarpe; end
|
4
|
+
module Scarpe::Components; end
|
27
5
|
class Scarpe
|
6
|
+
# Scarpe::Promise is a promises library, but one with no form of built-in
|
7
|
+
# concurrency. Instead, promise callbacks are executed synchronously.
|
8
|
+
# Even execution is usually synchronous, but can also be handled manually
|
9
|
+
# for forms of execution not controlled in Ruby (like Webview.)
|
10
|
+
#
|
11
|
+
# Funny thing... We need promises as an API concept since we have a JS event
|
12
|
+
# loop doing its thing, and we need to respond to actions that it takes.
|
13
|
+
# But there's not really a Ruby implementation of Promises *without* an
|
14
|
+
# attached form of concurrency. So here we are, writing our own :-/
|
15
|
+
#
|
16
|
+
# In theory you could probably write some kind of "no-op thread pool"
|
17
|
+
# for the ruby-concurrency gem, pass it manually to every promise we
|
18
|
+
# created and then raise an exception any time we tried to do something
|
19
|
+
# in the background. That's probably more code than writing our own, though,
|
20
|
+
# and we'd be fighting it constantly.
|
21
|
+
#
|
22
|
+
# This class is inspired by concurrent-ruby [Promise](https://ruby-concurrency.github.io/concurrent-ruby/1.1.5/Concurrent/Promise.html)
|
23
|
+
# which is inspired by Javascript Promises, which is what we actually need
|
24
|
+
# for our use case. We can't easily tell when our WebView begins processing
|
25
|
+
# our request, which removes the :processing state. This can be used for
|
26
|
+
# executing JS, but also generally waiting on events.
|
27
|
+
#
|
28
|
+
# We don't fully control ordering here, so it *is* conceivable that a
|
29
|
+
# child waiting on a parent can be randomly fulfilled, even if we didn't
|
30
|
+
# expect it. We don't consider that an error. Similarly, we'll call
|
31
|
+
# on_scheduled callbacks if a promise is fulfilled, even though we
|
32
|
+
# never explicitly scheduled it. If a promise is *rejected* without
|
33
|
+
# ever being scheduled, we won't call those callbacks.
|
28
34
|
class Promise
|
29
|
-
include
|
35
|
+
include Shoes::Log
|
30
36
|
|
37
|
+
# The unscheduled promise state means it's waiting on a parent promise that
|
38
|
+
# hasn't completed yet. The pending state means it's waiting to execute.
|
39
|
+
# Fulfilled means it has completed successfully and returned a value,
|
40
|
+
# while rejected means it has failed, normally producing a reason.
|
31
41
|
PROMISE_STATES = [:unscheduled, :pending, :fulfilled, :rejected]
|
32
42
|
|
43
|
+
# The state of the promise, which should be one of PROMISE_STATES
|
33
44
|
attr_reader :state
|
45
|
+
|
46
|
+
# The parent promises of this promise, sometimes an empty array
|
34
47
|
attr_reader :parents
|
48
|
+
|
49
|
+
# If the promise is fulfilled, this is the value returned
|
35
50
|
attr_reader :returned_value
|
36
|
-
attr_reader :reason
|
37
51
|
|
38
|
-
#
|
39
|
-
|
52
|
+
# If the promise is rejected, this is the reason, sometimes an exception
|
53
|
+
attr_reader :reason
|
40
54
|
|
41
55
|
# Create a promise and then instantly fulfill it.
|
42
56
|
def self.fulfilled(return_val = nil, parents: [], &block)
|
@@ -52,19 +66,19 @@ class Scarpe
|
|
52
66
|
p
|
53
67
|
end
|
54
68
|
|
55
|
-
#
|
56
|
-
|
69
|
+
# Fulfill the promise, setting the returned_value to value
|
57
70
|
def fulfilled!(value = nil)
|
58
71
|
set_state(:fulfilled, value)
|
59
72
|
end
|
60
73
|
|
74
|
+
# Reject the promise, setting the reason to reason
|
61
75
|
def rejected!(reason = nil)
|
62
76
|
set_state(:rejected, reason)
|
63
77
|
end
|
64
78
|
|
79
|
+
# Create a new promise with this promise as a parent. It runs the
|
80
|
+
# specified code in block when scheduled.
|
65
81
|
def then(&block)
|
66
|
-
# Create a new promise. It's waiting on us. It runs the
|
67
|
-
# specified code when scheduled.
|
68
82
|
Promise.new(parents: [self], &block)
|
69
83
|
end
|
70
84
|
|
@@ -73,7 +87,15 @@ class Scarpe
|
|
73
87
|
# the prettiest. However, they ensure that guarantees are made
|
74
88
|
# and so on, so they're great as plumbing under the syntactic
|
75
89
|
# sugar above.
|
76
|
-
|
90
|
+
#
|
91
|
+
# Note that the state passed in may not be the actual initial
|
92
|
+
# state. If a parent is rejected, the state will become
|
93
|
+
# rejected. If no parents are waiting or failed then a state
|
94
|
+
# of nil or :unscheduled will become :pending.
|
95
|
+
#
|
96
|
+
# @param state [Symbol] One of PROMISE_STATES for the initial state
|
97
|
+
# @param parents [Array] A list of promises that must be fulfilled before this one is scheduled
|
98
|
+
# @yield A block that executes when this promise is scheduled - when its parents, if any, are all fulfilled
|
77
99
|
def initialize(state: nil, parents: [], &scheduler)
|
78
100
|
log_init("Promise")
|
79
101
|
|
@@ -131,15 +153,43 @@ class Scarpe
|
|
131
153
|
end
|
132
154
|
end
|
133
155
|
|
156
|
+
# Return true if the Promise is either fulfilled or rejected.
|
157
|
+
#
|
158
|
+
# @return [Boolean] true if the promise is fulfilled or rejected
|
134
159
|
def complete?
|
135
160
|
@state == :fulfilled || @state == :rejected
|
136
161
|
end
|
137
162
|
|
163
|
+
# Return true if the promise is already fulfilled.
|
164
|
+
#
|
165
|
+
# @return [Boolean] true if the promise is fulfilled
|
166
|
+
def fulfilled?
|
167
|
+
@state == :fulfilled
|
168
|
+
end
|
169
|
+
|
170
|
+
# Return true if the promise is already rejected.
|
171
|
+
#
|
172
|
+
# @return [Boolean] true if the promise is rejected
|
173
|
+
def rejected?
|
174
|
+
@state == :rejected
|
175
|
+
end
|
176
|
+
|
177
|
+
# An inspect method to give slightly smaller output, for ease of reading in irb
|
178
|
+
def inspect
|
179
|
+
"#<Scarpe::Promise:#{object_id} " +
|
180
|
+
"@state=#{@state.inspect} @parents=#{@parents.inspect} " +
|
181
|
+
"@waiting_on=#{@waiting_on.inspect} @on_fulfilled=#{@on_fulfilled.size} " +
|
182
|
+
"@on_rejected=#{@on_rejected.size} @on_scheduled=#{@on_scheduled.size} " +
|
183
|
+
"@scheduler=#{@scheduler ? "Y" : "N"} @executor=#{@executor ? "Y" : "N"} " +
|
184
|
+
"@returned_value=#{@returned_value.inspect} @reason=#{@reason.inspect}" +
|
185
|
+
">"
|
186
|
+
end
|
187
|
+
|
138
188
|
# These promises are mostly designed for external execution.
|
139
189
|
# You could put together your own thread-pool, or use RPC,
|
140
190
|
# a WebView, a database or similar source of external calculation.
|
141
|
-
# But in
|
142
|
-
# In those cases, you can register an executor
|
191
|
+
# But in many cases it's reasonable to execute locally.
|
192
|
+
# In those cases, you can register an executor which will be
|
143
193
|
# called when the promise is ready to execute but has not yet
|
144
194
|
# done so. Registering an executor on a promise that is
|
145
195
|
# already fulfilled is an error. Registering an executor on
|
@@ -332,6 +382,11 @@ class Scarpe
|
|
332
382
|
|
333
383
|
public
|
334
384
|
|
385
|
+
# Register a handler to be called when the promise is fulfilled.
|
386
|
+
# If called on a fulfilled promise, the handler will be called immediately.
|
387
|
+
#
|
388
|
+
# @yield Handler to be called on fulfilled
|
389
|
+
# @return [Scarpe::Promise] self
|
335
390
|
def on_fulfilled(&handler)
|
336
391
|
unless handler
|
337
392
|
raise "You must pass a block to on_fulfilled!"
|
@@ -349,6 +404,11 @@ class Scarpe
|
|
349
404
|
self
|
350
405
|
end
|
351
406
|
|
407
|
+
# Register a handler to be called when the promise is rejected.
|
408
|
+
# If called on a rejected promise, the handler will be called immediately.
|
409
|
+
#
|
410
|
+
# @yield Handler to be called on rejected
|
411
|
+
# @return [Scarpe::Promise] self
|
352
412
|
def on_rejected(&handler)
|
353
413
|
unless handler
|
354
414
|
raise "You must pass a block to on_rejected!"
|
@@ -366,6 +426,12 @@ class Scarpe
|
|
366
426
|
self
|
367
427
|
end
|
368
428
|
|
429
|
+
# Register a handler to be called when the promise is scheduled.
|
430
|
+
# If called on a promise that was scheduled earlier, the handler
|
431
|
+
# will be called immediately.
|
432
|
+
#
|
433
|
+
# @yield Handler to be called on scheduled
|
434
|
+
# @return [Scarpe::Promise] self
|
369
435
|
def on_scheduled(&handler)
|
370
436
|
unless handler
|
371
437
|
raise "You must pass a block to on_scheduled!"
|
@@ -384,4 +450,5 @@ class Scarpe
|
|
384
450
|
self
|
385
451
|
end
|
386
452
|
end
|
453
|
+
Components::Promise = Promise
|
387
454
|
end
|