Skip to content

Instantly share code, notes, and snippets.

@briankung

briankung/000.sh Secret

Last active February 3, 2022 18:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save briankung/11e2a18b099c4d8b60ac8b519f7cef52 to your computer and use it in GitHub Desktop.
Save briankung/11e2a18b099c4d8b60ac8b519f7cef52 to your computer and use it in GitHub Desktop.
writing-a-rust-gem-from-scratch/
$ bundle gem ruffle
# ...omitted
Gem 'ruffle' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html
$ cd ruffle
$ bundle install
You have one or more invalid gemspecs that need to be fixed.
The gemspec at /Users/brian/Code/github/briankung/ruffle/ruffle.gemspec is not valid. Please fix this
gemspec.
The validation error was 'metadata['homepage_uri'] has invalid link: "TODO: Put your gem's website or
public repo URL here."'
# in ruffle.gemspec
Gem::Specification.new do |spec|
# ...omitted
spec.summary = "Write a short summary, because RubyGems requires one."
spec.description = "Write a longer description or delete this line."
spec.homepage = "https://example.com"
# ...omitted
spec.metadata["allowed_push_host"] = spec.homepage
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = spec.homepage
# ...omitted
end
$ bundle
Fetching gem metadata from https://rubygems.org/...
Resolving dependencies...
# ...omitted
Fetching rspec 3.10.0
Installing rspec 3.10.0
Bundle complete! 3 Gemfile dependencies, 9 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
$ rake # defaults to rake spec
Ruffle
has a version number
does something useful (FAILED - 1)
Failures:
1) Ruffle does something useful
Failure/Error: expect(false).to eq(true)
expected: true
got: false
(compared using ==)
Diff:
@@ -1 +1 @@
-true
+false
# ./spec/ruffle_spec.rb:9:in `block (2 levels) in <top (required)>'
# /Users/brian/Code/github/ianks/rubygems/bundler/spec/support/bundle.rb:8:in `load'
# /Users/brian/Code/github/ianks/rubygems/bundler/spec/support/bundle.rb:8:in `<main>'
Finished in 0.00947 seconds (files took 0.07426 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/ruffle_spec.rb:8 # Ruffle does something useful
# in spec/ruffle_spec.rb
RSpec.describe Ruffle do
it "has a #shuffle method" do
expect(Ruffle).to respond_to(:shuffle)
end
end
Failures:
1) Ruffle has a #shuffle method
Failure/Error: expect(Ruffle).to respond_to(:shuffle)
expected Ruffle to respond to :shuffle
$ cargo init --lib
Created library package
# in Rakefile
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
task default: :spec
# in Rakefile, under `task default: :spec`
desc "Compile the ruffle crate"
task :compile do
gemspec = File.expand_path('ruffle.gemspec')
output = File.expand_path('ruffle.gem')
`gem list -i "^ruffle$"`
gem_installed = Process.last_status.success?
system 'gem', 'uninstall', 'ruffle' if gem_installed
system 'gem', 'build', gemspec, '--output', output
system 'gem', 'install', output
end
# You can optionally have rake compile before every
# spec run by adding the :compile task as a
# prerequisite. This ended up being irritating while I
# got the Ruby parts working, so I've commented it out
# Rake::Task[:spec].prerequisites << :compile
$ rake compile
ERROR: While executing gem ... (NameError)
uninitialized constant Gem::Ext::Builder::FileUtils
Did you mean? FileTest
ERROR: Error installing /Users/brian/Code/github/briankung/ruffle/ruffle.gem:
ERROR: Failed to build gem native extension.
No builder for extension 'Cargo.toml
// ...omitted
# in Rakefile
desc "Compile the ruffle crate"
task :compile do
system 'gem', '--version'
end
$ rake compile
3.3.6
// ...omitted
$ export RUBYGEMS_PATH=path/to/your/cargo-builder/rubygems
# in Rakefile
desc "Compile the ruffle crate"
task :compile do
cargo_builder_gem = [
"ruby",
"-I#{ENV["RUBYGEMS_PATH"]}/lib",
"#{ENV["RUBYGEMS_PATH"]}/bin/gem"
]
gemspec = File.expand_path('ruffle.gemspec')
output = File.expand_path('ruffle.gem')
`gem list -i "^ruffle$"`
gem_installed = Process.last_status.success?
system *cargo_builder_gem, 'uninstall', 'ruffle' if gem_installed
system *cargo_builder_gem, 'build', gemspec, '--output', output
system *cargo_builder_gem, 'install', output
end
$ rake compile
true
Successfully uninstalled ruffle-0.1.0
Successfully built RubyGem
Name: ruffle
Version: 0.1.0
File: ruffle.gem
Successfully installed ruffle-0.1.0
1 gem installed
# in ruffle.gemspec
Gem::Specification.new do |spec|
# ...omitted
spec.required_ruby_version = ">= 2.6.0"
# 👇 Add this
spec.extensions = ["Cargo.toml"]
# ...
end
error: the lock file /Users/brian/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/ruffle-0.1.0/Cargo.lock needs to be updated but --locked was passed to prevent this
If you want to try to generate the lock file without accessing the network, remove the --locked flag and use --offline instead.
# in ruffle.gemspec
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject do |f|
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
end
end
# in ruffle.gemspec
spec.files = ["Cargo.toml", "Cargo.lock", "src/lib.rs"]
$ rake compile
# ...omitted
Compiling ruffle v0.1.0 (/Users/brian/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/ruffle-0.1.0)
Finished release [optimized] target(s) in 0.47s
Dynamic library not found for Rust extension (in /Users/brian/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/extensions/x86_64-darwin-21/3.0.0/ruffle-0.1.0)
Make sure you set "crate-type" in Cargo.toml to "cdylib"
# in Cargo.toml
[lib]
crate-type = ["cdylib"]
$ rake compile
Successfully built RubyGem
Name: ruffle
Version: 0.1.0
File: ruffle.gem
Building native extensions. This could take a while...
Successfully installed ruffle-0.1.0
1 gem installed
# in Cargo.toml
[dependencies]
rb-sys = { git = "https://github.com/ianks/rb-sys", tag = "v0.3.0" }
// replace the contents of ruffle/src/lib.rs with the code below
use rb_sys::{
rb_define_module, rb_define_module_function, rb_string_value_cstr, rb_utf8_str_new, VALUE,
};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_long};
#[inline]
unsafe fn cstr_to_string(str: *const c_char) -> String {
CStr::from_ptr(str).to_string_lossy().into_owned()
}
#[no_mangle]
unsafe extern "C" fn pub_shuffle(_klass: VALUE, mut input: VALUE) -> VALUE {
let ruby_string = cstr_to_string(rb_string_value_cstr(&mut input));
let shuffled = ruby_string.chars().rev().collect::<String>();
let shuffled_cstring = CString::new(shuffled).unwrap();
let size = ruby_string.len() as c_long;
rb_utf8_str_new(shuffled_cstring.as_ptr(), size)
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Init_ruffle() {
let name = CString::new("Ruffle").unwrap();
let shuffle = CString::new("shuffle").unwrap();
let callback = unsafe {
std::mem::transmute::<
unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
unsafe extern "C" fn() -> VALUE,
>(pub_shuffle)
};
let klass = unsafe { rb_define_module(name.as_ptr()) };
unsafe { rb_define_module_function(klass, shuffle.as_ptr(), Some(callback), 1) }
}
$ rake compile spec
# ...omitted
Ruffle
has a #shuffle method (FAILED - 1)
Failures:
1) Ruffle has a #shuffle method
Failure/Error: expect(Ruffle).to respond_to(:shuffle)
expected Ruffle to respond to :shuffle
# ./spec/ruffle_spec.rb:5:in `block (2 levels) in <top (required)>'
$ rm lib/ruffle.rb
$ rake spec
Ruffle
has a #shuffle method
Finished in 0.00205 seconds (files took 0.09782 seconds to load)
1 example, 0 failures
# in spec/ruffle_spec.rb
RSpec.describe Ruffle do
# ...
it "shuffles the characters in a string" do
string = "Ruffle"
expect(Ruffle.shuffle(string)).to_not eq(string)
expect(Ruffle.shuffle(string)).to_not eq(string.reverse)
end
end
$ rake
Ruffle
has a #shuffle method
shuffles the characters in a string (FAILED - 1)
Failures:
1) Ruffle shuffles the characters in a string
Failure/Error: expect(Ruffle.shuffle(string)).to_not eq(string.reverse)
expected: value != "elffuR"
got: "elffuR"
// in lib.rs
#[no_mangle]
unsafe extern "C" fn pub_shuffle(_klass: VALUE, mut input: VALUE) -> VALUE {
let ruby_string = cstr_to_string(rb_string_value_cstr(&mut input));
let shuffled = ruby_string.chars().rev().collect::<String>();
let shuffled_cstring = CString::new(shuffled).unwrap();
let size = ruby_string.len() as c_long;
rb_utf8_str_new(shuffled_cstring.as_ptr(), size)
}
// from https://stackoverflow.com/a/26035435/1042144
use rand::thread_rng;
use rand::seq::SliceRandom;
fn main() {
let mut vec: Vec<u32> = (0..10).collect();
vec.shuffle(&mut thread_rng());
println!("{:?}", vec);
}
# in Cargo.toml
[dependencies]
# ...
rand = "0.8.4"
// in rust_ruby_example/src/lib.rs
let reversed = ruby_string.chars().rev().collect::<String>();
let reversed_cstring = CString::new(reversed).unwrap();
mut input: VALUE
rb_string_value_cstr(&mut input)
cstr_to_string(rb_string_value_cstr(&mut input))
let mut chars: Vec<char> = cstr_to_string(rb_string_value_cstr(&mut input))
.chars()
.collect();
// at the top of lib.rs
use rand::seq::SliceRandom;
use rand::thread_rng;
chars.shuffle(&mut thread_rng());
// in lib.rs
#[no_mangle]
unsafe extern "C" fn pub_shuffle(_klass: VALUE, mut input: VALUE) -> VALUE {
let mut chars: Vec<char> = cstr_to_string(rb_string_value_cstr(&mut input))
.chars()
.collect();
chars.shuffle(&mut thread_rng());
// ...we'll add more code here
}
let shuffled: String = chars.iter().collect();
let size = shuffled.len() as c_long;
let shuffled_cstring = CString::new(shuffled).unwrap();
rb_utf8_str_new(shuffled_cstring.as_ptr(), size)
// in lib.rs
#[no_mangle]
unsafe extern "C" fn pub_shuffle(_klass: VALUE, mut input: VALUE) -> VALUE {
let mut chars: Vec<char> = cstr_to_string(rb_string_value_cstr(&mut input))
.chars()
.collect();
chars.shuffle(&mut thread_rng());
let shuffled: String = chars.iter().collect();
let size = shuffled.len() as c_long;
let shuffled_cstring = CString::new(shuffled).unwrap();
rb_utf8_str_new(shuffled_cstring.as_ptr(), size)
}
// in lib.rs (at the end)
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Init_ruffle() {
let name = CString::new("Ruffle").unwrap();
let shuffle = CString::new("shuffle").unwrap();
let callback = unsafe {
std::mem::transmute::<
unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
unsafe extern "C" fn() -> VALUE,
>(pub_shuffle)
};
let klass = unsafe { rb_define_module(name.as_ptr()) };
unsafe { rb_define_module_function(klass, shuffle.as_ptr(), Some(callback), 1) }
}
$ rake compile spec
true
Successfully uninstalled ruffle-0.1.0
Successfully built RubyGem
Name: ruffle
Version: 0.1.0
File: ruffle.gem
Building native extensions. This could take a while...
Successfully installed ruffle-0.1.0
1 gem installed
Ruffle
has a #shuffle method
shuffles the characters in a string
Finished in 0.00249 seconds (files took 0.15933 seconds to load)
2 examples, 0 failures
[...document.querySelectorAll('.wp-block-code')].map(el => {
console.log(el.innerText);
console.log("~~~~~~~~~~");
})
files = ARGF.read.split("~~~~~~~~~~").map(&:strip)
files.each_with_index do |file, index|
ext = case file[0]
when '/' then 'rs'
when '$' then 'sh'
when '#' then 'rb'
else 'txt'
end
File.write("%03d.#{ext}" % index, file.strip)
end

Right now the workflow for converting a wordpress blog post that has already had simple code embed blocks is:

  1. Create a new github gist with a temporary file in it
  2. Clone the gist using ssh: https://docs.github.com/en/get-started/writing-on-github/editing-and-sharing-content-with-gists/forking-and-cloning-gists
  3. Load the blog post in question
  4. Run the contents of output-all-wp-code-blocks.js in the browser console
  5. Copy the console output (right click and select "Export visible messages to > clipboard")
  6. cd into your github gist repository
  7. Run this in terminal session pbpaste | ruby splitter.rb to generate files from the clipboard
  8. delete the temporary file
  9. Manually edit the files to make sure the file extensions are correct / review in general
  10. commit and push to the gist
  11. View the gist in the browser
  12. Run the contents of copy-github-gist-link-on-click.js
  13. Click the anchor links of each code snippet to copy the url
  14. Paste the URL into the blog post above each code snippet to create a github gist embed
  15. Review and confirm each gist corresponds to the wp code block
  16. Delete the wp code blocks

...whew! Wtf that's what I did? Sigh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment