-
23
MAY
2015Rich Methods
Some APIs provide collections of dirt simple methods that just do one little thing.
This approach in less common in Ruby though, especially in the core and standard library of the language itself. Ruby often gives us rich methods with lots of switches we can toggle and half hidden behaviors.
Let's look at some examples of what I am talking about.
Get a Line at a Time
I suspect most Rubyists have used
gets()
to read lines of input from some kind ofIO
. Here's the basic usage:>> require "stringio" => true >> f = StringIO.new(<<END_STR) <xml> <tags>Content</tags> </xml> END_STR => #<StringIO:0x007fd5a264fa08> >> f.gets => "<xml>\n" >> f.gets => " <tags>Content</tags>\n"
I didn't want to mess with external files for these trivial examples, so I just loaded
StringIO
from the standard library. It allows us to wrap a simpleString
(defined in this example using the heredoc syntax) in theIO
interface. In other words, I'm callinggets()
here for aString
just as I could with aFile
or$stdin
. -
20
MAR
2015A Curses Application
I've now written two articles covering low-level
curses
and some higher abstractions. We know whatcurses
can do at this point, but we haven't really seen how to put everything together and build a full application. Let's do that today by examining an example of moderate size.I have written the beginnings of a command-line Twitter client using
curses
. To make this easier, I developed a super simple wrapper over the rawcurses
API, called Rurses. Rurses, for Rubycurses
, provides more Ruby-like abstractions over the clunky C API. Here's how the Twitter client looks in action:┌─JEG2─────────────────────────────────┐ @ │ ↑│ │Alex Harms @onealexh… 20/03/2015 14:57│ Clayton Flesher @Cal… 19/03/2015 20:52 │RT @postsecret: http://t.co/LrV0IIYgUM│ @TrevorBramble its inspired by, as in │ │ I played it once for about ten minutes │SimKlabnik 2000 @ste… 20/03/2015 14:57│ and then @JEG2 said 'go make your │the closure docs are finally flowing │ version of that'. │from my fingertips, it seems │ │ │ Mandy Moore @theruby… 19/03/2015 18:31 │ashe dryden @ashedry… 20/03/2015 14:57│ Thanks to @JEG2 I now want a MiP robot │Can anyone recommend an interestingly│ in the worst kind of way!!! │written (not dry) history of Haiti? │ │Preferably written by a Haitian. │ Sam Livingston-Gray … 19/03/2015 14:23 │ │ @avdi @JEG2 hush! Keep this up and │Jessica Lord @jllord 20/03/2015 14:57│ EVERYONE WILL KNOW │RT @PDX44: Welcome to Portland, where │ https://t.co/deJBBjoOTV │the old airport carpet is dressed up │ │as a human and named Grand Marshal of │ Josh Susser @joshsus… 19/03/2015 14:06 │a parade. http://t.co/aTCicqSzEI │ @geeksam @jeg2 @avdi POLS issue. No │ │ standard == no way to avoid surprising │Garann Means @garannm 20/03/2015 14:56│ many developers │RT @verge: Robyn launches tech │ │ ↓│ ↓ └──────────────────────────────────────┘
-
11
MAR
2015Curses Windows, Pads, and Panels
In the previous article, I showed how you can accomplish some terminal input and output operations using the
(n)curses(w)
library. What I showed for output were mostly low-level writing routines though. Included incurses
are some higher level abstractions that can be used to manage your program's output. This time, I want to show some of those tools.Windows
The primary abstraction used in
curses
is the concept of awindow
. We actually already used them last time, but we stuck withstdscr
(standard screen) which is just a window that fills the entire terminal. That's more for usingcurses
without thinking much about windows.When you want them though, you can section off the terminal screen into rectangular chunks. Each chunk is a window that you can print output inside of. Here's the most basic example that I could dream up:
require "ffi-ncurses" begin stdscr = FFI::NCurses.initscr FFI::NCurses.cbreak FFI::NCurses.noecho window = FFI::NCurses.newwin(5, 15, 4, 2) # make a new window FFI::NCurses.waddstr(window, "Hello world!") FFI::NCurses.wrefresh(stdscr) # still need this FFI::NCurses.wrefresh(window) FFI::NCurses.getch ensure FFI::NCurses.endwin end
-
28
FEB
2015Basic Curses
In my last article, I showed off some of the various magic commands a Unix terminal recognizes and how you can use those commands to move the cursor around, change the colors of your output, and read input in a character by character fashion. It's not very common to manipulate things manually as I did in those examples. There are libraries that wrap these mechanisms and add abstractions of their own. Using one of them can be easier and less error prone.
Probably the most famous of these higher abstraction libraries is
curses
. However, your version won't be called that.curses
was the original library for System V UNIX. These days you are far more likely to havencurses
which a replacement library that emulates the original. The truth is, you probably don't have exactly that library either. Instead, you may havencursesw
, which is the same library with wide character (read: non-ASCII) support. Also,curses
is often discussed with several add on libraries:panel
,menu
, andform
. Documentation often covers these separate units together. -
30
JAN
2015Random Access Terminal
I've recently been playing around with fancy terminal output in Ruby. I've learned quite a bit about this arcane magic. I've also realized that the documentation is pretty spotty. I want to see if I can improve that with a few blog posts, so let's dive right in.
Output
Program output typically happens in a linear order from top to bottom. For example, this code:
puts "onez" puts "twos" puts "threes"
generates this output:
onez twos threes
But what if you need to change some output? Could you replace the
z
above with ans
if you needed to? Yes, but it can get a little involved.ANSI Escape Codes
In many cases, we just push some characters to
$stdout
(the streamKernel#puts
is writing to above) and your terminal program happily shows them to the user. However, your terminal is watching these characters for special sequences that it understands. Some of those sequences of characters can cause your terminal to take actions other than just writing some output to the screen. -
31
DEC
2014Game Programming Patterns
I meet a lot of programmers that tell me they got started because they wanted to build games. However, when I ask most of them which games they have built, the list rarely includes anything more than mostly unplayable toy projects.
I can only guess at the reasons for this oddity, but I suspect it might be due to the fact that games are fairly complex. Even if you want to rebuild a fairly simple classic like Space Invaders or Snake you need to know at least a little about event loops, keyboard handling, animation, and collision detection. If your day job involves a different kind of programming, like Web application development, odds are good that you don't get a lot of practice with these concepts.
That may not be your story, but it was definitely mine. This year I decided that it was finally time to learn how to build games. I used several sources to gain this knowledge and some helped more than others, but the biggest win by far was a book called Game Programming Patterns by Bob Nystrom.
-
31
OCT
2014How to Avoid Taking a Dart to the Knee
I've been playing with Dart quite a bit lately. I really enjoy the language, but there are always snags that trip up those coming from other backgrounds. Here are the top three issues that have bit me in Dart, in the hopes of saving others some pain:
The Truth and Nothing But the Truth… Literally!
One of the challenges of any language is figuring out what it considers to be truthy in conditional expressions. Each system has its twists, but I find Dart to be extra strict in this case.
Here's some code illustrating the rule:
bool isTruthy(Object condition) { return !!condition; } void main() { var tests = [true, false, null, 42, 0, "", [ ], new Object()]; for (var test in tests) { print("$test is ${isTruthy(test)}"); } }
That outputs:
$ dart truthiness.dart true is true false is false null is false 42 is false 0 is false is false [] is false Instance of 'Object' is false
As you can see the literal
true
(just that one object) is truthy in Dart and everything else is consideredfalse
. I'm in the habit of playing pretty fast and loose with truthiness from all of my time working with Ruby, so this has surprised me a few times. -
24
OCT
2014The Three Tick Sort
Yesterday I showed a newer programmer some code like
scores.sort_by(&:reverse)
. This provoked a comment about how they where going to look upsort_by()
later to figure out what magic is involved here. It made me sad to realize how many cool tricks they weren't going to see in that bit of documentation.Allow me to enumerate those tricks for you, but first let's flesh out an example. Consider this code:
scores = { fifteen: 2, five_card_run: 5, five_card_flush: 5, four_card_run: 4, four_card_flush: 4, his_nobs: 1, pair: 2, three_card_run: 3, } scores.sort_by(&:reverse).each do |name, score| puts "Score #{score} for #{name}." end # >> Score 1 for his_nobs. # >> Score 2 for fifteen. # >> Score 2 for pair. # >> Score 3 for three_card_run. # >> Score 4 for four_card_flush. # >> Score 4 for four_card_run. # >> Score 5 for five_card_flush. # >> Score 5 for five_card_run.
In this case, the magic method call (
scores.sort_by(&:reverse)
) has reordered a list of Cribbage hands first by point value and then alphabetically ("ASCIIabetically" in truth). How this happens is a pretty interesting journey though. -
25
SEP
2014Regex Code Equivalency
#!/usr/bin/env ruby -w Name = "Gray, James" !!(Name =~ /\AGray/) # => true Name.start_with?("Gray") # => true !!(Name =~ /James\z/) # => true Name.end_with?("James") # => true !!(Name =~ /Dana/) # => false Name.include?("Dana") # => false !!(Name =~ /\A\z/) # => false Name.empty? # => false !!(Name =~ /\AGray, James\z/) # => true Name == "Gray, James" # => true !!(Name =~ /\A(?:Gray, James|Gray, Dana)\z/) # => true ["Gray, James", "Gray, Dana"].include?(Name) # => true Name =~ /\A\w+/ && $& # => "Gray" Name[/\A\w+/] # => "Gray" Name =~ /\A(\w+),\s*(\w+)\z/ && $2 # => "James" Name[/\A(\w+),\s*(\w+)\z/, 2] # => "James" Name =~ /\A(?<last>\w+),\s*(?<first>\w+)\z/ && $~[:first] # => "James" Name[/\A(?<last>\w+),\s*(?<first>\w+)\z/, :first] # => "James" Name.scan(/^.*\n?/) # => ["Gray, James"] Name.lines # => ["Gray, James"] Name.scan(/./m) # => ["G", "r", "a", "y", ",", " ", "J", "a", "m", "e", "s"] Name.chars # => ["G", "r", "a", "y", ",", " ", "J", "a", "m", "e", "s"] Name.gsub(/[aeiou]/, "") # => "Gry, Jms" Name.delete("aeiou") # => "Gry, Jms" Name.gsub(/[aeiou]/, "X") # => "GrXy, JXmXs" Name.tr("aeiou", "X") # => "GrXy, JXmXs" # For the destructive operations that follow you can drop the `dup()` and # switch `sub()` to `sub!()`, as long as you don't care about the return value. Name.sub(/(?=,)/, " II") # => "Gray II, James" Name.dup.insert(Name.index(","), " II") # => "Gray II, James" Name.sub(/\A/, "Name: ") # => "Name: Gray, James" Name.dup.prepend("Name: ") # => "Name: Gray, James" Name.sub(/\A.*\z/m, "Gray, Dana") # => "Gray, Dana" Name.dup.replace("Gray, Dana") # => "Gray, Dana" Name.sub(/\A.*\z/m, "") # => "" Name.dup.clear # => "" Spacey = "\tsome space\r\n" Spacey.sub(/\A\s+/, "") # => "some space\r\n" Spacey.lstrip # => "some space\r\n" Spacey.sub(/\s+\z/, "") # => "\tsome space" Spacey.rstrip # => "\tsome space" Spacey.sub(/\A\s*(.+?)\s*\z/m, '\1') # => "some space" Spacey.strip # => "some space" Spacey.sub(/(?:\r?\n|\r)\z/m, "") # => "\tsome space" Spacey.chomp # => "\tsome space" Spacey.sub(/(?:\r\n|.)\z/m, "") # => "\tsome space" Spacey.chop # => "\tsome space" Spacey.gsub(/ +/, " ") # => "\tsome space\r\n" Spacey.squeeze(" ") # => "\tsome space\r\n"
-
22
SEP
2014A Regex Can't Match Balanced Parentheses
Can we do math with regular expressions?
#!/usr/bin/env ruby -w def build_preparation_regex(number_regex, ops) %r{ (?<number> #{number_regex} ){0} (?<operator> [#{ops.map(&Regexp.method(:escape)).join}] ){0} (?<term_operator_term> \g<term> \s* \g<operator> \s* \g<term> ){0} (?<term> \g<number> | \( \s* \g<term_operator_term> \s* \) ){0} \g<term_operator_term>(?=\s*\z|[^)]) }x end NUMBER_REGEX = %r{ -? # an optional minus \d+ # an integer (?: \. \d+)? # an optional fractional bit }x PREPARE_MULT_AND_DIV_REGEX = build_preparation_regex(NUMBER_REGEX, %w[* /]) PREPARE_ADD_AND_SUB_REGEX = build_preparation_regex(NUMBER_REGEX, %w[* / + -]) CHECK_REGEX = %r{ \A # the start of the expression (?<term> # a term, which is: #{NUMBER_REGEX} # a number | # or \( \s* # a parenthesized group of \g<term> # a term \s* [*/+\-] \s* # an operator \g<term> # and another term \s* \) # the end of the parenthesized group ) \z # the end of the expression }x MATH_REGEX = %r{ \( \s* (?<left> #{NUMBER_REGEX} ) \s* (?<operator> [*/+\-] ) \s* (?<right> #{NUMBER_REGEX} ) \s* \) }x verbose = ARGV.delete("-v") problem = ARGV.first.strip or abort "USAGE: #{$PROGRAM_NAME} MATH_EXPRESSION" steps = [ ] [PREPARE_MULT_AND_DIV_REGEX, PREPARE_ADD_AND_SUB_REGEX].each do |preparation| loop do steps << problem.dup if verbose problem.sub!(preparation) { |term| "(#{term})" } or break end end problem =~ CHECK_REGEX or abort "Error: Invalid expression" solution = problem.dup loop do steps << solution.dup if verbose solution.sub!(MATH_REGEX) { $~[:left].to_f.public_send($~[:operator], $~[:right].to_f) } or break end puts steps.uniq[0..-2] if verbose puts solution.sub(/\.0+\z/, "")