Demo

I missed class last week where we practiced reading tarot cards in class so my main experience with tarot has been self-readings at home. (Sometimes, I’ll do self-readings twice or more!) I’m enjoying it so much that as I leave my house and collect my phone, wallet & keys, I ponder if I should also bring my tarot deck.

I find self-readings to be a very pleasant start to the day and for now it’s find a nice spot in my morning routine after my breathing meditations. Each reading serves as a reminder to slow down and internalize a problem, then think through that problem in a calm step-by-step fashion.

I realized this isn’t too different from the practice of rubber-ducking that happens in software development, a practice where one steps away from the problem to explain the situation to an imaginary duck. Except, rubber-ducking doesn’t go as far as tarot… until now.

I thought it would be nice to get a tarot reading when stuck in the middle of a coding bug, as a fun way to step away from the problem and to begin understanding the problem in a different way.

Design

I chose to start with a 3-card spread, mainly because it seems like the minimum number of cards needed for a meaningful tarot reading. With the scenario of “escape from your coding bugs” in mind, I chose to do the reading in the form of a 3-card spread representing (i) situation, (ii) action, and (iii) outcome.

While I love the standard tarot deck, I was interested in what might be more meaningful in my programming context. A moment from last week came into mind, where I accidentally deleted my iCloud Drive containing all my thesis work to date… rm -rf icloud-drive. (Luckily, I had a backup.) It got me thinking that rm can be shell’s digital analog to Death. Building on that, I found most of the standard shell commands can be interpreted in interesting ways, each personified similarly to programs-as-humans in the Matrix.

Lot’s of ruby headaches later, Oracle in the Shell was born.

Implementation

oracle-in-the-shell.rb

#!/usr/bin/env ruby

require 'pry'

def man_summary(cmd)
  summaries = `man #{cmd} | col -bx`.split("\n")
  description_label = "DESCRIPTION"
  description_found = false
  summary = ""

  summaries.each do |line|
    # end after we've parsed the paragraph after DESCRIPTION
    break if description_found && line == ""

    if line == description_label
      description_found = true
      next
    end

    summary += line.strip if description_found
  end

  print summary
end

def oracle_print(arr)
  while (arr.size < 3) do arr.push(nil) end

  3.times { print '*'*20 + "\t" }
  puts "\n"

  arr.each do |cmd|
    print (cmd.nil? ? '*'*20 : '*' + ' '*18 + '*')
    print "\t"
  end
  puts "\n"

  arr.each do |cmd|
    if cmd.nil?
      print '*'*20
    else
      spacing = ((18 - cmd.size) / 2).floor
      print '*'
      print ' ' * spacing
      print cmd
      print ' ' * (spacing + (cmd.size.odd? ? 1 : 0))
      print '*'
    end

    print "\t"
  end
  puts "\n"

  arr.each do |cmd|
    print (cmd.nil? ? '*'*20 : '*' + ' '*18 + '*')
    print "\t"
  end
  puts "\n"

  3.times { print '*'*20 + "\t" }
  puts "\n"
  puts "\n"
end

def frame(game_state, programs, cards)
  system "clear" or system "cls"

  if game_state == :ponder
    p "That's all!"
    response = gets.chomp
  end

  if game_state == :outcome
    cards[2] = programs.shuffle.pop
    oracle_print(cards)

    p 'This is your outcome...'

    puts "\n"
    puts "\n"
    p man_summary(cards[2])
    puts "\n"
    puts "\n"
    puts "\n"

    p 'Would you like to proceed? (y/?)'
    response = gets.chomp

    game_state = :ponder if response == 'y'
  end

  if game_state == :action
    cards[1] = programs.shuffle.pop
    oracle_print(cards)

    p 'This card is how I would advise you act...'

    puts "\n"
    puts "\n"
    p man_summary(cards[1])
    puts "\n"
    puts "\n"
    puts "\n"

    p 'Would you like to proceed? (y/?)'
    response = gets.chomp

    game_state = :outcome if response == 'y'
  end

  if game_state == :situation
    cards[0] = programs.shuffle.pop
    oracle_print(cards)

    p 'This first card represents your current situation'

    puts "\n"
    puts "\n"
    p man_summary(cards[0])
    puts "\n"
    puts "\n"
    puts "\n"

    p 'Would you like to proceed? (y/?)'
    response = gets.chomp

    game_state = :action if response == 'y'
  end

  if game_state == :query
    p 'Take a moment. Take a breath. Internalize your query...'
    sleep 1
    p 'Would you like to proceed? (y/?)'

    response = gets.chomp

    if response == '?'
      p 'Save your questions for later'
      p 'Would you like to proceed? (y/?)'

      response = gets.chomp
    end

    game_state = :situation if response == 'y'
  end

  frame(game_state, programs, cards)
end

# List shell programs and remove the non-char ones
all_programs = `ls /bin`
all_programs = all_programs.split.select { |program| program =~ /[a-z].*/ }

cards = [nil, nil, nil]
frame(:query, all_programs, cards)

Takeaways

It seemed natural to show the descriptions from each shell command’s man page, mostly because I don’t know what some of the shell commands do. However, I find the descriptions technically dry and not that interesting for interpretations as I hoped. If I revisit this, I’d like to write my own interpretations of each shell command.