module Timeless::CommandSemantics

Constants

APPNAME
VERSION

Public Class Methods

balance(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 245
def self.balance opts, argv
  from = opts.to_hash[:from] ? Chronic.parse(opts.to_hash[:from]) : nil
  to = opts.to_hash[:to] ? Chronic.parse(opts.to_hash[:to]) : nil

  # {key1 => {value1 => total .. } , ... }
  hash = Timeless::Storage::balance from, to, opts.to_hash[:filter]

  report "Projects", "p", hash
  report "Clients", "c", hash
  report "Activities", "a", hash

  remaining_tags = hash.reject! { |k| ["p", "c", "a"].include? k }
  remaining_tags.keys.each do |k|
    report k, k, remaining_tags
  end
end
clock(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 135
def self.clock opts, argv
  start = Chronic.parse(opts.to_hash[:start])   # nil if no option specified
  stop_opt = Chronic.parse(opts.to_hash[:stop]) # nil if no option specified
  end_opt  = Chronic.parse(opts.to_hash[:end])  # nil if no option specified
  stop = stop_opt ? stop_opt : end_opt          # stop_opt if --stop, end_opt if --end, nil otherwise
  notes = argv.join(" ")                        # empty string if no args specified

  if stop_opt and end_opt then
    puts "Timeless error: specify end time with either --end or --stop (not both)"
    exit
  end

  manage_stop start, stop, notes, opts[:last]
end
console(opts, argv = []) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 42
def self.console opts, argv = []
  all_commands = CommandSyntax.commands
  all_commands.delete(:console)
  
  i = 0
  while true
    string = Readline.readline("#{APPNAME}:%03d> " % i, true)
    string.gsub!(/^#{APPNAME} /, "") # as a courtesy, remove any leading appname string
    if string == "exit" or string == "quit" or string == "." then
      exit 0
    end
    reps all_commands, string.split(' ')
    i = i + 1
  end
end
current(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 150
def self.current opts, argv
  if Timeless::Stopwatch.clocking? then
    start, notes = Timeless::Stopwatch.get_start
    puts "Timeless: you have been clocking #{"%.0d" % ((Time.now- Time.parse(start)) / 60)} minutes"
    puts "on: #{notes}" if notes
  else
    puts "There is no clock started."
  end
end
export(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 172
def self.export opts, argv
  Timeless::Storage.export
end
forget(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 117
def self.forget opts, argv
  if File.exists?(Timeless::Stopwatch::START_FILENAME)
    entry = Timeless::Storage.last
    Timeless::Stopwatch.forget
    puts "Forgotten timer started at #{entry[0]} (#{entry[2]})."
  else
    puts "You don't have an active timer"
  end
end
gaps(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 176
def self.gaps opts, argv
  from = opts.to_hash[:from] ? Chronic.parse(opts.to_hash[:from]) : nil
  to = opts.to_hash[:to] ? Chronic.parse(opts.to_hash[:to]) : nil
  interval = opts.to_hash[:interval] || 1
    
  entries = Timeless::Storage.get(from, to)
  previous_stop = from || Time.parse(entries[0][0])
  day = ""

  printf "%-18s %-5s - %-5s   %-6s  %-s\n", "Day", "Start", "End", "Gap", "Command to fix"
  entries.sort { |x,y| Time.parse(x[0]) <=> Time.parse(y[0]) }.each do |entry|
    current_start = Time.parse(entry[0])
    
    current_day = current_start.strftime("%a %b %d, %Y")

    if (current_start.day == previous_stop.day and current_start - previous_stop > interval * 60) then

      if current_day != day then
        day = current_day
      else
        current_day = "" # avoid printing the day if same as before
      end

      duration = (current_start - previous_stop) / 60
      printf "%-18s %5s - %5s   %02i:%02i   %s\n",
             current_day,
             previous_stop.strftime("%H:%M"),
             current_start.strftime("%H:%M"),
             duration / 60, duration % 60, 
             "timeless clock --start '#{previous_stop}' --end '#{current_start}'"

    end
    previous_stop = Time.parse(entry[1])
  end
end
help(opts = nil, argv = []) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 26
def self.help opts = nil, argv = []
  all_commands = CommandSyntax.commands
  
  if argv != []
    argv.map { |x| puts all_commands[x.to_sym][2] }
  else
    puts "#{APPNAME} command [options] [args]"
    puts ""
    puts "Available commands:"
    puts ""
    all_commands.keys.each do |key|
      puts "  " + all_commands[key][0].banner
    end
  end
end
last(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 160
def self.last opts, argv
  puts last_entry
end
list(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 164
def self.list opts, argv
  values = Timeless::Storage.get_key argv[0]
  puts "Timeless: list of keys #{argv[0]} found in timesheets:"
  values.each do |value|
    puts value
  end
end
man(opts = nil, argv = []) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 19
def self.man opts = nil, argv = []
  path = File.join(File.dirname(__FILE__), "/../../../README.org")
  file = File.open(path, "r")
  contents = file.read
  puts contents
end
pom(opts, argv) click to toggle source

APP SPECIFIC COMMANDS

# File lib/timeless/cli/command_semantics.rb, line 92
def self.pom opts, argv
  duration = opts.to_hash[:duration] ||
             (opts.to_hash[:long] ? Timeless::Pomodoro::WORKING_LONG : Timeless::Pomodoro::WORKING)

  start, stop, notes = Timeless::Pomodoro.run_pomodoro_timer(duration, argv.join(" "))
  Timeless::Storage.store(start, stop, notes)
  puts last_entry 
end
reps(all_commands, argv) click to toggle source

read-eval-print step

# File lib/timeless/cli/command_semantics.rb, line 59
def self.reps all_commands, argv
  if argv == [] or argv[0] == "--help" or argv[0] == "-h"
    CommandSemantics.help
    exit 0
  else
    command = argv[0]
    syntax_and_semantics = all_commands[command.to_sym]
    if syntax_and_semantics
      opts = syntax_and_semantics[0]
      function = syntax_and_semantics[1]
      
      begin
        parser = Slop::Parser.new(opts)

        result = parser.parse(argv[1..-1])
        options = result.to_hash
        arguments = result.arguments

        eval "CommandSemantics::#{function}(options, arguments)"
      rescue Slop::Error => e
        puts "#{APPNAME}: #{e}"
      rescue Exception => e
        puts e
      end
    else
      puts "#{APPNAME}: '#{command}' is not a valid command. See '#{APPNAME} help'"
    end
  end
end
start(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 102
def self.start opts, argv
  start = Chronic.parse(opts.to_hash[:at]) # nil if no option specified
  notes = argv.join(" ")                   # empty string is no args specified
  force = opts.to_hash[:force]

  if Timeless::Stopwatch.clocking? and not force 
    start, notes = Timeless::Stopwatch.get_start # for information purposes only
    puts "There is a clock started at #{start} (notes: \"#{notes}\").  Use --force to override."
  else
    Timeless::Stopwatch.start(start, notes)
    puts "Clock started at #{start ? start : Time.now}."
    puts "You may want to specify notes for the entry when you stop clocking." if notes == ""
  end
end
statement(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 213
def self.statement opts, argv
  from = opts.to_hash[:from] ? Chronic.parse(opts.to_hash[:from]) : nil
  to = opts.to_hash[:to] ? Chronic.parse(opts.to_hash[:to]) : nil

  entries = Timeless::Storage.get(from, to, opts.to_hash[:filter])

  # it could become a function of a reporting module
  printf "%-18s %-5s - %-5s %-8s  %-s\n", "Day", "Start", "End", "Duration", "Notes"
  total = 0
  day = ""
  entries.sort { |x,y| Time.parse(x[0]) <=> Time.parse(y[0]) }.each do |entry|
    current_day = Time.parse(entry[0]).strftime("%a %b %d, %Y")
    if current_day != day then
      day = current_day
    else
      current_day = "" # avoid printing the day if same as before
    end
    
    duration = (Time.parse(entry[1]) - Time.parse(entry[0])) / 60
    total = total + duration

    printf "%-18s %5s - %5s   %5s   %s\n",
           current_day,
           Time.parse(entry[0]).strftime("%H:%M"),
           Time.parse(entry[1]).strftime("%H:%M"),
           in_hours(duration),
           entry[2]
  end
  puts "----------------------------------------------------------------------"
  printf "Total                              %02i:%02i\n", total / 60, total % 60
end
stop(opts, argv) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 127
def self.stop opts, argv
  start = Chronic.parse(opts.to_hash[:start])  # nil if no option specified
  stop  = Chronic.parse(opts.to_hash[:at])     # nil if no option specified
  notes = argv.join(" ")                       # empty string if no args specified

  manage_stop start, stop, notes, opts[:last]
end
version(opts = nil, argv = []) click to toggle source

Main App Starts Here!

# File lib/timeless/cli/command_semantics.rb, line 15
def self.version opts = nil, argv = []
  puts "#{APPNAME} version #{VERSION}"
end

Private Class Methods

backup(filename) click to toggle source

GENERAL UTILITIES

# File lib/timeless/cli/command_semantics.rb, line 332
def self.backup filename
  FileUtils::cp filename, filename + "~"
  puts "Backup copy #{filename} created in #{filename}~."
end
backup_and_write(filename, content) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 337
def self.backup_and_write filename, content
  backup(filename) if File.exist?(filename)
  File.open(filename, "w") { |f| f.puts content }
end
in_hours(value) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 277
def self.in_hours value
  sprintf "%02i:%02i", value / 60, value % 60
end
last_entry() click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 281
def self.last_entry
  start, stop, notes = Timeless::Storage.last

  interval = Time.parse(stop) - Time.parse(start)
  seconds = interval % 60
  minutes = (interval / 60) % 60
  hours = interval / 3600
  
  sprintf "Timeless: you clocked %02d:%02d:%02d on %s\nYou stopped clocking at: %s", hours, minutes, seconds, notes, stop
end
manage_stop(start, stop, notes, reuse_last) click to toggle source

code shared by stop and clock (which accept slightly different options, but behave in the same way

# File lib/timeless/cli/command_semantics.rb, line 296
def self.manage_stop start, stop, notes, reuse_last
  # syntax check:
  # - --start is illegal if there is a running clock
  # - --last is illegal if there are notes
  #
  # however:
  # - --last and notes prevail over notes stored when starting the clock
  if Timeless::Stopwatch.clocking? and start
    puts "Timeless error: you specified --start with a running clock.  Use 'timeless forget' or drop --start"
  end
  if reuse_last and notes != "" then
    puts "Timeless error: you specified both --last and notes.  Choose one or the other"
  end

  if reuse_last then
    _, _, notes = Timeless::Storage.last
  end
  
  if Timeless::Stopwatch.clocking?
    start, stop, notes = Timeless::Stopwatch.stop(start, stop, notes) # merge passed with data in running clock
    Timeless::Storage.store(start, stop, notes)

    puts "Clock stopped at #{stop}."
    puts last_entry
  else
    start = start ? start : Timeless::Storage.last[1]
    stop = stop ? stop : Time.now
    Timeless::Storage.store(start, stop, notes)

    puts "Clock stopped at #{stop}."
    puts last_entry
  end
end
report(title, key, hash) click to toggle source
# File lib/timeless/cli/command_semantics.rb, line 264
def self.report title, key, hash
  total = 0
  puts ""
  puts title
  puts "=" * title.size
  (hash[key] || []).each do |k, v|
    printf "%-20s%5s\n", k, in_hours(v)
    total += v
  end
  puts "-------------------------"
  printf "%-20s%5s\n", "Total", in_hours(total)
end