carolineartz
4/10/2014 - 9:26 PM

README.md

--- 
:@next: 
- Take the dog for a walk
:@work: 
- Pay lease bill
:@home: 
- Buy Duck Typing from RubyRags
- Buy Ruby Nerd from RubyRags
require 'minitest/autorun'
require 'minitest/pride'
require 'awesome_print'
require File.join(File.dirname(__FILE__), 'todo.rb')

describe Item do

  before do
    @item = Item.new('New todo item @home')
  end

  it 'assigns a text value for the todo' do
    @item.text.must_equal 'New todo item'
  end

  it 'assigns a context from the todo value' do
    @item.context.must_equal '@home'
  end

end

describe Todo do

  before do
    @todo = Todo.new
    @todo.clear!
    @todo.add 'Take the dog for a walk'
    @todo.add 'Pay lease bill @work'
    @todo.add 'Buy Duck Typing from RubyRags @home'
    @todo.add 'Buy Ruby Nerd from RubyRags @home'
  end

  it "finds the file path of the todo list" do
    @todo.file.must_equal File.expand_path('.todos')
  end

  it "adds the todo to the stack" do
    @todo.items.size.must_equal 4
  end

  it "creates a hash of attributes from the todo items" do
    @todo.to_hash.must_equal({
        :@next => ["Take the dog for a walk"],
        :@work => ["Pay lease bill"],
        :@home => ["Buy Duck Typing from RubyRags", "Buy Ruby Nerd from RubyRags"]
      })
  end

  it 'deletes a todo' do
    @todo.delete(2).text.must_equal "Pay lease bill"
    @todo.items.size.must_equal 3
  end

  it 'completes a todo' do
    @todo.done(2).context.must_equal "@done"
  end

end
#!/usr/bin/env ruby
require 'yaml'

# Represents a single todo item. An Item contains just a text and
# a context.
#
class Item
  attr_accessor :context, :text

  # Creates a new Item instance in-memory.
  #
  # value - The text of the Todo. Context is extracted if exists.
  #
  # Returns the unpersisted Item instance.
  def initialize(value)
    @context = value.scan(/@[A-Z0-9.-]+/i).last || '@next'
    @text    = value.gsub(context, '').strip
  end

  # Overide: Quick and simple way to print Items
  #
  # Returns String for this Item
  def to_s
    "#{@text}: #{@context}"
  end
end

# The Todo contains many Items. They exist as buckets in which to categorize
# individual Items. The relationship is maintained in a simple array.
#
class Todo

  # Creates a new Todo instance in-memory.
  #
  # Returns the persisted Todo instance.
  def initialize(options = {})
    @options, @items = options, []
    bootstrap
    load_items
  end

  # The main todos in the user's home directory
  FILE = File.expand_path('.todos')

  # Allow to items to be accessible from the outside
  attr_accessor :items

  # Creates a new todo
  #
  # Example:
  #   @todo.add('lorem epsim etc @work')
  #
  # Returns the add todo Item
  def add(todo)
    @items << Item.new(todo)
    save
    @items.last
  end

  # Removes the todo
  #
  # Example:
  #   @todo.delete(1)
  #
  # Returns the deleted todo Item
  def delete(index)
    todo = @items.delete_at(index.to_i-1)
    save
    todo
  end

  # Marks a todo as done
  #
  # Example:
  #   @todo.done(1)
  #
  # Returns the done todo Item
  def done(index)
    item = @items[index.to_i-1]
    item.context = '@done'
    save
    item
  end

  # Prints all the active todos in a nice neat format
  #
  # Examples:
  #   @todo.list @work
  #
  # Returns nothing
  def list
    longest = @items.map(&:text).max_by(&:length) || 0
    @items.each_with_index do |todo, index|
      printf "%s: %-#{longest.size+5}s %s\n", index+1, todo.text, todo.context
    end
  end

  # Moves a todo up or down in priority
  #
  # Example:
  #   @todo.bump(2, +1)
  #
  def bump(index, position = 1)
    @items.insert(position-1, @items.delete_at(index.to_i-1))
    save
    @items[position.to_i-1]
  end

  # Accessor for the todo list file
  #
  # Returns String file path
  def file
    @file ||= File.exist?(FILE) ? FILE : "#{ENV['HOME']}/.todos"
  end

  # Formats the current set of todos
  #
  # Returns a lovely hash
  def to_hash
    @items.group_by(&:context).inject({}) do |h,(k,v)|
      h[k.to_sym] = v.map(&:text); h
    end
  end

  # Loads the yaml todos file and creates a hash
  #
  # Returns the items loaded from the file
  def load_items
    YAML.load_file(file).each do |key, texts|
      texts.each do |text|
        if key.to_s == @options[:filter] || @options[:filter].nil?
          @items << Item.new("#{text} #{key}") if key.to_s != '@done'
        end
      end
    end
    @items
  end

  # Implodes all the todo items save an empty file
  #
  # Returns nothing
  def clear!
    @items.clear
    save
  end

  private

  # Saves the current list of todos to disk
  #
  # Returns nothing
  def save
    File.open(file, "w") {|f| f.write(to_hash.to_yaml) }
  end

  # Creates a new todo file if none is present
  #
  # Returns nothing
  def bootstrap
    return if File.exist?(file)
    save
  end

end

if __FILE__ == $0
  case ARGV[0]
  when 'list','ls'
    Todo.new(:filter => ARGV[1]).list
  when 'add','a'
    puts "Added: #{Todo.new.add(ARGV[1..-1].join(' '))}"
  when 'delete', 'del', 'd'
    puts "Deleted: #{Todo.new.delete(ARGV[1])}"
  when 'done'
    puts "Done: #{Todo.new.done(ARGV[1])}"
  when 'edit'
    system("`echo $EDITOR` #{Todo.new.file} &")
  when 'clear'
    puts "All #{Todo.new.clear!} todos cleared! #{Todo.new.clear!}"
  when 'bump'
    puts "Bump: #{Todo.new.bump(ARGV[1])}"
    Todo.new.list
  else
    puts "\nUsage: todo [options] COMMAND\n\n"
    puts "Commands:"
    puts "  add TODO        Adds a todo"
    puts "  delete NUM      Removes a todo"
    puts "  done NUM        Completes a todo"
    puts "  list [CONTEXT]  Lists all active todos"
    puts "  bump NUM        Bumps priority of a todo"
    puts "  edit            Opens todo file"
  end
end

Todo.rb

Todo.rb is a simple command-line tool for managing todos. It's minimal, straightforward, and you can use it with your favorite text editor.

Getting Started

Todo.rb doesn't require any third-party gems so you can copy the file anywhere and use it as long as it's executable:

$ chmod +x todo.rb

You may also create an alias to save keystrokes

alias todo='~/todo.rb'

Todo.rb can work with multiple todo files too. You can maintain one todo list (default: '~/.todos') for project-specific todo files.

Create a todo

We don't have to use quotes :-)

$ todo add Check out rubyrags.com
Add: (1) Check out rubyrags.com

# Create a todo with context
$ todo add Buy Duck Typing shirt from rubyrags.com @work
Add: (2) Buy Duck Typing shirt from rubyrags.com @work

$ todo add Buy Ruby Nerd shirt from rubyrags.com
Add: (3) Buy Ruby Nerd shirt from rubyrags.com

List todos

Prints the todos is a nice, tabbed format.

$ todo list
1. Check out rubyrags.com
2: Buy Ruby Nerd shirt from rubyrags.com       @work
3: Buy Duck Typing shirt from rubyrags.com     @work

$ todo list @work
1: Buy Ruby Nerd shirt from rubyrags.com       @work
2: Buy Duck Typing shirt from rubyrags.com     @work

Deleting todos

Use the todo number to delete it. del also works.

$ todo delete 1
Deleted: (1) Check out rubyrags.com

# Todo delete all the todos:
$ todo clear
All 3 todos cleared!

Completing todos

Use the todo number to complete the todo. This will simple archive the todo

$ todo done 2
Done: (2) Buy Duck Typing shirt from rubyrags.com    @work

Prioritizing todos

To bump a todo higher on the list:

$todo bump 2
Bump: (2) Buy Duck Typing shirt from rubyrags.com    @work

Help

Help is just a command away.

$ todo help

Manually Edit

If you want to edit the underlying todo file directly, make sure your $EDITOR environment variable is set, and run:

$ todo edit

Then you can see your todo list in a beautifully formated yaml file!

Ohai, Command Line!

Since it's the command line we have all the goodies available to use

$ todo list | grep Nerd
2: Buy Ruby Nerd shirt from rubyrags.com   @work