From 15028ad934703d0da3224b3d728d19c13a94e7a2 Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Mon, 15 Dec 2025 07:16:25 -0600 Subject: [PATCH 1/9] Upgrade to Ruby 3.2: add .ruby-version and update shebang --- .ruby-version | 1 + rp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..a4f52a5 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.2.0 \ No newline at end of file diff --git a/rp b/rp index a54cf4c..520656c 100755 --- a/rp +++ b/rp @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/opt/homebrew/opt/ruby@3.2/bin/ruby require 'bundler/setup' require 'colorize' From c53895d22c6558598ecf44c3db192621f81d50f0 Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Mon, 15 Dec 2025 16:24:55 -0600 Subject: [PATCH 2/9] modernization --- AGENTS.md | 19 ++++++++++++++++ Gemfile | 7 +++--- initRubyGems.sh | 2 +- lessons/0.0.json | 10 ++++----- lessons/0.1.json | 10 ++++----- lessons/0.2.json | 8 +++---- lessons/1.0.json | 18 +++++++-------- lib/justify.rb | 23 ------------------- lib/stringformat.rb | 54 +++++++++++++++++++++++++++++++++++++++++++++ rp | 29 +++++++++++------------- 10 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 AGENTS.md delete mode 100644 lib/justify.rb create mode 100644 lib/stringformat.rb diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d9be1ed --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +## Ruby Primer Agent Instructions + +### Build, Lint, and Test + +- **Testing**: This project uses RSpec. + - Run all tests: `bundle exec rspec` + - Run a single test file: `bundle exec rspec examples_spec/0.0.2.rb` +- **Linting**: There is no linting configuration. Please adhere to the style of existing code. + +### Code Style + +- **Formatting**: Use 4-space indentation. +- **Naming**: Use `snake_case` for variables and methods. +- **Strings**: Use double quotes for strings, especially with interpolation. Use single quotes for `require` statements. +- **Methods**: Use parentheses for method definitions with arguments. +- **Error Handling**: Not specified. +- **Imports**: Use `require`. +- **Types**: Not specified. +- **General**: Follow existing patterns in the code. Explicit `return` is preferred. diff --git a/Gemfile b/Gemfile index d2ecdb6..bfae0fb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source 'https://rubygems.org' -gem 'colorize' -gem 'figlet' -gem 'rspec' -gem 'json' +gem 'colorize', '~> 1.1.0' +gem 'ruby_figlet', '~> 0.6' +gem 'rspec', '~> 3.13.0' diff --git a/initRubyGems.sh b/initRubyGems.sh index 184754c..af60699 100755 --- a/initRubyGems.sh +++ b/initRubyGems.sh @@ -1,3 +1,3 @@ #!/bin/sh -bundle install --path vendor/bundle +/opt/homebrew/opt/ruby@3.2/bin/bundle install --path vendor/bundle diff --git a/lessons/0.0.json b/lessons/0.0.json index dcb7b63..45a014c 100644 --- a/lessons/0.0.json +++ b/lessons/0.0.json @@ -15,7 +15,7 @@ }, { "type": "paragraph", - "value": "Roleplaying as an object in your program is an integral part of object-oriented programming. To know which object you are at the moment, one may use the keyword 'self'." + "value": "Roleplaying as an object in your program is an integral part of object-oriented programming. To know which object you are at the moment, one may use the keyword `self`." }, { "type": "example", @@ -26,7 +26,7 @@ }, { "type": "paragraph", - "value": "As you can see, if you don't specify which object you are, you automatically play the role of the 'main' object that Ruby provides us by default." + "value": "As you can see, if you don't specify which object you are, you automatically play the role of the `main` object that Ruby provides us by default." }, { "type": "paragraph", @@ -44,7 +44,7 @@ }, { "type": "paragraph", - "value": "In the example below, we call the method 'even?' on the object that is the number '2' by placing a period (.) after the object, then adding in the name of the method we want to invoke." + "value": "In the example below, we call the method `even?` on the object that is the number `2` by placing a period (`.`) after the object, then adding in the name of the method we want to invoke." }, { "type": "example", @@ -55,11 +55,11 @@ }, { "type": "paragraph", - "value": "Invoking a method on an object inevitably generates a response. This response is always another object. Calling the method 'next' on the object '1' has it give us the next consecutive value, '2'." + "value": "Invoking a method on an object inevitably generates a response. This response is always another object. Calling the method `next` on the object `1` has it give us the next consecutive value, `2`." }, { "type": "paragraph", - "value": "One may also chain method invocations by simply adding more periods and method names sequentially - each method in the chain is called on the result of the previous method. Go on and try it by invoking 'next' twice on '1' to get '3'." + "value": "One may also chain method invocations by simply adding more periods and method names sequentially - each method in the chain is called on the result of the previous method. Go on and try it by invoking `next` twice on `1` to get `3`." }, { "type": "test_example", diff --git a/lessons/0.1.json b/lessons/0.1.json index ba74f13..fa7c9ce 100644 --- a/lessons/0.1.json +++ b/lessons/0.1.json @@ -7,7 +7,7 @@ }, { "type": "paragraph", - "value": "Ruby objects are happy to tell you what methods they provide. You simply call the 'methods' method on them." + "value": "Ruby objects are happy to tell you what methods they provide. You simply call the `methods` method on them." }, { "type": "example", @@ -18,7 +18,7 @@ }, { "type": "paragraph", - "value": "As you can see, you get a listing of all the methods on the number '1' that you could invoke. The names are prefixed with a colon (:) that you can safely ignore for now. If you find the results too muddled, you can easily sort them alphabetically. Try it for yourself - simply call the method 'sort' on the result of 'methods':" + "value": "As you can see, you get a listing of all the methods on the number `1` that you could invoke. The names are prefixed with a colon (`:`) that you can safely ignore for now. If you find the results too muddled, you can easily sort them alphabetically. Try it for yourself - simply call the method `sort` on the result of `methods`:" }, { "type": "test_example", @@ -40,7 +40,7 @@ }, { "type": "paragraph", - "value": "Here's an example of an argument to the method 'index', which finds the position of the argument in the array:" + "value": "Here's an example of an argument to the method `index`, which finds the position of the argument in the array:" }, { "type": "example", @@ -51,11 +51,11 @@ }, { "type": "paragraph", - "value": "Here, 'index' is the method and 'paper' the argument. If there is more than one argument, they can be passed to the method by simply separating them with commas." + "value": "Here, `index` is the method and `paper` the argument. If there is more than one argument, they can be passed to the method by simply separating them with commas." }, { "type": "paragraph", - "value": "Try using a method that takes two arguments - use the 'between?' method to determine if the number '2' lies between the numbers '1' and '3'." + "value": "Try using a method that takes two arguments - use the `between?` method to determine if the number `2` lies between the numbers `1` and `3`." }, { "type": "test_example", diff --git a/lessons/0.2.json b/lessons/0.2.json index d91ad67..a59a009 100644 --- a/lessons/0.2.json +++ b/lessons/0.2.json @@ -3,11 +3,11 @@ "name": "Syntactic Sugar for Special Methods", "items": [{ "type": "paragraph", - "value": "As an observant initiate, you've probably noticed that in the last lesson, Integer objects list mathematical operators like '+' and '-' amont their methods. You probably also thought to yourself that invoking the '+' method like so - '1.+(2)' - to add two numbers would be ... clumsy." + "value": "As an observant initiate, you've probably noticed that in the last lesson, `Integer` objects list mathematical operators like `+` and `-` amont their methods. You probably also thought to yourself that invoking the `+` method like so - `1.+(2)` - to add two numbers would be ... clumsy." }, { "type": "paragraph", - "value": "It is, though it works just fine - try for yourself by adding '4' to '3' in the exercise below." + "value": "It is, though it works just fine - try for yourself by adding `4` to `3` in the exercise below." }, { "type": "test_example", @@ -37,11 +37,11 @@ }, { "type": "paragraph", - "value": " + - * / = == != > < >= <= []" + "value": " `+` `-` `*` `/` `=` `==` `!=` `>` `<` `>=` `<=` `[]`" }, { "type": "paragraph", - "value": "This last method ('[]') you've probably already seen in the lesson that covers Arrays, and is arguably the most unique in its syntax. Not only does it not require a period, it also encloses the arguments to itself. Here's a quick example to refresh your memory." + "value": "This last method (`[]`) you've probably already seen in the lesson that covers Arrays, and is arguably the most unique in its syntax. Not only does it not require a period, it also encloses the arguments to itself. Here's a quick example to refresh your memory." }, { "type": "example", diff --git a/lessons/1.0.json b/lessons/1.0.json index 903a514..2a42691 100644 --- a/lessons/1.0.json +++ b/lessons/1.0.json @@ -7,11 +7,11 @@ }, { "type": "paragraph", - "value": "In doing lies the true path, so let us begin by doing. Type 'edit' and then type \"RubyMonk\" in the editor (remember to include the double quotes)." + "value": "In doing lies the true path, so let us begin by doing. Type `edit` and then type \"RubyMonk\" in the editor (remember to include the double quotes)." }, { "type": "paragraph", - "value": "After you've edited the file, type 'run' to see what happens." + "value": "After you've edited the file, type `run` to see what happens." }, { "type": "test_example", @@ -27,11 +27,11 @@ }, { "type": "paragraph", - "value": "String construction has what is known as a literal form - the interpreter treats anything surrounded with single quotes (') or double quotes(\") as a string. In other words, both 'RubyMonk' and \"RubyMonk\" will create instances of strings." + "value": "String construction has what is known as a literal form - the interpreter treats anything surrounded with single quotes (`'`) or double quotes(`\"`) as a string. In other words, both `'RubyMonk'` and `\"RubyMonk\"` will create instances of strings." }, { "type": "paragraph", - "value": "It's your turn now; go ahead and create a string that contains the name of the current month. Like, 'April', or 'December'." + "value": "It's your turn now; go ahead and create a string that contains the name of the current month. Like, `April`, or `December`." }, { "type": "test_example", @@ -39,7 +39,7 @@ }, { "type": "paragraph", - "value": "Action etches deeper into the memory than mere words. Recreate the string in the exercise above using double quotes (\"). You'll see that the tests pass the same way as they did before." + "value": "Action etches deeper into the memory than mere words. Recreate the string in the exercise above using double quotes (`\"`). You'll see that the tests pass the same way as they did before." }, { "type": "test_example", @@ -51,7 +51,7 @@ }, { "type": "paragraph", - "value": "All Strings are instances of the Ruby String class which provides a number of methods to manipulate the string. Now that you have successfully mastered creating strings let's take a look at some of the most commonly used methods." + "value": "All Strings are instances of the Ruby `String` class which provides a number of methods to manipulate the string. Now that you have successfully mastered creating strings let's take a look at some of the most commonly used methods." }, { "type": "paragraph", @@ -59,11 +59,11 @@ }, { "type": "paragraph", - "value": "As a programmer, one of the common things you need to know about a String is its length. Instead of merely telling you how to do this, let me share a secret about the Ruby API with you: Try the obvious! The Ruby core libraries are very intuitive, and with a little practice you can effortlessly convert instructions in plain English to Ruby." + "value": "As a programmer, one of the common things you need to know about a `String` is its length. Instead of merely telling you how to do this, let me share a secret about the Ruby API with you: Try the obvious! The Ruby core libraries are very intuitive, and with a little practice you can effortlessly convert instructions in plain English to Ruby." }, { "type": "paragraph", - "value": "Test your intuition and try to change the code below to return the length of the string 'RubyMonk'." + "value": "Test your intuition and try to change the code below to return the length of the string `RubyMonk`." }, { "type": "test_example", @@ -74,4 +74,4 @@ "value": "That shouldn't have taxed your intuition too much! Let's move on to slightly more complex concepts." }] }] -} \ No newline at end of file +} diff --git a/lib/justify.rb b/lib/justify.rb deleted file mode 100644 index 615be17..0000000 --- a/lib/justify.rb +++ /dev/null @@ -1,23 +0,0 @@ -# created from the 'justify' gem - http://www.rubydoc.info/gems/justify/1.0.2 -class String - def justify(len = 80) - unless self.length < len - words = self.gsub("\n", " ").scan(/[^ ]+/) - actual_len = 0 - output = "" - words.each do |w| - output += w - actual_len += w.length - if actual_len >= len - output += "\n" - actual_len = 0 - else - output += " " - end - end - return output - else - self - end - end -end diff --git a/lib/stringformat.rb b/lib/stringformat.rb new file mode 100644 index 0000000..ee29170 --- /dev/null +++ b/lib/stringformat.rb @@ -0,0 +1,54 @@ +class String + def format() + require 'tempfile' + require 'base64' + + # Get terminal columns, similar to tput cols + cols = `tput cols`.to_i + em = (cols * 0.60).to_f + + commonmark_content = <<~CMARK + --- + fontcolor: white + documentclass: standalone + classoption: preview + header-includes: + - | + ```{=latex} + \\usepackage[width=#{em}em]{geometry} + \\usepackage{xcolor} + \\pagecolor{black} + ``` + include-before: + - | + ```{=latex} + \\color{white} + \\Large + ``` + ... + #{self} + CMARK + + tmp_file = Tempfile.new(['stringformat', '.md']) + tmp_file.write(commonmark_content) + tmp_file.close + + pdf_file = tmp_file.path.sub('.md', '.pdf') + + # Run pandoc + system("pandoc --pdf-engine=xelatex -V mainfont=\"IBM Plex Serif Light\" -f commonmark_x #{tmp_file.path} -o #{pdf_file} >/dev/null 2>&1") + + if File.exist?(pdf_file) + pdf_data = File.read(pdf_file) + base64_data = Base64.strict_encode64(pdf_data) + # Clean up + tmp_file.unlink + File.unlink(pdf_file) + # Return iTerm2 inline image sequence + "\e]1337;File=inline=1;preserveAspectRatio=1:#{base64_data}\a" + else + # Fallback if PDF generation fails + self + end + end +end diff --git a/rp b/rp index 520656c..2cb9c85 100755 --- a/rp +++ b/rp @@ -2,37 +2,33 @@ require 'bundler/setup' require 'colorize' -require 'figlet' +require 'ruby_figlet' require 'tempfile' require 'rspec' require 'rspec/core/formatters/progress_formatter' require 'json' -load 'lib/justify.rb' +load 'lib/stringformat.rb' load 'lib/sayings.rb' def print_header(header) puts "" puts "===========================" - font = Figlet::Font.new('fonts/standard.flf') - figlet = Figlet::Typesetter.new(font) - puts figlet[header].magenta + figlet = RubyFiglet::Figlet.new(header, 'standard') + puts figlet.to_s.magenta puts "===========================" puts "" end def print_welcome() message = <<-WELCOME - - - Welcome to the Ruby Primer. This is a console version of the Ruby Primer - found at http://rubymonk.com/learning/books/1-ruby-primer, maintained - by C42 Engineering. This is a program written in ruby that runs actual - ruby code snippets in the ruby interpreter on your system. It is intended - to be interactive, fun, and educational. Enjoy! - +Welcome to the Ruby Primer. This is a console version of the Ruby Primer +found at http://rubymonk.com/learning/books/1-ruby-primer, maintained +by C42 Engineering. This is a program written in ruby that runs actual +ruby code snippets in the ruby interpreter on your system. It is intended +to be interactive, fun, and educational. Enjoy! WELCOME - puts message.yellow + puts "\n#{message.format}\n\n" end def get_input(question) @@ -42,11 +38,12 @@ def get_input(question) end def print_paragraph(para, mod=nil) - paratext = para.chomp.justify.cyan + paratext = para.chomp if mod puts paratext.send(mod) else - puts paratext + puts "" + puts paratext.format end puts "" end From 4461a7d1fd0ea0b20ad2f097daa781ea354a16a6 Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 05:30:07 -0600 Subject: [PATCH 3/9] fix indentation --- AGENTS.md | 2 +- examples_spec/0.0.2.rb | 8 +- examples_spec/0.1.2.rb | 46 ++++----- examples_spec/0.1.4.rb | 8 +- examples_spec/0.2.1.rb | 8 +- examples_spec/1.0.0.rb | 14 +-- examples_spec/1.0.1.rb | 8 +- examples_spec/1.0.2.rb | 8 +- examples_spec/1.0.3.rb | 8 +- lessons/0.0.json | 83 ++++++++------- lessons/0.1.json | 80 ++++++++------- lessons/0.2.json | 74 +++++++------- lessons/1.0.json | 158 +++++++++++++++-------------- lessons/toc.json | 12 +-- lib/sayings.rb | 116 ++++++++++----------- lib/stringformat.rb | 90 ++++++++--------- rp | 224 ++++++++++++++++++++--------------------- 17 files changed, 483 insertions(+), 464 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d9be1ed..2dd94cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ ### Code Style -- **Formatting**: Use 4-space indentation. +- **Formatting**: Use 2-space indentation. - **Naming**: Use `snake_case` for variables and methods. - **Strings**: Use double quotes for strings, especially with interpolation. Use single quotes for `require` statements. - **Methods**: Use parentheses for method definitions with arguments. diff --git a/examples_spec/0.0.2.rb b/examples_spec/0.0.2.rb index 8c67d01..a606d98 100644 --- a/examples_spec/0.0.2.rb +++ b/examples_spec/0.0.2.rb @@ -1,9 +1,9 @@ require 'rspec' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "results in 3" do - expect(output).to eq("3") - end + it "results in 3" do + expect(output).to eq("3") + end end diff --git a/examples_spec/0.1.2.rb b/examples_spec/0.1.2.rb index bf248e5..9c87669 100644 --- a/examples_spec/0.1.2.rb +++ b/examples_spec/0.1.2.rb @@ -2,29 +2,29 @@ describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - let(:rspec_methods) { [ - :as_null_object, - :null_object?, - :pretty_inspect, - :pretty_print, - :pretty_print_cycle, - :pretty_print_inspect, - :pretty_print_instance_variables, - :received_message?, - :should, - :should_not, - :should_not_receive, - :should_receive, - :stub, - :stub_chain, + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:rspec_methods) { [ + :as_null_object, + :null_object?, + :pretty_inspect, + :pretty_print, + :pretty_print_cycle, + :pretty_print_inspect, + :pretty_print_instance_variables, + :received_message?, + :should, + :should_not, + :should_not_receive, + :should_receive, + :stub, + :stub_chain, :to_json, - :unstub, - ] - } - let(:sorted_methods) { 1.methods.sort - rspec_methods } + :unstub, + ] + } + let(:sorted_methods) { 1.methods.sort - rspec_methods } - it "should sort the method list" do - expect(eval(output)).to eq(sorted_methods) - end + it "should sort the method list" do + expect(eval(output)).to eq(sorted_methods) + end end diff --git a/examples_spec/0.1.4.rb b/examples_spec/0.1.4.rb index 107a648..23f212e 100644 --- a/examples_spec/0.1.4.rb +++ b/examples_spec/0.1.4.rb @@ -1,9 +1,9 @@ require 'rspec' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "should check if two is between one and three" do - expect(eval(output)).to eq(2.between?(1,3)) - end + it "should check if two is between one and three" do + expect(eval(output)).to eq(2.between?(1,3)) + end end diff --git a/examples_spec/0.2.1.rb b/examples_spec/0.2.1.rb index 6ad27fe..63bf6c9 100644 --- a/examples_spec/0.2.1.rb +++ b/examples_spec/0.2.1.rb @@ -1,9 +1,9 @@ require 'rspec' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "should evaluate to seven" do - expect(output).to eq("7") - end + it "should evaluate to seven" do + expect(output).to eq("7") + end end diff --git a/examples_spec/1.0.0.rb b/examples_spec/1.0.0.rb index c7746f0..ef0196f 100644 --- a/examples_spec/1.0.0.rb +++ b/examples_spec/1.0.0.rb @@ -1,13 +1,13 @@ require 'rspec' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "should be a string" do - expect(output.is_a?(String)).to be(true) - end + it "should be a string" do + expect(output.is_a?(String)).to be(true) + end - it "should equal 'RubyMonk'" do - expect(output).to eq("RubyMonk") - end + it "should equal 'RubyMonk'" do + expect(output).to eq("RubyMonk") + end end diff --git a/examples_spec/1.0.1.rb b/examples_spec/1.0.1.rb index 3320a04..ffcc451 100644 --- a/examples_spec/1.0.1.rb +++ b/examples_spec/1.0.1.rb @@ -2,9 +2,9 @@ require 'date' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "should equal the current month" do - expect(output).to eq(Date.today.strftime("%B")) - end + it "should equal the current month" do + expect(output).to eq(Date.today.strftime("%B")) + end end diff --git a/examples_spec/1.0.2.rb b/examples_spec/1.0.2.rb index 3320a04..ffcc451 100644 --- a/examples_spec/1.0.2.rb +++ b/examples_spec/1.0.2.rb @@ -2,9 +2,9 @@ require 'date' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "should equal the current month" do - expect(output).to eq(Date.today.strftime("%B")) - end + it "should equal the current month" do + expect(output).to eq(Date.today.strftime("%B")) + end end diff --git a/examples_spec/1.0.3.rb b/examples_spec/1.0.3.rb index dfd3449..18eac70 100644 --- a/examples_spec/1.0.3.rb +++ b/examples_spec/1.0.3.rb @@ -2,9 +2,9 @@ require 'date' describe "example code" do - let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } + let(:output) { `#{RbConfig.ruby} __TMPFILE__`.chomp } - it "should equal the length of RubyMonk" do - expect(output.to_i).to eq("RubyMonk".length) - end + it "should equal the length of RubyMonk" do + expect(output.to_i).to eq("RubyMonk".length) + end end diff --git a/lessons/0.0.json b/lessons/0.0.json index 45a014c..0f200ec 100644 --- a/lessons/0.0.json +++ b/lessons/0.0.json @@ -1,73 +1,78 @@ { - "subsections": [{ - "name": "Everything is an object", - "items": [{ - "type": "paragraph", - "value": "We will begin our journey with objects." + "subsections": [ + { + "name": "Everything is an object", + "items": [ + { + "type": "paragraph", + "value": "We will begin our journey with objects." }, { - "type": "paragraph", - "value": "In Ruby, just like in real life, our world is filled with objects. Everything is an object - integers, characters, text, arrays - everything." + "type": "paragraph", + "value": "In Ruby, just like in real life, our world is filled with objects. Everything is an object - integers, characters, text, arrays - everything." }, { - "type": "paragraph", - "value": "To make things happen using Ruby, one always puts oneself in the place of an object and then has conversations with other objects, telling them to do stuff." + "type": "paragraph", + "value": "To make things happen using Ruby, one always puts oneself in the place of an object and then has conversations with other objects, telling them to do stuff." }, { - "type": "paragraph", - "value": "Roleplaying as an object in your program is an integral part of object-oriented programming. To know which object you are at the moment, one may use the keyword `self`." + "type": "paragraph", + "value": "Roleplaying as an object in your program is an integral part of object-oriented programming. To know which object you are at the moment, one may use the keyword `self`." }, { - "type": "example", - "value": "0.0.0" + "type": "example", + "value": "0.0.0" }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "As you can see, if you don't specify which object you are, you automatically play the role of the `main` object that Ruby provides us by default." + "type": "paragraph", + "value": "As you can see, if you don't specify which object you are, you automatically play the role of the `main` object that Ruby provides us by default." }, { - "type": "paragraph", - "value": "We'll delve into how one can play the role of different objects and why this is useful a little further down the line." + "type": "paragraph", + "value": "We'll delve into how one can play the role of different objects and why this is useful a little further down the line." }, { - "type": "press_enter" - }] + "type": "press_enter" + } + ] }, { - "name": "Talking to objects", - "items": [{ - "type": "paragraph", - "value": "One object interacts with another by using what are called 'methods'. More specifically, one object 'calls' or 'invokes the methods' of another object." + "name": "Talking to objects", + "items": [ + { + "type": "paragraph", + "value": "One object interacts with another by using what are called 'methods'. More specifically, one object 'calls' or 'invokes the methods' of another object." }, { - "type": "paragraph", - "value": "In the example below, we call the method `even?` on the object that is the number `2` by placing a period (`.`) after the object, then adding in the name of the method we want to invoke." + "type": "paragraph", + "value": "In the example below, we call the method `even?` on the object that is the number `2` by placing a period (`.`) after the object, then adding in the name of the method we want to invoke." }, { - "type": "example", - "value": "0.0.1" + "type": "example", + "value": "0.0.1" }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "Invoking a method on an object inevitably generates a response. This response is always another object. Calling the method `next` on the object `1` has it give us the next consecutive value, `2`." + "type": "paragraph", + "value": "Invoking a method on an object inevitably generates a response. This response is always another object. Calling the method `next` on the object `1` has it give us the next consecutive value, `2`." }, { - "type": "paragraph", - "value": "One may also chain method invocations by simply adding more periods and method names sequentially - each method in the chain is called on the result of the previous method. Go on and try it by invoking `next` twice on `1` to get `3`." + "type": "paragraph", + "value": "One may also chain method invocations by simply adding more periods and method names sequentially - each method in the chain is called on the result of the previous method. Go on and try it by invoking `next` twice on `1` to get `3`." }, { - "type": "test_example", - "value": "0.0.2" + "type": "test_example", + "value": "0.0.2" }, { - "type": "press_enter" - }] - }] + "type": "press_enter" + } + ] + } + ] } - diff --git a/lessons/0.1.json b/lessons/0.1.json index fa7c9ce..7254ba2 100644 --- a/lessons/0.1.json +++ b/lessons/0.1.json @@ -1,68 +1,74 @@ { - "subsections": [{ - "name": "Looking up methods", - "items": [{ - "type": "header", - "value": "Looking up methods" + "subsections": [ + { + "name": "Looking up methods", + "items": [ + { + "type": "header", + "value": "Looking up methods" }, { - "type": "paragraph", - "value": "Ruby objects are happy to tell you what methods they provide. You simply call the `methods` method on them." + "type": "paragraph", + "value": "Ruby objects are happy to tell you what methods they provide. You simply call the `methods` method on them." }, { - "type": "example", - "value": "0.1.1" - }, + "type": "example", + "value": "0.1.1" + }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "As you can see, you get a listing of all the methods on the number `1` that you could invoke. The names are prefixed with a colon (`:`) that you can safely ignore for now. If you find the results too muddled, you can easily sort them alphabetically. Try it for yourself - simply call the method `sort` on the result of `methods`:" + "type": "paragraph", + "value": "As you can see, you get a listing of all the methods on the number `1` that you could invoke. The names are prefixed with a colon (`:`) that you can safely ignore for now. If you find the results too muddled, you can easily sort them alphabetically. Try it for yourself - simply call the method `sort` on the result of `methods`:" }, { - "type": "test_example", - "value": "0.1.2" + "type": "test_example", + "value": "0.1.2" }, { - "type": "press_enter" - }] + "type": "press_enter" + } + ] }, { - "name": "Invoking methods with arguments", - "items": [{ - "type": "paragraph", - "value": "When talking to an object via its methods, it is possible to give it additional information so it can give you an appropriate response." + "name": "Invoking methods with arguments", + "items": [ + { + "type": "paragraph", + "value": "When talking to an object via its methods, it is possible to give it additional information so it can give you an appropriate response." }, { - "type": "paragraph", - "value": "This additional information is called the 'arguments to a method'. The name 'argument' makes sense if you stop to think about the fact that methods are the paths of communication between objects." + "type": "paragraph", + "value": "This additional information is called the 'arguments to a method'. The name 'argument' makes sense if you stop to think about the fact that methods are the paths of communication between objects." }, { - "type": "paragraph", - "value": "Here's an example of an argument to the method `index`, which finds the position of the argument in the array:" + "type": "paragraph", + "value": "Here's an example of an argument to the method `index`, which finds the position of the argument in the array:" }, { - "type": "example", - "value": "0.1.3" + "type": "example", + "value": "0.1.3" }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "Here, `index` is the method and `paper` the argument. If there is more than one argument, they can be passed to the method by simply separating them with commas." + "type": "paragraph", + "value": "Here, `index` is the method and `paper` the argument. If there is more than one argument, they can be passed to the method by simply separating them with commas." }, { - "type": "paragraph", - "value": "Try using a method that takes two arguments - use the `between?` method to determine if the number `2` lies between the numbers `1` and `3`." + "type": "paragraph", + "value": "Try using a method that takes two arguments - use the `between?` method to determine if the number `2` lies between the numbers `1` and `3`." }, { - "type": "test_example", - "value": "0.1.4" + "type": "test_example", + "value": "0.1.4" }, { - "type": "press_enter" - }] - }] + "type": "press_enter" + } + ] + } + ] } diff --git a/lessons/0.2.json b/lessons/0.2.json index a59a009..4899263 100644 --- a/lessons/0.2.json +++ b/lessons/0.2.json @@ -1,69 +1,73 @@ { - "subsections": [{ - "name": "Syntactic Sugar for Special Methods", - "items": [{ - "type": "paragraph", - "value": "As an observant initiate, you've probably noticed that in the last lesson, `Integer` objects list mathematical operators like `+` and `-` amont their methods. You probably also thought to yourself that invoking the `+` method like so - `1.+(2)` - to add two numbers would be ... clumsy." + "subsections": [ + { + "name": "Syntactic Sugar for Special Methods", + "items": [ + { + "type": "paragraph", + "value": "As an observant initiate, you've probably noticed that in the last lesson, `Integer` objects list mathematical operators like `+` and `-` amont their methods. You probably also thought to yourself that invoking the `+` method like so - `1.+(2)` - to add two numbers would be ... clumsy." }, { - "type": "paragraph", - "value": "It is, though it works just fine - try for yourself by adding `4` to `3` in the exercise below." + "type": "paragraph", + "value": "It is, though it works just fine - try for yourself by adding `4` to `3` in the exercise below." }, { - "type": "test_example", - "value": "0.2.1" + "type": "test_example", + "value": "0.2.1" }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "Ruby as a language aims to be extremely programmer-friendly, so you can usually safely assume that there is a better way. Ruby makes an exception in its syntactic rules for commonly used operators so you don't have to use periods to invoke them on object." + "type": "paragraph", + "value": "Ruby as a language aims to be extremely programmer-friendly, so you can usually safely assume that there is a better way. Ruby makes an exception in its syntactic rules for commonly used operators so you don't have to use periods to invoke them on object." }, { - "type": "paragraph", - "value": "Let us rephrase the previous example in a more natural syntax by omitting the periods and brackets." + "type": "paragraph", + "value": "Let us rephrase the previous example in a more natural syntax by omitting the periods and brackets." }, { - "type": "example", - "value": "0.2.2" + "type": "example", + "value": "0.2.2" }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "There are several other method names that have this special status - here's a quick summary of the ones you're most likely to run into." + "type": "paragraph", + "value": "There are several other method names that have this special status - here's a quick summary of the ones you're most likely to run into." }, { - "type": "paragraph", - "value": " `+` `-` `*` `/` `=` `==` `!=` `>` `<` `>=` `<=` `[]`" + "type": "paragraph", + "value": " `+` `-` `*` `/` `=` `==` `!=` `>` `<` `>=` `<=` `[]`" }, { - "type": "paragraph", - "value": "This last method (`[]`) you've probably already seen in the lesson that covers Arrays, and is arguably the most unique in its syntax. Not only does it not require a period, it also encloses the arguments to itself. Here's a quick example to refresh your memory." + "type": "paragraph", + "value": "This last method (`[]`) you've probably already seen in the lesson that covers Arrays, and is arguably the most unique in its syntax. Not only does it not require a period, it also encloses the arguments to itself. Here's a quick example to refresh your memory." }, { - "type": "example", - "value": "0.2.3" + "type": "example", + "value": "0.2.3" }, { - "type": "paragraph", - "value": "Even more interesting is that it still works if you use the more traditional method syntax - see for yourself by running the example below." + "type": "paragraph", + "value": "Even more interesting is that it still works if you use the more traditional method syntax - see for yourself by running the example below." }, { - "type": "example", - "value": "0.2.4" + "type": "example", + "value": "0.2.4" }, { - "type": "press_enter" + "type": "press_enter" }, { - "type": "paragraph", - "value": "This is a common pattern in Ruby - two different ways to do the same thing where one remains consistent and the other changes the syntax to be more programmer friendly." + "type": "paragraph", + "value": "This is a common pattern in Ruby - two different ways to do the same thing where one remains consistent and the other changes the syntax to be more programmer friendly." }, { - "type": "press_enter" - }] - }] + "type": "press_enter" + } + ] + } + ] } diff --git a/lessons/1.0.json b/lessons/1.0.json index 2a42691..8780e9d 100644 --- a/lessons/1.0.json +++ b/lessons/1.0.json @@ -1,77 +1,81 @@ -{ - "subsections": [{ - "name": "Introduction to Strings", - "items": [{ - "type": "paragraph", - "value": "String construction" - }, - { - "type": "paragraph", - "value": "In doing lies the true path, so let us begin by doing. Type `edit` and then type \"RubyMonk\" in the editor (remember to include the double quotes)." - }, - { - "type": "paragraph", - "value": "After you've edited the file, type `run` to see what happens." - }, - { - "type": "test_example", - "value": "1.0.0" - }, - { - "type": "paragraph", - "value": "Strings are key to communicating with a user and programming apprentices will encounter and start manipulating strings from the earliest stages of their journey. In this lesson, we will explore some of the tools Ruby provides to create and manipulate strings." - }, - { - "type": "paragraph", - "value": "Literal forms" - }, - { - "type": "paragraph", - "value": "String construction has what is known as a literal form - the interpreter treats anything surrounded with single quotes (`'`) or double quotes(`\"`) as a string. In other words, both `'RubyMonk'` and `\"RubyMonk\"` will create instances of strings." - }, - { - "type": "paragraph", - "value": "It's your turn now; go ahead and create a string that contains the name of the current month. Like, `April`, or `December`." - }, - { - "type": "test_example", - "value": "1.0.1" - }, - { - "type": "paragraph", - "value": "Action etches deeper into the memory than mere words. Recreate the string in the exercise above using double quotes (`\"`). You'll see that the tests pass the same way as they did before." - }, - { - "type": "test_example", - "value": "1.0.2" - }, - { - "type": "paragraph", - "value": "The single quoted and double quoted approaches have some differences, which we will look into later. For most purposes they are equivalent." - }, - { - "type": "paragraph", - "value": "All Strings are instances of the Ruby `String` class which provides a number of methods to manipulate the string. Now that you have successfully mastered creating strings let's take a look at some of the most commonly used methods." - }, - { - "type": "paragraph", - "value": "String Length" - }, - { - "type": "paragraph", - "value": "As a programmer, one of the common things you need to know about a `String` is its length. Instead of merely telling you how to do this, let me share a secret about the Ruby API with you: Try the obvious! The Ruby core libraries are very intuitive, and with a little practice you can effortlessly convert instructions in plain English to Ruby." - }, - { - "type": "paragraph", - "value": "Test your intuition and try to change the code below to return the length of the string `RubyMonk`." - }, - { - "type": "test_example", - "value": "1.0.3" - }, - { - "type": "paragraph", - "value": "That shouldn't have taxed your intuition too much! Let's move on to slightly more complex concepts." - }] - }] -} +{ + "subsections": [ + { + "name": "Introduction to Strings", + "items": [ + { + "type": "paragraph", + "value": "String construction" + }, + { + "type": "paragraph", + "value": "In doing lies the true path, so let us begin by doing. Type `edit` and then type \"RubyMonk\" in the editor (remember to include the double quotes)." + }, + { + "type": "paragraph", + "value": "After you've edited the file, type `run` to see what happens." + }, + { + "type": "test_example", + "value": "1.0.0" + }, + { + "type": "paragraph", + "value": "Strings are key to communicating with a user and programming apprentices will encounter and start manipulating strings from the earliest stages of their journey. In this lesson, we will explore some of the tools Ruby provides to create and manipulate strings." + }, + { + "type": "paragraph", + "value": "Literal forms" + }, + { + "type": "paragraph", + "value": "String construction has what is known as a literal form - the interpreter treats anything surrounded with single quotes (`'`) or double quotes(`\"`) as a string. In other words, both `'RubyMonk'` and `\"RubyMonk\"` will create instances of strings." + }, + { + "type": "paragraph", + "value": "It's your turn now; go ahead and create a string that contains the name of the current month. Like, `April`, or `December`." + }, + { + "type": "test_example", + "value": "1.0.1" + }, + { + "type": "paragraph", + "value": "Action etches deeper into the memory than mere words. Recreate the string in the exercise above using double quotes (`\"`). You'll see that the tests pass the same way as they did before." + }, + { + "type": "test_example", + "value": "1.0.2" + }, + { + "type": "paragraph", + "value": "The single quoted and double quoted approaches have some differences, which we will look into later. For most purposes they are equivalent." + }, + { + "type": "paragraph", + "value": "All Strings are instances of the Ruby `String` class which provides a number of methods to manipulate the string. Now that you have successfully mastered creating strings let's take a look at some of the most commonly used methods." + }, + { + "type": "paragraph", + "value": "String Length" + }, + { + "type": "paragraph", + "value": "As a programmer, one of the common things you need to know about a `String` is its length. Instead of merely telling you how to do this, let me share a secret about the Ruby API with you: Try the obvious! The Ruby core libraries are very intuitive, and with a little practice you can effortlessly convert instructions in plain English to Ruby." + }, + { + "type": "paragraph", + "value": "Test your intuition and try to change the code below to return the length of the string `RubyMonk`." + }, + { + "type": "test_example", + "value": "1.0.3" + }, + { + "type": "paragraph", + "value": "That shouldn't have taxed your intuition too much! Let's move on to slightly more complex concepts." + } + ] + } + ] +} diff --git a/lessons/toc.json b/lessons/toc.json index f46529c..e3dbf00 100644 --- a/lessons/toc.json +++ b/lessons/toc.json @@ -1,8 +1,8 @@ { - "lessons": [ - "0.0.json", - "0.1.json", - "0.2.json", - "1.0.json" - ] + "lessons": [ + "0.0.json", + "0.1.json", + "0.2.json", + "1.0.json" + ] } diff --git a/lib/sayings.rb b/lib/sayings.rb index 1f875b6..3be388d 100644 --- a/lib/sayings.rb +++ b/lib/sayings.rb @@ -1,66 +1,66 @@ module Sayings - def Sayings.get_saying(type) - happy = [ - "More ramen to you!", - "I'm pleased.", - "I see great potential.", - "Thou shalt pass.", - "Good, good!", - "That was quite something.", - "Most excellent.", - "I'm proud of you.", - "Your brothers are proud of you.", - "Your sisters are proud of you.", - "Like a feather, your code floats in the air.", - "Pizza for you! With anchovies I presume?", - "Sweet smell of working code.", - "As you grow older, you will learn there are many ways to do the right thing.", - "Miyagi have hope for you.", - "Good! Now use head for something other than target.", - "Man who catch fly with chopstick accomplish anything.", - "Banzai!", - "Do you want to learn kung fu?", - "I vowed to train you, and you have been trained. You are free to eat.", - "There are no accidents.", - "What goes on inside your head I do not always understand. But what goes on in your heart will never let us down.", - "Beautiful. Elegant. Perfect. You made me proud.", - "The Force will be with you, always." - ] - - sad = [ - "Do I hear a spec failing?", - "The path to enlightenment is a little long.", - "You shall not pass!", - "This isn't the code I was looking for.", - "Hmm, a valiant attempt. But you must try again!", - "Not quite there yet.", - "No pizza for you. Not yet.", - "Never give up. Ever!", - "Your code did not fail, you just found another method that will not solve this problem.", - "Ten flips, now!", - "If no mistake have you made, yet losing you are ... a different game you should play.", - "No! Try not. Do, or do not. There is no try.", - "No such thing as bad student, only bad teacher. Teacher say, student do.", - "You too much TV.", - "First learn stand, then learn fly. Nature rule, Daniel-san, not mine.", - "Wax on... wax off. Wax on... wax off.", - "Breathe in, breathe out. And no scare fish.", - "Hai! Hurt old man feeling.", - "Oh, Daniel-san! You all wet behind ear!", - "Learn how punch, after you learn how keep dry!", - "Look at this tree, Shifu: I cannot make it blossom when it suits me nor make it bear fruit before its time.", - "There is now a level zero.", - "There are no accidents.", - "Remember the path!", - "When the path you walk always leads back to yourself, you never get anywhere.", - "To make a bad day worse spend it wishing for the impossible." - ] + def Sayings.get_saying(type) + happy = [ + "More ramen to you!", + "I'm pleased.", + "I see great potential.", + "Thou shalt pass.", + "Good, good!", + "That was quite something.", + "Most excellent.", + "I'm proud of you.", + "Your brothers are proud of you.", + "Your sisters are proud of you.", + "Like a feather, your code floats in the air.", + "Pizza for you! With anchovies I presume?", + "Sweet smell of working code.", + "As you grow older, you will learn there are many ways to do the right thing.", + "Miyagi have hope for you.", + "Good! Now use head for something other than target.", + "Man who catch fly with chopstick accomplish anything.", + "Banzai!", + "Do you want to learn kung fu?", + "I vowed to train you, and you have been trained. You are free to eat.", + "There are no accidents.", + "What goes on inside your head I do not always understand. But what goes on in your heart will never let us down.", + "Beautiful. Elegant. Perfect. You made me proud.", + "The Force will be with you, always." + ] + + sad = [ + "Do I hear a spec failing?", + "The path to enlightenment is a little long.", + "You shall not pass!", + "This isn't the code I was looking for.", + "Hmm, a valiant attempt. But you must try again!", + "Not quite there yet.", + "No pizza for you. Not yet.", + "Never give up. Ever!", + "Your code did not fail, you just found another method that will not solve this problem.", + "Ten flips, now!", + "If no mistake have you made, yet losing you are ... a different game you should play.", + "No! Try not. Do, or do not. There is no try.", + "No such thing as bad student, only bad teacher. Teacher say, student do.", + "You too much TV.", + "First learn stand, then learn fly. Nature rule, Daniel-san, not mine.", + "Wax on... wax off. Wax on... wax off.", + "Breathe in, breathe out. And no scare fish.", + "Hai! Hurt old man feeling.", + "Oh, Daniel-san! You all wet behind ear!", + "Learn how punch, after you learn how keep dry!", + "Look at this tree, Shifu: I cannot make it blossom when it suits me nor make it bear fruit before its time.", + "There is now a level zero.", + "There are no accidents.", + "Remember the path!", + "When the path you walk always leads back to yourself, you never get anywhere.", + "To make a bad day worse spend it wishing for the impossible." + ] saying = sad.sample if type == "happy" - saying = happy.sample + saying = happy.sample end saying - end + end end diff --git a/lib/stringformat.rb b/lib/stringformat.rb index ee29170..c4cc6c1 100644 --- a/lib/stringformat.rb +++ b/lib/stringformat.rb @@ -1,54 +1,54 @@ class String - def format() - require 'tempfile' - require 'base64' + def format() + require 'tempfile' + require 'base64' - # Get terminal columns, similar to tput cols - cols = `tput cols`.to_i - em = (cols * 0.60).to_f + # Get terminal columns, similar to tput cols + cols = `tput cols`.to_i + em = (cols * 0.60).to_f - commonmark_content = <<~CMARK - --- - fontcolor: white - documentclass: standalone - classoption: preview - header-includes: - - | - ```{=latex} - \\usepackage[width=#{em}em]{geometry} - \\usepackage{xcolor} - \\pagecolor{black} - ``` - include-before: - - | - ```{=latex} - \\color{white} - \\Large - ``` - ... - #{self} - CMARK + commonmark_content = <<~CMARK + --- + fontcolor: white + documentclass: standalone + classoption: preview + header-includes: + - | + ```{=latex} + \\usepackage[width=#{em}em]{geometry} + \\usepackage{xcolor} + \\pagecolor{black} + ``` + include-before: + - | + ```{=latex} + \\color{white} + \\Large + ``` + ... + #{self} + CMARK - tmp_file = Tempfile.new(['stringformat', '.md']) - tmp_file.write(commonmark_content) - tmp_file.close + tmp_file = Tempfile.new(['stringformat', '.md']) + tmp_file.write(commonmark_content) + tmp_file.close - pdf_file = tmp_file.path.sub('.md', '.pdf') + pdf_file = tmp_file.path.sub('.md', '.pdf') - # Run pandoc - system("pandoc --pdf-engine=xelatex -V mainfont=\"IBM Plex Serif Light\" -f commonmark_x #{tmp_file.path} -o #{pdf_file} >/dev/null 2>&1") + # Run pandoc + system("pandoc --pdf-engine=xelatex -V mainfont=\"IBM Plex Serif Light\" -f commonmark_x #{tmp_file.path} -o #{pdf_file} >/dev/null 2>&1") - if File.exist?(pdf_file) - pdf_data = File.read(pdf_file) - base64_data = Base64.strict_encode64(pdf_data) - # Clean up - tmp_file.unlink - File.unlink(pdf_file) - # Return iTerm2 inline image sequence - "\e]1337;File=inline=1;preserveAspectRatio=1:#{base64_data}\a" - else - # Fallback if PDF generation fails - self - end + if File.exist?(pdf_file) + pdf_data = File.read(pdf_file) + base64_data = Base64.strict_encode64(pdf_data) + # Clean up + tmp_file.unlink + File.unlink(pdf_file) + # Return iTerm2 inline image sequence + "\e]1337;File=inline=1;preserveAspectRatio=1:#{base64_data}\a" + else + # Fallback if PDF generation fails + self end + end end diff --git a/rp b/rp index 2cb9c85..4145bcc 100755 --- a/rp +++ b/rp @@ -12,195 +12,195 @@ load 'lib/stringformat.rb' load 'lib/sayings.rb' def print_header(header) - puts "" - puts "===========================" - figlet = RubyFiglet::Figlet.new(header, 'standard') - puts figlet.to_s.magenta - puts "===========================" - puts "" + puts "" + puts "===========================" + figlet = RubyFiglet::Figlet.new(header, 'standard') + puts figlet.to_s.magenta + puts "===========================" + puts "" end def print_welcome() - message = <<-WELCOME + message = <<-WELCOME Welcome to the Ruby Primer. This is a console version of the Ruby Primer found at http://rubymonk.com/learning/books/1-ruby-primer, maintained by C42 Engineering. This is a program written in ruby that runs actual ruby code snippets in the ruby interpreter on your system. It is intended to be interactive, fun, and educational. Enjoy! - WELCOME - puts "\n#{message.format}\n\n" + WELCOME + puts "\n#{message.format}\n\n" end def get_input(question) - print question - STDOUT.flush - STDIN.gets.chomp + print question + STDOUT.flush + STDIN.gets.chomp end def print_paragraph(para, mod=nil) - paratext = para.chomp - if mod - puts paratext.send(mod) - else - puts "" - puts paratext.format - end + paratext = para.chomp + if mod + puts paratext.send(mod) + else puts "" + puts paratext.format + end + puts "" end def print_example(example, test=false, example_id=nil) - print_paragraph("Example Code:", "underline") - print_paragraph(example, "green") - ans = get_input("Type 'edit' to edit the code in vim, or type 'run' to run the code: ") - if ans.downcase == "edit" - puts "Editing code ..." + print_paragraph("Example Code:", "underline") + print_paragraph(example, "green") + ans = get_input("Type 'edit' to edit the code in vim, or type 'run' to run the code: ") + if ans.downcase == "edit" + puts "Editing code ..." edited_code = edit_code(example) print_example(edited_code, test, example_id) - elsif ans.downcase == "run" - puts "Running code ..." + elsif ans.downcase == "run" + puts "Running code ..." if test - test_code(example, example_id) - else + test_code(example, example_id) + else run_code(example) end - else - puts "" - puts " ERROR: I'm not sure what you mean by '#{ans}' ... please try again".red + else + puts "" + puts " ERROR: I'm not sure what you mean by '#{ans}' ... please try again".red puts "" print_example(example, test, example_id) - end + end end def get_example(example_id) - file = File.new("examples/#{example_id}.rb", "r") - contents = file.read - file.close - contents + file = File.new("examples/#{example_id}.rb", "r") + contents = file.read + file.close + contents end def write_code_to_tmpfile(code) - file = Tempfile.new('rubyprimer') - ObjectSpace.undefine_finalizer(file) - file.write(code) - path = file.path - file.close - path + file = Tempfile.new('rubyprimer') + ObjectSpace.undefine_finalizer(file) + file.write(code) + path = file.path + file.close + path end def read_file_and_remove(path) - contents = File.read(path) - File.unlink(path) - contents + contents = File.read(path) + File.unlink(path) + contents end def edit_code(code) - path = write_code_to_tmpfile(code) - command = "/usr/bin/vim #{path}" - pid = spawn(command) - Process.wait(pid) - read_file_and_remove(path) + path = write_code_to_tmpfile(code) + command = "/usr/bin/vim #{path}" + pid = spawn(command) + Process.wait(pid) + read_file_and_remove(path) end def decorate_output(location) - if location == "top" - puts "#### Output: ####".bold.light_green - elsif location == "bottom" - puts "#################".bold.light_green - end + if location == "top" + puts "#### Output: ####".bold.light_green + elsif location == "bottom" + puts "#################".bold.light_green + end end def run_code(code, decorate=true) - decorate_output("top") unless !decorate - command = "#{RbConfig.ruby} -e '#{code}'" - pid = spawn(command) - Process.wait(pid) - puts "" - decorate_output("bottom") unless !decorate + decorate_output("top") unless !decorate + command = "#{RbConfig.ruby} -e '#{code}'" + pid = spawn(command) + Process.wait(pid) + puts "" + decorate_output("bottom") unless !decorate end def test_code(code, example_id) - path = write_code_to_tmpfile(code) - spec = File.read("examples_spec/#{example_id}.rb") - spec.sub! '__TMPFILE__', path - test_path = write_code_to_tmpfile(spec) - decorate_output("top") + path = write_code_to_tmpfile(code) + spec = File.read("examples_spec/#{example_id}.rb") + spec.sub! '__TMPFILE__', path + test_path = write_code_to_tmpfile(spec) + decorate_output("top") - run_code(code, false) + run_code(code, false) - config = RSpec.configuration - config.color = true - RSpec.clear_examples - status = RSpec::Core::Runner.run([test_path]) + config = RSpec.configuration + config.color = true + RSpec.clear_examples + status = RSpec::Core::Runner.run([test_path]) - should_rerun = false - if status == 0 + should_rerun = false + if status == 0 puts "" - puts "---> SUCCESS: #{Sayings.get_saying('happy')}".bold.light_yellow + puts "---> SUCCESS: #{Sayings.get_saying('happy')}".bold.light_yellow puts "" - else + else puts "" - puts "---> WHOOPS: #{Sayings.get_saying('sad')}".bold.light_red + puts "---> WHOOPS: #{Sayings.get_saying('sad')}".bold.light_red puts "" should_rerun = true - end - decorate_output("bottom") + end + decorate_output("bottom") - File.unlink(path) - File.unlink(test_path) + File.unlink(path) + File.unlink(test_path) - if should_rerun - print_example(get_example(example_id), true, example_id) - end + if should_rerun + print_example(get_example(example_id), true, example_id) + end end def press_enter() - get_input("Press ENTER to continue") + get_input("Press ENTER to continue") end def read_json_file(file_name) - file = File.read(file_name) - JSON.parse(file) + file = File.read(file_name) + JSON.parse(file) end def process_toc(lesson_id=nil) - toc = read_json_file('lessons/toc.json') - - toc['lessons'].each do |lesson_file| - if lesson_id - if lesson_file == "#{lesson_id}.json" - print_paragraph("\n\nSkipping to lesson: #{lesson_id}!") - process_lesson("lessons/#{lesson_id}.json") - end - else - process_lesson("lessons/#{lesson_file}") + toc = read_json_file('lessons/toc.json') + + toc['lessons'].each do |lesson_file| + if lesson_id + if lesson_file == "#{lesson_id}.json" + print_paragraph("\n\nSkipping to lesson: #{lesson_id}!") + process_lesson("lessons/#{lesson_id}.json") end + else + process_lesson("lessons/#{lesson_file}") end + end end def process_lesson(lesson_file) - lesson = read_json_file(lesson_file) - lesson['subsections'].each do |subsection| - print_header(subsection['name']) - subsection['items'].each do |item| - case item['type'] - when "press_enter" - press_enter() + lesson = read_json_file(lesson_file) + lesson['subsections'].each do |subsection| + print_header(subsection['name']) + subsection['items'].each do |item| + case item['type'] + when "press_enter" + press_enter() when "paragraph" - print_paragraph(item['value']) + print_paragraph(item['value']) when "example" - print_example(get_example(item['value'])) + print_example(get_example(item['value'])) when "test_example" - print_example(get_example(item['value']), true, item['value']) - end + print_example(get_example(item['value']), true, item['value']) + end end - end + end end def main(args) - print_welcome() - press_enter() - lesson_id = args[0] - process_toc(lesson_id) + print_welcome() + press_enter() + lesson_id = args[0] + process_toc(lesson_id) end ######################################################## main(ARGV) From 7b4cb57fe02aaf531054d1f1b43881dd73b80c2e Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 05:35:54 -0600 Subject: [PATCH 4/9] more formatting / cleanup --- lib/sad.rb | 28 ------------------------ lib/sayings.rb | 58 +++++++++++++++++++++++++------------------------- rp | 58 +++++++++++++++++++++++++------------------------- 3 files changed, 58 insertions(+), 86 deletions(-) delete mode 100644 lib/sad.rb diff --git a/lib/sad.rb b/lib/sad.rb deleted file mode 100644 index ef820f8..0000000 --- a/lib/sad.rb +++ /dev/null @@ -1,28 +0,0 @@ -sad = [ - "Do I hear a spec failing?", - "The path to enlightenment is a little long.", - "You shall not pass!", - "This isn't the code I was looking for.", - "Hmm, a valiant attempt. But you must try again!", - "Not quite there yet.", - "No pizza for you. Not yet.", - "Never give up. Ever!", - "Your code did not fail, you just found another method that will not solve this problem.", - "Ten flips, now!", - "If no mistake have you made, yet losing you are ... a different game you should play.", - "No! Try not. Do, or do not. There is no try.", - "No such thing as bad student, only bad teacher. Teacher say, student do.", - "You too much TV.", - "First learn stand, then learn fly. Nature rule, Daniel-san, not mine.", - "Wax on... wax off. Wax on... wax off.", - "Breathe in, breathe out. And no scare fish.", - "Hai! Hurt old man feeling.", - "Oh, Daniel-san! You all wet behind ear!", - "Learn how punch, after you learn how keep dry!", - "Look at this tree, Shifu: I cannot make it blossom when it suits me nor make it bear fruit before its time.", - "There is now a level zero.", - "There are no accidents.", - "Remember the path!", - "When the path you walk always leads back to yourself, you never get anywhere.", - "To make a bad day worse spend it wishing for the impossible." -] diff --git a/lib/sayings.rb b/lib/sayings.rb index 3be388d..096a714 100644 --- a/lib/sayings.rb +++ b/lib/sayings.rb @@ -2,30 +2,30 @@ module Sayings def Sayings.get_saying(type) happy = [ - "More ramen to you!", - "I'm pleased.", - "I see great potential.", - "Thou shalt pass.", - "Good, good!", - "That was quite something.", - "Most excellent.", - "I'm proud of you.", - "Your brothers are proud of you.", - "Your sisters are proud of you.", - "Like a feather, your code floats in the air.", - "Pizza for you! With anchovies I presume?", - "Sweet smell of working code.", - "As you grow older, you will learn there are many ways to do the right thing.", - "Miyagi have hope for you.", - "Good! Now use head for something other than target.", - "Man who catch fly with chopstick accomplish anything.", - "Banzai!", - "Do you want to learn kung fu?", - "I vowed to train you, and you have been trained. You are free to eat.", - "There are no accidents.", - "What goes on inside your head I do not always understand. But what goes on in your heart will never let us down.", - "Beautiful. Elegant. Perfect. You made me proud.", - "The Force will be with you, always." + "More ramen to you!", + "I'm pleased.", + "I see great potential.", + "Thou shalt pass.", + "Good, good!", + "That was quite something.", + "Most excellent.", + "I'm proud of you.", + "Your brothers are proud of you.", + "Your sisters are proud of you.", + "Like a feather, your code floats in the air.", + "Pizza for you! With anchovies I presume?", + "Sweet smell of working code.", + "As you grow older, you will learn there are many ways to do the right thing.", + "Miyagi have hope for you.", + "Good! Now use head for something other than target.", + "Man who catch fly with chopstick accomplish anything.", + "Banzai!", + "Do you want to learn kung fu?", + "I vowed to train you, and you have been trained. You are free to eat.", + "There are no accidents.", + "What goes on inside your head I do not always understand. But what goes on in your heart will never let us down.", + "Beautiful. Elegant. Perfect. You made me proud.", + "The Force will be with you, always." ] sad = [ @@ -57,10 +57,10 @@ def Sayings.get_saying(type) "To make a bad day worse spend it wishing for the impossible." ] - saying = sad.sample - if type == "happy" - saying = happy.sample - end - saying + saying = sad.sample + if type == "happy" + saying = happy.sample + end + saying end end diff --git a/rp b/rp index 4145bcc..94c1e0f 100755 --- a/rp +++ b/rp @@ -54,20 +54,20 @@ def print_example(example, test=false, example_id=nil) ans = get_input("Type 'edit' to edit the code in vim, or type 'run' to run the code: ") if ans.downcase == "edit" puts "Editing code ..." - edited_code = edit_code(example) - print_example(edited_code, test, example_id) + edited_code = edit_code(example) + print_example(edited_code, test, example_id) elsif ans.downcase == "run" puts "Running code ..." - if test - test_code(example, example_id) + if test + test_code(example, example_id) else - run_code(example) - end + run_code(example) + end else puts "" puts " ERROR: I'm not sure what you mean by '#{ans}' ... please try again".red - puts "" - print_example(example, test, example_id) + puts "" + print_example(example, test, example_id) end end @@ -134,14 +134,14 @@ def test_code(code, example_id) should_rerun = false if status == 0 - puts "" + puts "" puts "---> SUCCESS: #{Sayings.get_saying('happy')}".bold.light_yellow - puts "" + puts "" else - puts "" + puts "" puts "---> WHOOPS: #{Sayings.get_saying('sad')}".bold.light_red - puts "" - should_rerun = true + puts "" + should_rerun = true end decorate_output("bottom") @@ -167,12 +167,12 @@ def process_toc(lesson_id=nil) toc['lessons'].each do |lesson_file| if lesson_id - if lesson_file == "#{lesson_id}.json" - print_paragraph("\n\nSkipping to lesson: #{lesson_id}!") - process_lesson("lessons/#{lesson_id}.json") - end + if lesson_file == "#{lesson_id}.json" + print_paragraph("\n\nSkipping to lesson: #{lesson_id}!") + process_lesson("lessons/#{lesson_id}.json") + end else - process_lesson("lessons/#{lesson_file}") + process_lesson("lessons/#{lesson_file}") end end end @@ -182,17 +182,17 @@ def process_lesson(lesson_file) lesson['subsections'].each do |subsection| print_header(subsection['name']) subsection['items'].each do |item| - case item['type'] - when "press_enter" - press_enter() - when "paragraph" - print_paragraph(item['value']) - when "example" - print_example(get_example(item['value'])) - when "test_example" - print_example(get_example(item['value']), true, item['value']) - end - end + case item['type'] + when "press_enter" + press_enter() + when "paragraph" + print_paragraph(item['value']) + when "example" + print_example(get_example(item['value'])) + when "test_example" + print_example(get_example(item['value']), true, item['value']) + end + end end end From 9436babb8f50f9454fbe6d8c083d4ab70322e64e Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 05:40:25 -0600 Subject: [PATCH 5/9] add syntax checks --- .github/workflows/json-syntax.yml | 15 +++++++++++++++ .github/workflows/ruby-syntax.yml | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .github/workflows/json-syntax.yml create mode 100644 .github/workflows/ruby-syntax.yml diff --git a/.github/workflows/json-syntax.yml b/.github/workflows/json-syntax.yml new file mode 100644 index 0000000..5d34ca1 --- /dev/null +++ b/.github/workflows/json-syntax.yml @@ -0,0 +1,15 @@ +name: JSON Syntax Check + +on: + pull_request: + paths: + - '**/*.json' + +jobs: + json-syntax: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check JSON syntax + run: | + find . -name '*.json' | xargs -I {} jq . {} > /dev/null \ No newline at end of file diff --git a/.github/workflows/ruby-syntax.yml b/.github/workflows/ruby-syntax.yml new file mode 100644 index 0000000..c888d3f --- /dev/null +++ b/.github/workflows/ruby-syntax.yml @@ -0,0 +1,19 @@ +name: Ruby Syntax Check + +on: + pull_request: + paths: + - '**/*.rb' + +jobs: + ruby-syntax: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + - name: Check Ruby syntax + run: | + find . -name '*.rb' -exec ruby -c {} \; \ No newline at end of file From b31f2f25dea1de3fe7b7c424bfbf1891de18baa6 Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 05:54:13 -0600 Subject: [PATCH 6/9] add color support for latex renderings --- lib/stringformat.rb | 5 ++--- rp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/stringformat.rb b/lib/stringformat.rb index c4cc6c1..24a5d07 100644 --- a/lib/stringformat.rb +++ b/lib/stringformat.rb @@ -1,5 +1,5 @@ class String - def format() + def format(color: 'white') require 'tempfile' require 'base64' @@ -9,7 +9,6 @@ def format() commonmark_content = <<~CMARK --- - fontcolor: white documentclass: standalone classoption: preview header-includes: @@ -22,7 +21,7 @@ def format() include-before: - | ```{=latex} - \\color{white} + \\color{#{color}} \\Large ``` ... diff --git a/rp b/rp index 94c1e0f..1466bf1 100755 --- a/rp +++ b/rp @@ -28,7 +28,7 @@ by C42 Engineering. This is a program written in ruby that runs actual ruby code snippets in the ruby interpreter on your system. It is intended to be interactive, fun, and educational. Enjoy! WELCOME - puts "\n#{message.format}\n\n" + puts "\n#{message.format(color:'yellow')}\n\n" end def get_input(question) From 3dac384b9a0ea4975b9282b7486d99efb2049b62 Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 06:51:03 -0600 Subject: [PATCH 7/9] add toc functionality --- rp | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/rp b/rp index 1466bf1..f56e658 100755 --- a/rp +++ b/rp @@ -162,6 +162,32 @@ def read_json_file(file_name) JSON.parse(file) end +def print_toc + toc = read_json_file('lessons/toc.json') + max_name_length = 0 + toc['lessons'].each do |lesson| + lesson_data = read_json_file("lessons/#{lesson}") + lesson_data['subsections'].each do |sub| + max_name_length = [max_name_length, sub['name'].length].max + end + end + width = 10 + max_name_length + puts "" + puts "Table of Contents".center(width).bold.underline + toc['lessons'].each do |lesson| + id = lesson.sub('.json', '') + puts " • #{id}" + lesson_data = read_json_file("lessons/#{lesson}") + lesson_data['subsections'].each do |sub| + puts " ◦ #{sub['name']}" + end + end + puts (" " * width).bold.underline + puts "" + puts " To begin learning with a specific lesson (such as 0.0), run: #{$0} 0.0" + puts "" +end + def process_toc(lesson_id=nil) toc = read_json_file('lessons/toc.json') @@ -197,10 +223,14 @@ def process_lesson(lesson_file) end def main(args) - print_welcome() - press_enter() - lesson_id = args[0] - process_toc(lesson_id) + if args[0] == "toc" + print_toc + else + print_welcome() + press_enter() + lesson_id = args[0] + process_toc(lesson_id) + end end ######################################################## main(ARGV) From e4e02513586d865c2dabc565f332cbcef3305d1d Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 06:56:25 -0600 Subject: [PATCH 8/9] recapitalize titles --- lessons/0.0.json | 6 +++--- lessons/0.1.json | 6 +++--- lessons/0.2.json | 2 +- lessons/1.0.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lessons/0.0.json b/lessons/0.0.json index 0f200ec..c49a601 100644 --- a/lessons/0.0.json +++ b/lessons/0.0.json @@ -1,7 +1,7 @@ { "subsections": [ { - "name": "Everything is an object", + "name": "Everything Is An Object", "items": [ { "type": "paragraph", @@ -40,7 +40,7 @@ ] }, { - "name": "Talking to objects", + "name": "Talking to Objects", "items": [ { "type": "paragraph", @@ -75,4 +75,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/lessons/0.1.json b/lessons/0.1.json index 7254ba2..1ef8a1a 100644 --- a/lessons/0.1.json +++ b/lessons/0.1.json @@ -1,7 +1,7 @@ { "subsections": [ { - "name": "Looking up methods", + "name": "Looking Up Methods", "items": [ { "type": "header", @@ -32,7 +32,7 @@ ] }, { - "name": "Invoking methods with arguments", + "name": "Invoking Methods with Arguments", "items": [ { "type": "paragraph", @@ -71,4 +71,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/lessons/0.2.json b/lessons/0.2.json index 4899263..d83cdb4 100644 --- a/lessons/0.2.json +++ b/lessons/0.2.json @@ -70,4 +70,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/lessons/1.0.json b/lessons/1.0.json index 8780e9d..6a769e7 100644 --- a/lessons/1.0.json +++ b/lessons/1.0.json @@ -9,7 +9,7 @@ }, { "type": "paragraph", - "value": "In doing lies the true path, so let us begin by doing. Type `edit` and then type \"RubyMonk\" in the editor (remember to include the double quotes)." + "value": "In doing lies the true path, so let us begin by doing. Type `edit` and then type `\"RubyMonk\"` in the editor (remember to include the double quotes)." }, { "type": "paragraph", From bbcd5889241dc1ea38f9d50178a859b466a323ea Mon Sep 17 00:00:00 2001 From: Jason Reeves Date: Tue, 16 Dec 2025 07:00:49 -0600 Subject: [PATCH 9/9] RIP rubymonk --- rp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rp b/rp index f56e658..1b05f43 100755 --- a/rp +++ b/rp @@ -23,10 +23,9 @@ end def print_welcome() message = <<-WELCOME Welcome to the Ruby Primer. This is a console version of the Ruby Primer -found at http://rubymonk.com/learning/books/1-ruby-primer, maintained -by C42 Engineering. This is a program written in ruby that runs actual -ruby code snippets in the ruby interpreter on your system. It is intended -to be interactive, fun, and educational. Enjoy! +previously found at http://rubymonk.com/. This is a program written in `ruby` +that runs actual `ruby` code snippets in the `ruby` interpreter on your system. +It is intended to be interactive, fun, and educational. Enjoy! WELCOME puts "\n#{message.format(color:'yellow')}\n\n" end