From 7fd0a464821aa9ad9ef0d5a16d0bedecdf83c542 Mon Sep 17 00:00:00 2001 From: Katharina Fey Date: Sun, 4 Aug 2019 02:21:22 +0200 Subject: First functional version Has a few problems: - Doesn't properly indent entries - Only processes _first_ open account - Doesn't catch enough errors - CLI parsing sucks --- Gemfile | 3 +++ cass.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ parser.rb | 53 ++++++++++++++++++++++++----------------------------- start.rb | 25 +++++++++++++++++++++++++ stop.rb | 32 ++++++++++++++++++++++++++++++++ time_util.rb | 7 +++++++ 6 files changed, 149 insertions(+), 29 deletions(-) create mode 100644 Gemfile create mode 100755 cass.rb mode change 100755 => 100644 parser.rb create mode 100644 start.rb create mode 100644 stop.rb create mode 100644 time_util.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..f007eb2 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "parslet" diff --git a/cass.rb b/cass.rb new file mode 100755 index 0000000..7c8abbf --- /dev/null +++ b/cass.rb @@ -0,0 +1,58 @@ +#!/usr/bin/env ruby + +require "optparse" +require "pp" + +require_relative "parser" +require_relative "time_util" + +op = OptionParser.new +op.on("start ACCOUNT", String) do |acc| + puts acc +end + +options = {} +OptionParser.new do |opts| + opts.banner = "Usage: cassiopeia " + + opts.on("-f FILE", "--file=FILE", "Provide the working file") do |f| + options[:file] = f + end + + opts.on("--start=ACCOUNT", "Start tracking time") do |acc| + options[:start] = acc + end + + opts.on("--stop=ACCOUNT", "Stop working!") do |acc| + options[:stop] = acc + end + + opts.on("-r", "--round", "Round to the next 15 minutes") do |r| + options[:round] = r + end + + opts.on("-v", "--version", "Print program version") do |v| + puts "0.1.0" + end +end.parse! + +path = options[:file] +start = options[:start] +stop = options[:stop] +round = !!options[:round] + +file = File.open(path, "r") { |f| CassParser.new.parse(f.read) } + +if start + require_relative "start" + new = start(start, round, file) + + File.open(path, "a") { |f| f.write(new) } +elsif stop + require_relative "stop" + line, new = stop(stop, round, file) + + lines = File.open(path, "r") { |f| f.readlines } + lines[line - 1] = new + File.open(path, "w") { |f| f.write(lines.join) } +end diff --git a/parser.rb b/parser.rb old mode 100755 new mode 100644 index 4e261ba..08cc50c --- a/parser.rb +++ b/parser.rb @@ -5,23 +5,23 @@ require "pp" require "parslet" include Parslet -class EntryParser < Parslet::Parser +class CassParser < Parslet::Parser root :doc ### Type segments - rule(:hour) { hour_.as(:hour) } - rule(:minute) { minute_.as(:minute) } - rule(:second) { second_.as(:second) } + # rule(:hour) { hour_.as(:hour) } + # rule(:minute) { minute_.as(:minute) } + # rule(:second) { second_.as(:second) } - rule(:year) { year_.as(:year) } - rule(:month) { month_.as(:month) } - rule(:day) { day_.as(:day) } + # rule(:year) { year_.as(:year) } + # rule(:month) { month_.as(:month) } + # rule(:day) { day_.as(:day) } - rule(:time) { time_.as(:time) } - rule(:local) { local_.as(:local) } - rule(:offset) { offset_.as(:offset) } + # rule(:time) { time_.as(:time) } + # rule(:local) { local_.as(:local) } + # rule(:offset) { offset_.as(:offset) } - rule(:date) { date_.as(:date) } + # rule(:date) { date_.as(:date) } rule(:datetime) { date >> str(" ") >> time } rule(:daterange) { start >> str(" -") >> stop } @@ -32,21 +32,21 @@ class EntryParser < Parslet::Parser rule(:doc) { line_.repeat } ### Combinatory segments - rule(:hour_) { digit.repeat(2, 2) } - rule(:minute_) { digit.repeat(2, 2) } - rule(:second_) { digit.repeat(2, 2) } + rule(:hour) { digit.repeat(2, 2) } + rule(:minute) { digit.repeat(2, 2) } + rule(:second) { digit.repeat(2, 2) } - rule(:time_) { local >> offset } - rule(:date_) { year >> str("-") >> month >> str("-") >> day } + rule(:time) { local >> offset } + rule(:date) { year >> str("-") >> month >> str("-") >> day } - rule(:year_) { digit.repeat(4, 4) } - rule(:month_) { digit.repeat(2, 2) } - rule(:day_) { digit.repeat(2, 2) } - - rule(:local_) { hour >> str(":") >> minute >> str(":") >> second } - rule(:offset_) { match("[zZ]") | tnoffset_ } - rule(:poffset_) { str(" ") >> match("[+\-]") } - rule(:tnoffset_) { poffset_ >> hour >> str(":").maybe >> minute } + rule(:year) { digit.repeat(4, 4) } + rule(:month) { digit.repeat(2, 2) } + rule(:day) { digit.repeat(2, 2) } + + rule(:local) { hour >> str(":") >> minute >> str(":") >> second } + rule(:offset) { match("[zZ]") | tnoffset } + rule(:poffset) { str(" ") >> match("[+\-]") } + rule(:tnoffset) { poffset >> hour >> str(":").maybe >> minute } rule(:title_) { text >> str(":") } @@ -61,11 +61,6 @@ class EntryParser < Parslet::Parser rule(:newline) { spaces.maybe >> str("\n") } end -PP.pp EntryParser.new.parse(" -# This is a comment -Bobs Burger: 2019-07-21 21:00:00 +0200 - -") - __END__ # Following is an example of what the `timebox` file format looks like diff --git a/start.rb b/start.rb new file mode 100644 index 0000000..3ec3c48 --- /dev/null +++ b/start.rb @@ -0,0 +1,25 @@ +""" +This file is part of cassiopeia and licensed +under the GNU Public License 3.0 (or later) + +cassiopeia-start - start working on an account + +This file is included and run from cass(1) +""" + +require "time" +require_relative "time_util" + +def start(account, round, file = []) + rel = file.select { |i| i[:title].to_s.include? account } + + # This is a kind of fuzzy find + account = rel[0][:title].to_s[0..-2] + + # Check if there's any ling jobs + jobs = rel.select { |i| i[:stop] == nil }.length + STDERR.puts \ + "[Warn]: #{jobs} dangling jobs for account #{account}" if jobs > 0 + + "#{account}: #{round ? Time.now.round : Time.now} - " +end diff --git a/stop.rb b/stop.rb new file mode 100644 index 0000000..fff5744 --- /dev/null +++ b/stop.rb @@ -0,0 +1,32 @@ +""" +This file is part of cassiopeia and licensed +under the GNU Public License 3.0 (or later) + +cassiopeia-stop - stop working on an account + +This file is included and run from cass(1) +""" + +require "time" +require_relative "time_util" + +def stop(account, round, file = []) + rel = file.select { |i| i[:title].to_s.include? account } + + # This is a kind of fuzzy find + account = rel[0][:title].to_s[0..-2] + + jobs = rel.select { |i| i[:stop] == nil } + if jobs.length == 0 + STDERR.puts "No unfinished accounts for '#{account}'!" + exit 2 + end + + STDERR.puts "[Warn]: More than one dangling account!" unless jobs.length == 1 + job = jobs[0] + line, _ = job[:title].line_and_column + + # We return the line number too, so that we can inset correctly + return line, "#{account}:\ + #{job[:start]} - #{round ? Time.now.round : Time.now}" +end diff --git a/time_util.rb b/time_util.rb new file mode 100644 index 0000000..410b5cd --- /dev/null +++ b/time_util.rb @@ -0,0 +1,7 @@ +class Time + def round(minutes = 15) + minutes *= 60 + Time.at((self.to_f / minutes).round * minutes) + end +end + -- cgit v1.2.3