galaaz 2.1.7 → 2.1.9
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 +4 -4
- data/CHANGELOG.md +39 -0
- data/Rakefile +20 -2
- data/bin/check_gemfile_lock_version +46 -0
- data/bin/release_bump +26 -0
- data/blogs/README.md +4 -0
- data/blogs/galaaz_2_0/galaaz_2_0.Rmd +385 -0
- data/blogs/galaaz_2_0/galaaz_2_0.md +409 -0
- data/blogs/galaaz_2_0/galaaz_2_0.tex +756 -0
- data/blogs/galaaz_2_0/images/galaaz-header.png +0 -0
- data/blogs/galaaz_2_0/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +76 -58
- data/blogs/galaaz_ggplot/galaaz_ggplot.md +188 -161
- data/blogs/galaaz_ggplot/galaaz_ggplot.tex +185 -117
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
- data/blogs/galaaz_ggplot/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/gknit/gknit.Rmd +48 -28
- data/blogs/gknit/gknit.md +60 -43
- data/blogs/gknit/gknit.tex +1409 -0
- data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
- data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
- data/blogs/gknit/gknit_files/figure-latex/bubble-1.png +0 -0
- data/blogs/gknit/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/manual/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/manual/manual.Rmd +161 -71
- data/blogs/manual/manual.md +573 -482
- data/blogs/manual/manual.tex +1113 -517
- data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
- data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
- data/blogs/manual/manual_files/figure-markdown_github/bubble-1.png +0 -0
- data/blogs/manual/manual_files/figure-markdown_github/diverging_bar.png +0 -0
- data/blogs/nse_dplyr/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/nse_dplyr/nse_dplyr.Rmd +42 -8
- data/blogs/nse_dplyr/nse_dplyr.md +69 -50
- data/blogs/nse_dplyr/nse_dplyr.tex +1626 -0
- data/blogs/oh_my/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/oh_my/oh_my.Rmd +206 -55
- data/blogs/oh_my/oh_my.md +247 -96
- data/blogs/oh_my/oh_my.tex +2038 -93
- data/blogs/r_on_rails_ledger/images/00_portfolio_page.png +0 -0
- data/blogs/r_on_rails_ledger/images/01_results_panel.png +0 -0
- data/blogs/r_on_rails_ledger/images/02_density_tail_risk.png +0 -0
- data/blogs/r_on_rails_ledger/images/03_mc_cone.png +0 -0
- data/blogs/r_on_rails_ledger/images/04_rolling_var.png +0 -0
- data/blogs/r_on_rails_ledger/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/r_on_rails_ledger/r_on_rails_ledger.Rmd +354 -0
- data/blogs/r_on_rails_ledger/r_on_rails_ledger.md +365 -0
- data/blogs/r_on_rails_ledger/r_on_rails_ledger.tex +670 -0
- data/blogs/ruby_plot/images/galaaz-lockup-stacked.png +0 -0
- data/blogs/ruby_plot/ruby_plot.Rmd +56 -35
- data/blogs/ruby_plot/ruby_plot.md +63 -47
- data/blogs/ruby_plot/ruby_plot.tex +194 -125
- data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/dose_len.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
- data/lib/galaaz/cli.rb +98 -13
- data/logos/icon-font/README.md +27 -0
- data/logos/icon-font/build_font.py +130 -0
- data/logos/icon-font/galaaz-mark.svg +34 -0
- data/script/omarchy/README.md +9 -2
- data/script/omarchy/fonts/galaaz.ttf +0 -0
- data/script/omarchy/galaaz-guide.sh +1 -1
- data/script/omarchy/install-galaaz.sh +8 -1
- data/script/omarchy/omarchy-menu.jsonc +21 -9
- data/sty/galaaz-header.png +0 -0
- data/sty/galaaz-headers-from-p3.tex +4 -0
- data/sty/galaaz.sty +76 -23
- data/version.rb +1 -1
- metadata +46 -7
- data/blogs/galaaz_ggplot/galaaz_ggplot.log +0 -754
- data/blogs/manual/manual.log +0 -1786
- data/blogs/nse_dplyr/nse_dplyr.log +0 -928
- data/blogs/oh_my/oh_my.log +0 -804
- data/blogs/ruby_plot/ruby_plot.log +0 -885
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e591ab4464b4ef2d75313c91b9f22de54229f2e83ed9758d66de69758e6828e
|
|
4
|
+
data.tar.gz: a8978965526f22ba7abfc1f643c20516b2f962297e2254d0fc5105a96733c7f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3dfe41a6abb29b76ff48d9282dbce82d450975fb96b6f84eb3d564b9fda75f4220e5a773f64f59a7b416749bdcb58ab58cd63b8928ce878160d6983134ae6c80
|
|
7
|
+
data.tar.gz: 3bfcbf82a7dacbfa2357532d6fb0db4c651acd8b98d8b231da1d07cb36c7e47d73c2947773752afe80a78dc0b1b8f5c660d310887cddffba98235de984a85491
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 2.1.9
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `rake release:bump VERSION=x.y.z` (or `bin/release_bump x.y.z`) — rewrite
|
|
10
|
+
`version.rb` and refresh `Gemfile.lock` so CI frozen `bundle install` stays in
|
|
11
|
+
sync; `rake release:check` / CI job `Gemfile.lock matches version.rb` fail early
|
|
12
|
+
with that message instead of Bundler exit 16. `make_gem` / `publish_gem` depend
|
|
13
|
+
on `release:check`.
|
|
14
|
+
- Blogs: `galaaz_2_0` and `r_on_rails_ledger` (sources + rendered outputs); listed
|
|
15
|
+
in `galaaz blogs init` / `BLOG_NAMES`.
|
|
16
|
+
- Brand kit under `logos/` (incl. transparent masters). PDF headers via
|
|
17
|
+
`sty/galaaz.sty`: small logo left, upright page number + italic uppercase
|
|
18
|
+
section title right; optional `\galaazheaderfrompage` (shared
|
|
19
|
+
`sty/galaaz-headers-from-p3.tex` for title/TOC + splash). Main blogs share
|
|
20
|
+
HTML `before_body` lockup + PDF splash; `galaaz blogs init` / `add tex`
|
|
21
|
+
install sty header PNG and the from-p3 override.
|
|
22
|
+
- Omarchy menu brand mark: monochrome icon font `galaaz` (`U+E900`, source
|
|
23
|
+
`logos/icon-font/`) installed to `~/.local/share/fonts/galaaz/` by
|
|
24
|
+
`galaaz omarchy` / core installer — replaces the Ruby-on-Rails Nerd Font gem.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `galaaz add ledger`: run `rails tailwindcss:build` after seed so Propshaft finds
|
|
29
|
+
`tailwind.css` (builds dir is gitignored).
|
|
30
|
+
- Sync `Gemfile.lock` path gem with `version.rb` on every release bump (CI frozen
|
|
31
|
+
install).
|
|
32
|
+
|
|
33
|
+
## 2.1.8
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- `galaaz add ledger`: always `bundle install` under `mise x` (honours app `.ruby-version`),
|
|
38
|
+
ensure the lockfile's bundler is installed, then `bundle update galaaz` and
|
|
39
|
+
`bundle exec` for setup/rails — avoids Omarchy PATH Ruby 4.x mixing with ledger
|
|
40
|
+
Ruby 3.3 and "gems not found" / wrong bundler errors.
|
|
41
|
+
|
|
3
42
|
## 2.1.7
|
|
4
43
|
|
|
5
44
|
### Added
|
data/Rakefile
CHANGED
|
@@ -220,12 +220,30 @@ blogs.each do |dir|
|
|
|
220
220
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
+
#===========================================================================================
|
|
224
|
+
# Release helpers: keep version.rb and Gemfile.lock in sync (CI frozen install)
|
|
225
|
+
#===========================================================================================
|
|
226
|
+
|
|
227
|
+
namespace :release do
|
|
228
|
+
desc 'Fail if version.rb does not match the PATH galaaz version in Gemfile.lock'
|
|
229
|
+
task :check do
|
|
230
|
+
ruby 'bin/check_gemfile_lock_version'
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
desc 'Set version.rb to VERSION=x.y.z and refresh Gemfile.lock (bundle lock)'
|
|
234
|
+
task :bump do
|
|
235
|
+
new_ver = ENV['VERSION'].to_s.strip
|
|
236
|
+
abort 'Usage: rake release:bump VERSION=x.y.z' if new_ver.empty?
|
|
237
|
+
ruby 'bin/release_bump', new_ver
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
223
241
|
#===========================================================================================
|
|
224
242
|
# Makes a gem for publishing in RubyGems
|
|
225
243
|
#===========================================================================================
|
|
226
244
|
|
|
227
245
|
desc 'Makes a Gem'
|
|
228
|
-
task :make_gem do
|
|
246
|
+
task :make_gem => 'release:check' do
|
|
229
247
|
(sh %{ gem build #{$gem_name}.gemspec })
|
|
230
248
|
end
|
|
231
249
|
|
|
@@ -234,7 +252,7 @@ end
|
|
|
234
252
|
#===========================================================================================
|
|
235
253
|
|
|
236
254
|
desc 'Publish gem to rubygems'
|
|
237
|
-
task :publish_gem do
|
|
255
|
+
task :publish_gem => 'release:check' do
|
|
238
256
|
(sh %{ gem push #{$gem_name}-#{$version}.gem })
|
|
239
257
|
end
|
|
240
258
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Fail if version.rb and the PATH galaaz entry in Gemfile.lock disagree.
|
|
5
|
+
# CI runs this before frozen bundle install so the failure is obvious
|
|
6
|
+
# (instead of Bundler exit 16 from ruby/setup-ruby).
|
|
7
|
+
|
|
8
|
+
root = File.expand_path('..', __dir__)
|
|
9
|
+
version_rb = File.join(root, 'version.rb')
|
|
10
|
+
lock_path = File.join(root, 'Gemfile.lock')
|
|
11
|
+
|
|
12
|
+
abort "missing #{version_rb}" unless File.file?(version_rb)
|
|
13
|
+
abort "missing #{lock_path}" unless File.file?(lock_path)
|
|
14
|
+
|
|
15
|
+
version_src = File.read(version_rb)
|
|
16
|
+
declared = version_src[/\$version\s*=\s*["']([^"']+)["']/, 1]
|
|
17
|
+
abort "could not parse $version from version.rb" if declared.nil? || declared.empty?
|
|
18
|
+
|
|
19
|
+
lock = File.read(lock_path)
|
|
20
|
+
# Prefer the PATH gemspec block (first galaaz version under PATH … specs:).
|
|
21
|
+
path_block = lock[/\APATH\n.*?^GEM\n/m]
|
|
22
|
+
locked =
|
|
23
|
+
if path_block
|
|
24
|
+
path_block[/^\s+galaaz \(([^)]+)\)/, 1]
|
|
25
|
+
end
|
|
26
|
+
locked ||= lock[/^\s+galaaz \(([^)]+)\)/, 1]
|
|
27
|
+
abort 'could not parse galaaz version from Gemfile.lock PATH specs' if locked.nil? || locked.empty?
|
|
28
|
+
|
|
29
|
+
if declared == locked
|
|
30
|
+
puts "OK: version.rb (#{declared}) matches Gemfile.lock path gem (#{locked})"
|
|
31
|
+
exit 0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
warn <<~MSG
|
|
35
|
+
version.rb and Gemfile.lock disagree.
|
|
36
|
+
|
|
37
|
+
version.rb $version = #{declared}
|
|
38
|
+
Gemfile.lock PATH galaaz (#{locked})
|
|
39
|
+
|
|
40
|
+
After bumping the gem version, refresh the lockfile in the same commit:
|
|
41
|
+
|
|
42
|
+
rake release:bump VERSION=#{declared}
|
|
43
|
+
|
|
44
|
+
Or: bundle lock && git add Gemfile.lock
|
|
45
|
+
MSG
|
|
46
|
+
exit 1
|
data/bin/release_bump
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Bump version.rb and refresh Gemfile.lock so CI frozen bundle install stays valid.
|
|
5
|
+
# Usage: bin/release_bump x.y.z
|
|
6
|
+
|
|
7
|
+
root = File.expand_path('..', __dir__)
|
|
8
|
+
new_ver = ARGV[0].to_s.strip
|
|
9
|
+
abort 'Usage: bin/release_bump x.y.z' unless new_ver.match?(/\A\d+\.\d+\.\d+(?:[.-][\w.]+)?\z/)
|
|
10
|
+
|
|
11
|
+
path = File.join(root, 'version.rb')
|
|
12
|
+
src = File.read(path)
|
|
13
|
+
unless src.sub!(/\$version\s*=\s*["'][^"']+["']/, "$version=\"#{new_ver}\"")
|
|
14
|
+
abort "could not rewrite $version in #{path}"
|
|
15
|
+
end
|
|
16
|
+
File.write(path, src)
|
|
17
|
+
puts "Wrote version.rb $version=#{new_ver}"
|
|
18
|
+
|
|
19
|
+
Dir.chdir(root) do
|
|
20
|
+
ok = system('bundle', 'lock')
|
|
21
|
+
abort 'bundle lock failed' unless ok
|
|
22
|
+
ok = system(RbConfig.ruby, File.join(root, 'bin/check_gemfile_lock_version'))
|
|
23
|
+
abort 'version check failed after bump' unless ok
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
puts 'Next: update CHANGELOG.md, commit version.rb + Gemfile.lock, then rake make_gem && rake publish_gem'
|
data/blogs/README.md
CHANGED
|
@@ -21,6 +21,8 @@ Examples:
|
|
|
21
21
|
bin/gknit blogs/oh_my/oh_my.Rmd
|
|
22
22
|
bin/gknit blogs/gknit/gknit.Rmd
|
|
23
23
|
bin/gknit blogs/galaaz_ggplot/galaaz_ggplot.Rmd
|
|
24
|
+
bin/gknit blogs/galaaz_2_0/galaaz_2_0.Rmd
|
|
25
|
+
bin/gknit blogs/r_on_rails_ledger/r_on_rails_ledger.Rmd
|
|
24
26
|
bin/gknit blogs/manual/manual.Rmd
|
|
25
27
|
bin/gknit blogs/nse_dplyr/nse_dplyr.Rmd
|
|
26
28
|
bin/gknit blogs/ruby_plot/ruby_plot.Rmd
|
|
@@ -36,6 +38,8 @@ From the project root:
|
|
|
36
38
|
rake blog:oh_my
|
|
37
39
|
rake blog:gknit
|
|
38
40
|
rake blog:galaaz_ggplot
|
|
41
|
+
rake blog:galaaz_2_0
|
|
42
|
+
rake blog:r_on_rails_ledger
|
|
39
43
|
rake blog:manual
|
|
40
44
|
rake blog:nse_dplyr
|
|
41
45
|
rake blog:ruby_plot
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Galaaz 2.0: Ruby meets real GNU R (again)"
|
|
3
|
+
subtitle: "NewBridge, JRuby or CRuby, CRAN/Bioconductor, and Arrow-shaped data paths"
|
|
4
|
+
author: "Rodrigo Botafogo"
|
|
5
|
+
tags: [Galaaz, "Galaaz 2.0", Ruby, R, JRuby, CRuby, "GNU R", NewBridge, Arrow, knitr, gknit]
|
|
6
|
+
date: "2026"
|
|
7
|
+
output:
|
|
8
|
+
html_document:
|
|
9
|
+
self_contained: true
|
|
10
|
+
keep_md: true
|
|
11
|
+
toc: true
|
|
12
|
+
toc_float: true
|
|
13
|
+
toc_depth: 2
|
|
14
|
+
number_sections: true
|
|
15
|
+
includes:
|
|
16
|
+
before_body: _logo_before_body.html
|
|
17
|
+
pdf_document:
|
|
18
|
+
includes:
|
|
19
|
+
in_header:
|
|
20
|
+
- "../../sty/galaaz.sty"
|
|
21
|
+
- "../../sty/galaaz-headers-from-p3.tex"
|
|
22
|
+
keep_tex: yes
|
|
23
|
+
number_sections: yes
|
|
24
|
+
toc: true
|
|
25
|
+
toc_depth: 2
|
|
26
|
+
fontsize: 11pt
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
```{r setup, echo=FALSE}
|
|
30
|
+
options(width = 70)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```{ruby setup_ruby, echo=FALSE}
|
|
34
|
+
ENV['GALAAZ_BRIDGE_TIMEOUT_SEC'] ||= '300'
|
|
35
|
+
ENV['GALAAZ_JOBS_DIR'] ||=
|
|
36
|
+
File.join(Dir.tmpdir, 'galaaz_jobs_galaaz_2_0')
|
|
37
|
+
R.options(crayon__enabled: false, width: 70)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```{r brand_logo, echo=FALSE, fig.align='center', out.width='45%', eval=knitr::is_latex_output()}
|
|
41
|
+
# PDF only — HTML uses _logo_before_body.html (above the TOC).
|
|
42
|
+
knitr::include_graphics("images/galaaz-lockup-stacked.png")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
# Introduction
|
|
46
|
+
|
|
47
|
+
Ruby is excellent for web apps, orchestration, and expressive object models.
|
|
48
|
+
It has never matched **R** for statistics, Bioconductor pipelines, or the
|
|
49
|
+
depth of CRAN. **Python** closed a similar gap with NumPy, pandas, and a
|
|
50
|
+
huge scientific ecosystem. For Ruby, the practical answer is not to
|
|
51
|
+
reimplement that world — it is to **drive real R** from idiomatic Ruby.
|
|
52
|
+
|
|
53
|
+
**Galaaz** is that coupling. An earlier line of work ran on Oracle’s
|
|
54
|
+
**GraalVM** with **TruffleRuby** and **FastR** in one JVM. **Galaaz 2.0**
|
|
55
|
+
is a different architecture: **JRuby or CRuby** talk to **standard GNU R**
|
|
56
|
+
over a process bridge we call **NewBridge**. You keep CRAN and Bioconductor
|
|
57
|
+
(including compiled packages), and you keep a normal Ruby toolchain.
|
|
58
|
+
|
|
59
|
+
This post is the 2.0 story: what changed, how the bridge works, how to pin
|
|
60
|
+
R versions in containers, how data moves (including **Apache Arrow**), and
|
|
61
|
+
how to keep the bridge free for long R jobs. Deep API detail lives in the
|
|
62
|
+
[Galaaz Manual](https://rbotafogo.github.io/galaaz/); here we stay at
|
|
63
|
+
blog length with a few runnable sketches.
|
|
64
|
+
|
|
65
|
+
# What changed
|
|
66
|
+
|
|
67
|
+
| | Historical stack | Galaaz 2.0 |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Ruby | TruffleRuby (Graal) | **JRuby** or **CRuby** |
|
|
70
|
+
| R | FastR (subset of GNU R) | **GNU R** (CRAN / Bioconductor) |
|
|
71
|
+
| Coupling | Same-JVM interop | **NewBridge** (separate processes) |
|
|
72
|
+
| Install | Special Graal distribution | `gem install` + compile gatekeeper |
|
|
73
|
+
| Legacy tools | `grun`, polyglot `gknit-draft` | `bin/galaaz-ruby`, `bin/gknit` |
|
|
74
|
+
|
|
75
|
+
Ideas from the older articles still matter — ggplot layers, dplyr pipes,
|
|
76
|
+
gKnit literate docs — but the **engine** underneath is different. If you
|
|
77
|
+
land on a 2018 “Ruby + R on GraalVM” post, treat the plots and narrative
|
|
78
|
+
as inspiration and this post (plus the manual) as the current stack.
|
|
79
|
+
|
|
80
|
+
# NewBridge in one picture
|
|
81
|
+
|
|
82
|
+
Galaaz 2.0 does **not** embed Renjin or FastR. Roughly:
|
|
83
|
+
|
|
84
|
+
1. Your **Ruby** process (JRuby or CRuby) loads Galaaz.
|
|
85
|
+
2. A native **gatekeeper** (`ext/new_bridge`, Rcpp / C++) mediates the protocol.
|
|
86
|
+
3. A **GNU R** process evaluates calls, holds large objects, and runs packages.
|
|
87
|
+
|
|
88
|
+
Ruby stays Ruby: `R.mean`, `E.aes`, method chaining, classes and modules.
|
|
89
|
+
R stays R: the same `lm`, `DESeq2`, or `ggplot2` you would run in RStudio.
|
|
90
|
+
Large panels should usually **stay in R**; Ruby orchestrates and pulls
|
|
91
|
+
small results (Remote Control). Opt in to unbox scalars when you need a
|
|
92
|
+
plain Ruby number (`>> 0` for length-1 vectors is the common 2.0 idiom).
|
|
93
|
+
|
|
94
|
+
A minimal session looks like this:
|
|
95
|
+
|
|
96
|
+
```{ruby hello_bridge}
|
|
97
|
+
require 'galaaz'
|
|
98
|
+
|
|
99
|
+
puts R.sum(R.c(1, 2, 3, 4, 5))
|
|
100
|
+
mtcars = ~R[:mtcars]
|
|
101
|
+
puts mtcars.dim
|
|
102
|
+
puts R.summary(mtcars.mpg)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Callbacks still exist: Ruby procs can be registered so R code can call
|
|
106
|
+
back into Ruby when a package expects an R function. That path is
|
|
107
|
+
documented in the manual; the important product point is that **real
|
|
108
|
+
GNU R** is on the other end of the wire.
|
|
109
|
+
|
|
110
|
+
# Either Ruby engine
|
|
111
|
+
|
|
112
|
+
NewBridge is tested on both:
|
|
113
|
+
|
|
114
|
+
* **JRuby** (e.g. 10.x + JDK 21) — real OS threads, JVM ecosystem, natural
|
|
115
|
+
fit for Rails under load.
|
|
116
|
+
* **CRuby** (e.g. 3.3+) — MRI gem ecosystem, familiar for many Ruby shops.
|
|
117
|
+
|
|
118
|
+
The public API is the same. Launchers honor **`GALAAZ_RUBY`**.
|
|
119
|
+
Use **`bin/galaaz-ruby`**, or **`bin/galaaz-jruby`** to force JRuby.
|
|
120
|
+
Prefer **JRuby** when you want parallel Ruby threads feeding R;
|
|
121
|
+
prefer **CRuby** when your app and gems already live there. Arrow
|
|
122
|
+
helpers need engine-specific native bits (Java Arrow + `JAVA_OPTS`
|
|
123
|
+
nio opens on JRuby; Arrow GLib + `red-arrow` on CRuby) — see the
|
|
124
|
+
manual’s Arrow section.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# From a checkout or installed layout:
|
|
128
|
+
bin/galaaz-ruby -e \
|
|
129
|
+
'require "galaaz"; puts R.R__version[["version.string"]]'
|
|
130
|
+
GALAAZ_RUBY=jruby bin/galaaz-ruby -e \
|
|
131
|
+
'require "galaaz"; puts R.pi'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
# Multiple R versions via containers
|
|
135
|
+
|
|
136
|
+
Because R is a **separate process**, the Ruby gem does not hard-wire one
|
|
137
|
+
R build. Whatever `R` / `Rscript` is on `PATH` (and can load the
|
|
138
|
+
gatekeeper) is the R you drive. That makes **containers** a clean way to
|
|
139
|
+
pin versions:
|
|
140
|
+
|
|
141
|
+
* Image A: Ubuntu + **R 4.3** + system libs for your Bioconductor set.
|
|
142
|
+
* Image B: same app code + **R 4.4** (or a rocker image) for comparison.
|
|
143
|
+
* Your Galaaz gem and Ruby code stay the same; only the image’s R changes.
|
|
144
|
+
|
|
145
|
+
Sketch (conceptual):
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Dev against the R that the image provides
|
|
149
|
+
docker run --rm -it myorg/galaaz-r43 \
|
|
150
|
+
bin/galaaz-ruby script/analysis.rb
|
|
151
|
+
|
|
152
|
+
docker run --rm -it myorg/galaaz-r44 \
|
|
153
|
+
bin/galaaz-ruby script/analysis.rb
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Inside each image, `R --version` differs; NewBridge still speaks the same
|
|
157
|
+
protocol. For a first taste without building your own Dockerfile, the
|
|
158
|
+
repo ships **try** images and runners:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# From a Galaaz checkout
|
|
162
|
+
./docker/try-gstudio/run.sh # JRuby + gstudio-oriented image
|
|
163
|
+
./docker/try-cruby/run.sh # CRuby try image
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Cold-install** proofs (`docker/cold-install`,
|
|
167
|
+
`docker/cold-install-cruby`) install the gem on a throwaway Ubuntu and
|
|
168
|
+
compile the gatekeeper — useful when you want “empty machine → working
|
|
169
|
+
bridge” confidence. Multi-runtime Docker R matrices are still evolving
|
|
170
|
+
(slow integration coverage); the sketch above is the pattern to build on.
|
|
171
|
+
|
|
172
|
+
# Moving data: Apache Arrow
|
|
173
|
+
|
|
174
|
+
Crossing a process boundary means you must be honest about **copies**.
|
|
175
|
+
Galaaz’s Arrow roadmap has three stages:
|
|
176
|
+
|
|
177
|
+
| Stage | Idea | Status |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| **A** | Copy columnar data into R; return an R-side Arrow/proxy handle | Available (`from_ruby_batches`, `table_from`, …) |
|
|
180
|
+
| **B** | Same-machine **IPC file**: bulk bytes stay off the MsgPack bridge; only a **file path** crosses NewBridge | Shipped (two directions: B1 and B2) |
|
|
181
|
+
| **C** | Shared-memory bus (true shared RAM) | Future |
|
|
182
|
+
|
|
183
|
+
Do **not** read Stage A as “zero-copy shared RAM.” Stage A is “build the
|
|
184
|
+
table in R efficiently, then Remote-Control it.” Stage B is still a
|
|
185
|
+
file/`mmap` handoff on one machine—not Stage C—but it keeps large
|
|
186
|
+
columns out of the control plane.
|
|
187
|
+
|
|
188
|
+
## Stage B in plain words
|
|
189
|
+
|
|
190
|
+
**IPC** here means Apache Arrow’s **Inter-Process Communication** file
|
|
191
|
+
format: a portable columnar dump on disk (often under `/dev/shm` so it
|
|
192
|
+
lives in RAM-backed tmpfs). Ruby and R never shove megabytes through
|
|
193
|
+
NewBridge; they agree on a path, then each side’s Arrow stack
|
|
194
|
+
reads or writes that file.
|
|
195
|
+
|
|
196
|
+
Stage B splits by **direction**:
|
|
197
|
+
|
|
198
|
+
* **B1 — Ruby → R (ingest).** Ruby writes an IPC file
|
|
199
|
+
(`Galaaz::ArrowIpc.write` / `write_batches`). R opens it with
|
|
200
|
+
`R::Arrow.open_ipc(path)` and gets a table proxy for dplyr or
|
|
201
|
+
modeling. After R has materialised the table, Ruby can
|
|
202
|
+
`Galaaz::ArrowIpc.release(path)` (unlink the scratch file).
|
|
203
|
+
* **B2 — R → Ruby (export).** After analytics in R, `R::Arrow.write_ipc`
|
|
204
|
+
writes another IPC file and returns its path over the bridge. Ruby
|
|
205
|
+
reads columns or row hashes with `Galaaz::ArrowIpc.read` /
|
|
206
|
+
`read_batches`—handy when the next step is a DB write or an API
|
|
207
|
+
payload in Ruby.
|
|
208
|
+
|
|
209
|
+
Needs: R package `arrow`, plus a Ruby writer/reader (CRuby:
|
|
210
|
+
**red-arrow**; JRuby: Arrow Java JARs). Specs skip when those are
|
|
211
|
+
missing; the sketch below does the same.
|
|
212
|
+
|
|
213
|
+
## Stage B sketch (B1 ingest + B2 export)
|
|
214
|
+
|
|
215
|
+
This is the path to prefer for large same-machine handoffs. Ruby
|
|
216
|
+
threads build row batches; **B1** writes one IPC file and R opens it by
|
|
217
|
+
path; dplyr summarises in R; **B2** writes the result IPC and Ruby
|
|
218
|
+
reads row hashes. NewBridge only carries paths.
|
|
219
|
+
|
|
220
|
+
```{ruby arrow_ipc_b12, message=FALSE, warning=FALSE}
|
|
221
|
+
ipc_ok = Galaaz::ArrowIpc.available? &&
|
|
222
|
+
(R::Support.eval(
|
|
223
|
+
"requireNamespace('arrow', quietly=TRUE) && " +
|
|
224
|
+
"requireNamespace('dplyr', quietly=TRUE)") == true)
|
|
225
|
+
unless ipc_ok
|
|
226
|
+
puts '(Skip: need Arrow IPC backend + R arrow/dplyr.)'
|
|
227
|
+
else
|
|
228
|
+
thread_count = 2
|
|
229
|
+
rows_per_thread = 250
|
|
230
|
+
group_count = 5
|
|
231
|
+
batches = []
|
|
232
|
+
mutex = Mutex.new
|
|
233
|
+
threads = []
|
|
234
|
+
|
|
235
|
+
thread_count.times do |tid|
|
|
236
|
+
threads << Thread.new do
|
|
237
|
+
start = tid * rows_per_thread
|
|
238
|
+
local = (start...(start + rows_per_thread)).map do |i|
|
|
239
|
+
{
|
|
240
|
+
id: i,
|
|
241
|
+
grp: "g#{i % group_count}",
|
|
242
|
+
value: (i % 17) + 1,
|
|
243
|
+
weight: ((i % 5) + 1) * 0.5
|
|
244
|
+
}
|
|
245
|
+
end
|
|
246
|
+
mutex.synchronize { batches << local }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
threads.each(&:join)
|
|
250
|
+
|
|
251
|
+
paths = []
|
|
252
|
+
begin
|
|
253
|
+
# B1: Ruby → IPC file → R table proxy
|
|
254
|
+
# write_batches wants flat row Hashes (not nested batches)
|
|
255
|
+
in_path = Galaaz::ArrowIpc.write_batches(batches.flatten)
|
|
256
|
+
paths << in_path
|
|
257
|
+
tbl = R::Arrow.open_ipc(in_path)
|
|
258
|
+
puts "R class: #{tbl.rclass}"
|
|
259
|
+
puts "IPC in: #{File.basename(in_path)}"
|
|
260
|
+
|
|
261
|
+
grouped = R.dplyr___group_by(tbl, :grp)
|
|
262
|
+
summed = R.dplyr___summarise(
|
|
263
|
+
grouped,
|
|
264
|
+
n: E.n(),
|
|
265
|
+
total: E.sum(:value),
|
|
266
|
+
wsum: E.sum(R[:value] * R[:weight])
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# B2: R → IPC file → Ruby row hashes
|
|
270
|
+
out_path = R::Arrow.write_ipc(summed)
|
|
271
|
+
paths << out_path
|
|
272
|
+
rows = Galaaz::ArrowIpc.read_batches(out_path)
|
|
273
|
+
puts "IPC out: #{File.basename(out_path)}"
|
|
274
|
+
rows.first(5).each do |r|
|
|
275
|
+
puts "#{r[:grp]} n=#{r[:n]} " +
|
|
276
|
+
"total=#{r[:total]} wsum=#{r[:wsum]}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
total_n = rows.map { |r| r[:n].to_i }.sum
|
|
280
|
+
expect_n = thread_count * rows_per_thread
|
|
281
|
+
puts "Sum of n (expect #{expect_n}): #{total_n}"
|
|
282
|
+
ensure
|
|
283
|
+
paths.each { |p| Galaaz::ArrowIpc.release(p) }
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
What to notice: `write_batches` / `open_ipc` is **B1**; after dplyr,
|
|
289
|
+
`write_ipc` / `read_batches` is **B2**. Specs:
|
|
290
|
+
`arrow_ipc_handoff_spec.rb`, `arrow_ipc_export_spec.rb`. Deeper notes:
|
|
291
|
+
`Documentation/ROADMAP_ARROW_RUBY_R.md`.
|
|
292
|
+
|
|
293
|
+
## Stage A (still useful)
|
|
294
|
+
|
|
295
|
+
Stage A copies batches straight into R with
|
|
296
|
+
`R::Arrow.from_ruby_batches`—no IPC file. Fine for modest tables or
|
|
297
|
+
when you do not have a Ruby Arrow writer installed. Same threaded
|
|
298
|
+
shape as above, one call instead of B1:
|
|
299
|
+
|
|
300
|
+
```{ruby arrow_pipeline, message=FALSE, warning=FALSE}
|
|
301
|
+
arrow_ok = R::Support.eval(
|
|
302
|
+
"requireNamespace('arrow', quietly=TRUE) && " +
|
|
303
|
+
"requireNamespace('dplyr', quietly=TRUE)")
|
|
304
|
+
unless arrow_ok == true
|
|
305
|
+
puts '(Skip: need arrow + dplyr in R.)'
|
|
306
|
+
else
|
|
307
|
+
batches = [
|
|
308
|
+
[{ id: 1, grp: 'a', value: 10, weight: 1.0 },
|
|
309
|
+
{ id: 2, grp: 'b', value: 20, weight: 0.5 }],
|
|
310
|
+
[{ id: 3, grp: 'a', value: 30, weight: 1.5 }]
|
|
311
|
+
]
|
|
312
|
+
tbl = R::Arrow.from_ruby_batches(batches)
|
|
313
|
+
puts "R class: #{tbl.rclass}"
|
|
314
|
+
grouped = R.dplyr___group_by(tbl, :grp)
|
|
315
|
+
summarised = R.dplyr___summarise(
|
|
316
|
+
grouped, n: E.n(), total: E.sum(:value))
|
|
317
|
+
out = R.dplyr___collect(summarised)
|
|
318
|
+
puts R.as__data__frame(out)
|
|
319
|
+
end
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Larger Stage A demos live in `arrow_from_ruby_batches_spec.rb` and
|
|
323
|
+
`arrow_large_pipeline_spec.rb` (under `specs/` / `slow-specs/`).
|
|
324
|
+
|
|
325
|
+
# Keeping the bridge free
|
|
326
|
+
|
|
327
|
+
Two different “don’t block” stories:
|
|
328
|
+
|
|
329
|
+
* **`R::Async` / `R.eval_r_async`** — free a **Ruby** thread while the
|
|
330
|
+
**same** bridge R process works. Good for short/medium calls when you
|
|
331
|
+
still want one R session.
|
|
332
|
+
* **`R::Job`** — run heavy work in a **child `Rscript`**. The bridge
|
|
333
|
+
stays free for other traffic. Package installs
|
|
334
|
+
(`R.install_and_loads`) use this path so `make`/`gcc` do not wedge
|
|
335
|
+
the gatekeeper.
|
|
336
|
+
|
|
337
|
+
```{ruby job_lm, message=FALSE, warning=FALSE}
|
|
338
|
+
begin
|
|
339
|
+
coef = R::Job.eval(<<~R) { |job| job.load_rds }
|
|
340
|
+
fit <- lm(mpg ~ wt, data = mtcars)
|
|
341
|
+
saveRDS(unname(coef(fit)), result_path)
|
|
342
|
+
R
|
|
343
|
+
puts coef
|
|
344
|
+
rescue => e
|
|
345
|
+
puts e.class.to_s
|
|
346
|
+
e.message.to_s.scan(/.{1,68}/).each { |line| puts line }
|
|
347
|
+
end
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Rule of thumb: short interactive analytics on the bridge; installs and
|
|
351
|
+
multi-minute fits in **`R::Job`**. Details and timeouts
|
|
352
|
+
(`GALAAZ_INSTALL_TIMEOUT_SEC`, `GALAAZ_JOBS_DIR`) are in the manual.
|
|
353
|
+
|
|
354
|
+
# What stayed the same
|
|
355
|
+
|
|
356
|
+
Galaaz 2.0 is a new **runtime**, not a new plotting language. These still
|
|
357
|
+
apply:
|
|
358
|
+
|
|
359
|
+
* **ggplot2** from Ruby — see *Ruby Plotting with Galaaz* /
|
|
360
|
+
*Beautiful Ruby Plots*.
|
|
361
|
+
* **dplyr / NSE** — see *Non Standard Evaluation in dplyr with Galaaz*.
|
|
362
|
+
* **Literate docs** — **gKnit** still wraps knitr so Ruby chunks render
|
|
363
|
+
next to R (HTML/PDF).
|
|
364
|
+
* **Classes, modules, procs** — the *oh my* S4 comparison still teaches
|
|
365
|
+
structure; unwrap and bridge details follow 2.0 rules.
|
|
366
|
+
|
|
367
|
+
R-on-Rails (Ruby web + R science) is the product framing: Rails (or any
|
|
368
|
+
Ruby app) orchestrates; GNU R computes.
|
|
369
|
+
|
|
370
|
+
# Conclusion
|
|
371
|
+
|
|
372
|
+
**Galaaz 2.0** bets on **boring, powerful defaults**: standard Ruby
|
|
373
|
+
engines, standard GNU R, a clear process bridge, containers to pin R,
|
|
374
|
+
and Arrow-shaped paths for larger tables — with zero-copy shared memory
|
|
375
|
+
still on the roadmap. The Graal-era prototype proved the *idea*; 2.0
|
|
376
|
+
makes the *ecosystem* reachable.
|
|
377
|
+
|
|
378
|
+
## Further reading
|
|
379
|
+
|
|
380
|
+
* Manual (GitHub Pages): https://rbotafogo.github.io/galaaz/
|
|
381
|
+
* Arrow roadmap: `Documentation/ROADMAP_ARROW_RUBY_R.md`
|
|
382
|
+
* Repo blogs under `blogs/` (ggplot, dplyr, gKnit, oh my, manual)
|
|
383
|
+
* Try images: `docker/try-gstudio`, `docker/try-cruby`
|
|
384
|
+
* Older Graal-era plot narrative (historical): search
|
|
385
|
+
“Ruby Plotting with Galaaz in GraalVM” on Towards Data Science
|