summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/postrunner.rb1
-rw-r--r--lib/postrunner/ActivitiesDB.rb57
-rw-r--r--lib/postrunner/Activity.rb38
-rw-r--r--lib/postrunner/FlexiTable.rb81
-rw-r--r--lib/postrunner/Main.rb94
-rw-r--r--lib/postrunner/RuntimeConfig.rb20
-rw-r--r--test/.PostRunner_spec.rb.swpbin0 -> 12288 bytes
-rw-r--r--test/FlexiTable_spec.rb14
-rw-r--r--test/PostRunner_spec.rb69
9 files changed, 281 insertions, 93 deletions
diff --git a/lib/postrunner.rb b/lib/postrunner.rb
index 681c583..6d241e6 100644
--- a/lib/postrunner.rb
+++ b/lib/postrunner.rb
@@ -1,7 +1,6 @@
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', 'fit4ruby', 'lib'))
$:.unshift(File.dirname(__FILE__))
-require 'postrunner/version'
require 'postrunner/Main'
module PostRunner
diff --git a/lib/postrunner/ActivitiesDB.rb b/lib/postrunner/ActivitiesDB.rb
index 74314c1..2bd7093 100644
--- a/lib/postrunner/ActivitiesDB.rb
+++ b/lib/postrunner/ActivitiesDB.rb
@@ -11,6 +11,8 @@ module PostRunner
include Fit4Ruby::Converters
+ attr_reader :db_dir, :fit_dir
+
def initialize(db_dir)
@db_dir = db_dir
@fit_dir = File.join(@db_dir, 'fit')
@@ -51,7 +53,7 @@ module PostRunner
begin
fit_activity = Fit4Ruby.read(fit_file)
rescue Fit4Ruby::Error
- Log.error "Cannot read #{fit_file}: #{$!}"
+ Log.error $!
return false
end
@@ -61,7 +63,7 @@ module PostRunner
Log.fatal "Cannot copy #{fit_file} into #{@fit_dir}: #{$!}"
end
- @activities << Activity.new(base_fit_file, fit_activity)
+ @activities << Activity.new(self, base_fit_file, fit_activity)
@activities.sort! do |a1, a2|
a2.start_time <=> a1.start_time
end
@@ -71,22 +73,41 @@ module PostRunner
true
end
- def delete(fit_file)
- base_fit_file = File.basename(fit_file)
- index = @activities.index { |a| a.fit_file == base_fit_file }
- @activities.delete_at(index)
+ def delete(activity)
+ @activities.delete(activity)
sync
end
- def rename(fit_file, name)
- base_fit_file = File.basename(fit_file)
- @activities.each do |a|
- if a.fit_file == base_fit_file
- a.name = name
- sync
- break
+ def check
+ @activities.each { |a| a.check }
+ end
+
+ def find(query)
+ case query
+ when /\A-?\d+$\z/
+ index = query.to_i
+ # The UI counts the activities from 1 to N. Ruby counts from 0 -
+ # (N-1).
+ index -= 1 if index > 0
+ if (a = @activities[index])
+ return [ a ]
end
+ when /\A-?\d+--?\d+\z/
+ idxs = query.match(/(?<sidx>-?\d+)-(?<eidx>-?[0-9]+)/)
+ sidx = idxs['sidx'].to_i
+ eidx = idxs['eidx'].to_i
+ # The UI counts the activities from 1 to N. Ruby counts from 0 -
+ # (N-1).
+ sidx -= 1 if sidx > 0
+ eidx -= 1 if eidx > 0
+ unless (as = @activities[sidx..eidx]).empty?
+ return as
+ end
+ else
+ Log.error "Invalid activity query: #{query}"
end
+
+ []
end
def map_to_files(query)
@@ -125,7 +146,15 @@ module PostRunner
i = 0
t = FlexiTable.new
t.head
- t.row(%w( Ref. Activity Start Distance Duration Pace ))
+ t.row(%w( Ref. Activity Start Distance Duration Pace ),
+ { :halign => :left })
+ t.set_column_attributes([
+ { :halign => :right },
+ {}, {},
+ { :halign => :right },
+ { :halign => :right },
+ { :halign => :right }
+ ])
t.body
@activities.each do |a|
t.row([
diff --git a/lib/postrunner/Activity.rb b/lib/postrunner/Activity.rb
index 964a702..838ac8e 100644
--- a/lib/postrunner/Activity.rb
+++ b/lib/postrunner/Activity.rb
@@ -1,18 +1,17 @@
require 'fit4ruby'
-require 'postrunner/RuntimeConfig'
module PostRunner
class Activity
- attr_reader :fit_file
- attr_accessor :name
+ attr_reader :fit_file, :name
# This is a list of variables that provide data from the fit file. To
# speed up access to it, we cache the data in the activity database.
@@CachedVariables = %w( start_time distance duration avg_speed )
- def initialize(fit_file, fit_activity, name = nil)
+ def initialize(db, fit_file, fit_activity, name = nil)
+ @db = db
@fit_file = fit_file
@fit_activity = fit_activity
@name = name || fit_file
@@ -24,6 +23,11 @@ module PostRunner
end
end
+ def check
+ load_fit_file
+ Log.info "FIT file #{@fit_file} is OK"
+ end
+
def yaml_initialize(tag, value)
# Create attr_readers for cached variables.
@@CachedVariables.each { |v| self.class.send(:attr_reader, v.to_sym) }
@@ -47,15 +51,29 @@ module PostRunner
end
end
- def method_missing(method_name, *args, &block)
- fit_file = File.join(Config['fit_dir'], @fit_file)
+ #def method_missing(method_name, *args, &block)
+ # @fit_activity = load_fit_file unless @fit_activity
+ # @fit_activity.send(method_name, *args, &block)
+ #end
+
+ def summary(fit_file)
+ load_fit_file
+
+ end
+
+ def rename(name)
+ @name = name
+ end
+
+ private
+
+ def load_fit_file
+ fit_file = File.join(@db.fit_dir, @fit_file)
begin
- @fit_activity = Fit4Ruby.read(fit_file) unless @fit_activity
+ return Fit4Ruby.read(fit_file)
rescue Fit4Ruby::Error
- Log.error "Cannot read #{fit_file}: #{$!}"
- return false
+ Log.fatal $!
end
- @fit_activity.send(method_name, *args, &block)
end
end
diff --git a/lib/postrunner/FlexiTable.rb b/lib/postrunner/FlexiTable.rb
index 90f17cd..041e414 100644
--- a/lib/postrunner/FlexiTable.rb
+++ b/lib/postrunner/FlexiTable.rb
@@ -4,19 +4,35 @@ module PostRunner
class Attributes
- attr_accessor :min_terminal_width, :horizontal_alignment
+ attr_accessor :min_terminal_width, :halign
def initialize(attrs = {})
@min_terminal_width = nil
- @horizontal_alignment = :left
+ @halign = nil
+
+ attrs.each do |name, value|
+ ivar_name = '@' + name.to_s
+ unless instance_variable_defined?(ivar_name)
+ Log.fatal "Unsupported attribute #{name}"
+ end
+ instance_variable_set(ivar_name, value)
+ end
+ end
+
+ def [](name)
+ ivar_name = '@' + name.to_s
+ return nil unless instance_variable_defined?(ivar_name)
+
+ instance_variable_get(ivar_name)
end
end
class Cell
- def initialize(table, content, attributes)
+ def initialize(table, row, content, attributes)
@table = table
+ @row = row
@content = content
@attributes = attributes
@@ -35,11 +51,9 @@ module PostRunner
def to_s
s = @content.to_s
- column_attributes = @table.column_attributes[@column_index]
- alignment = column_attributes.horizontal_alignment
- width = column_attributes.min_terminal_width
- case alignment
- when :left
+ width = get_attribute(:min_terminal_width)
+ case get_attribute(:halign)
+ when :left, nil
s + ' ' * (width - s.length)
when :right
' ' * (width - s.length) + s
@@ -48,25 +62,47 @@ module PostRunner
left_padding = w / 2
right_padding = w / 2 + w % 2
' ' * left_padding + s + ' ' * right_padding
+ else
+ raise "Unknown alignment"
end
end
def to_html
end
+ private
+
+ def get_attribute(name)
+ @attributes[name] || @row.attributes[name] ||
+ @table.column_attributes[@column_index][name]
+ end
+
end
class Row < Array
+ attr_reader :attributes
+
def initialize(table)
@table = table
+ @attributes = nil
super()
end
+ def cell(content, attributes)
+ c = Cell.new(@table, self, content, attributes)
+ self << c
+ c
+ end
+
def set_indicies(col_idx, row_idx)
self[col_idx].set_indicies(col_idx, row_idx)
end
+ def set_row_attributes(attributes)
+ @attributes = Attributes.new(attributes)
+ end
+
def to_s
s = ''
frame = @table.frame
@@ -86,6 +122,7 @@ module PostRunner
@head_rows = []
@body_rows = []
@foot_rows = []
+ @column_count = 0
@current_section = :body
@current_row = nil
@@ -114,7 +151,6 @@ module PostRunner
end
def cell(content, attributes = {})
- c = Cell.new(self, content, attributes)
if @current_row.nil?
case @current_section
when :head
@@ -127,16 +163,29 @@ module PostRunner
raise "Unknown section #{@current_section}"
end << (@current_row = Row.new(self))
end
- @current_row << c
-
- c
+ @current_row.cell(content, attributes)
end
- def row(cells)
+ def row(cells, attributes = {})
cells.each { |c| cell(c) }
+ set_row_attributes(attributes)
new_row
end
+ def set_column_attributes(col_attributes)
+ col_attributes.each.with_index do |ca, idx|
+ @column_attributes[idx] = Attributes.new(ca)
+ end
+ end
+
+ def set_row_attributes(row_attributes)
+ unless @current_row
+ raise "No current row. Use after first cell definition but before " +
+ "new_row call."
+ end
+ @current_row.set_row_attributes(row_attributes)
+ end
+
def enable_frame(enabled)
@frame = enabled
end
@@ -162,7 +211,9 @@ module PostRunner
private
def index_table
- @body_rows[0].each.with_index do |c, i|
+ @column_count = (@head_rows[0] || @body_rows[0]).length
+
+ @column_count.times do |i|
index_table_rows(i, @head_rows)
index_table_rows(i, @body_rows)
index_table_rows(i, @foot_rows)
@@ -176,7 +227,7 @@ module PostRunner
end
def calc_terminal_columns
- @body_rows[0].each.with_index do |c, i|
+ @column_count.times do |i|
col_mtw = nil
col_mtw = calc_section_teminal_columns(i, col_mtw, @head_rows)
diff --git a/lib/postrunner/Main.rb b/lib/postrunner/Main.rb
index f32fca4..89823d4 100644
--- a/lib/postrunner/Main.rb
+++ b/lib/postrunner/Main.rb
@@ -1,6 +1,8 @@
require 'optparse'
require 'logger'
require 'fit4ruby'
+
+require 'postrunner/version'
require 'postrunner/RuntimeConfig'
require 'postrunner/ActivitiesDB'
@@ -18,9 +20,12 @@ module PostRunner
def initialize(args)
@filter = nil
@name = nil
- @activities = ActivitiesDB.new(File.join(ENV['HOME'], '.postrunner'))
+ @activities = nil
+ @db_dir = File.join(ENV['HOME'], '.postrunner')
+
+ return if (args = parse_options(args)).nil?
- execute_command(parse_options(args))
+ execute_command(args)
end
private
@@ -73,25 +78,30 @@ EOT
opts.separator ""
opts.separator "General options:"
+ opts.on('--dbdir dir', String,
+ 'Directory for the activity database and related files') do |d|
+ @db_dir = d
+ end
opts.on('-v', '--verbose',
'Show internal messages helpful for debugging problems') do
Log.level = Logger::DEBUG
end
opts.on('-h', '--help', 'Show this message') do
$stderr.puts opts
- exit
+ return nil
end
opts.on('--version', 'Show version number') do
$stderr.puts VERSION
- exit
+ return nil
end
opts.separator <<"EOT"
Commands:
-check <fit file> ...
- Check the provided FIT file(s) for structural errors.
+check [ <fit file> | <ref> ... ]
+ Check the provided FIT file(s) for structural errors. If no file or
+ reference is provided, the complete archive is checked.
dump <fit file> | <ref>
Dump the content of the FIT file.
@@ -109,7 +119,7 @@ rename <ref>
Replace the FIT file name with a more meaningful name that describes
the activity.
-summary <fit file> | <ref>
+summary <ref>
Display the summary information for the FIT file.
EOT
@@ -119,9 +129,15 @@ EOT
end
def execute_command(args)
+ @activities = ActivitiesDB.new(@db_dir)
+
case (cmd = args.shift)
when 'check'
- process_files_or_activities(args, :check)
+ if args.empty?
+ @activities.check
+ else
+ process_files_or_activities(args, :check)
+ end
when 'delete'
process_activities(args, :delete)
when 'dump'
@@ -134,7 +150,7 @@ EOT
when 'rename'
process_activities(args, :rename)
when 'summary'
- process_files_or_activities(args, :summary)
+ process_activities(args, :summary)
when nil
Log.fatal("No command provided. " +
"See 'postrunner -h' for more information.")
@@ -147,30 +163,24 @@ EOT
def process_files_or_activities(files_or_activities, command)
files_or_activities.each do |foa|
if foa[0] == ':'
- files = @activities.map_to_files(foa[1..-1])
- if files.empty?
- Log.warn "No matching activities found for '#{foa}'"
- return
- end
-
- process_files(files, command)
+ process_activities([ foa ], command)
else
process_files([ foa ], command)
end
end
end
- def process_activities(activity_files, command)
- activity_files.each do |a|
- if a[0] == ':'
- files = @activities.map_to_files(a[1..-1])
- if files.empty?
- Log.warn "No matching activities found for '#{a}'"
+ def process_activities(activity_refs, command)
+ activity_refs.each do |a_ref|
+ if a_ref[0] == ':'
+ activities = @activities.find(a_ref[1..-1])
+ if activities.empty?
+ Log.warn "No matching activities found for '#{a_ref}'"
return
end
- process_files(files, command)
+ activities.each { |a| process_activity(a, command) }
else
- Log.fatal "Activity references must start with ':': #{a}"
+ Log.fatal "Activity references must start with ':': #{a_ref}"
end
end
@@ -194,19 +204,37 @@ EOT
def process_file(file, command)
case command
- when :delete
- @activities.delete(file)
+ when :check, :dump
+ read_fit_file(file)
when :import
@activities.add(file)
+ else
+ Log.fatal("Unknown file command #{command}")
+ end
+ end
+
+ def process_activity(activity, command)
+ case command
+ when :check
+ activity.check
+ when :delete
+ @activities.delete(activity)
+ when :dump
+ activity.dump
when :rename
- @activities.rename(file, @name)
+ activity.rename(@name)
+ when :summary
+ activity.summary
else
- begin
- activity = Fit4Ruby::read(file, @filter)
- #rescue
- # Log.error("File '#{file}' is corrupted!: #{$!}")
- end
- puts activity.to_s if command == :summary
+ Log.fatal("Unknown activity command #{command}")
+ end
+ end
+
+ def read_fit_file(fit_file)
+ begin
+ return Fit4Ruby::read(fit_file, @filter)
+ rescue StandardError
+ Log.error("Cannot read FIT file '#{fit_file}': #{$!}")
end
end
diff --git a/lib/postrunner/RuntimeConfig.rb b/lib/postrunner/RuntimeConfig.rb
deleted file mode 100644
index 2d5dccb..0000000
--- a/lib/postrunner/RuntimeConfig.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module PostRunner
-
- class RuntimeConfig
-
- def initialize
- @settings = {}
- @settings['data_dir'] = File.join(ENV['HOME'], '.postrunner')
- @settings['fit_dir'] = File.join(@settings['data_dir'], 'fit')
- end
-
- def [](key)
- @settings[key]
- end
-
- end
-
- Config = RuntimeConfig.new
-
-end
-
diff --git a/test/.PostRunner_spec.rb.swp b/test/.PostRunner_spec.rb.swp
new file mode 100644
index 0000000..db98879
--- /dev/null
+++ b/test/.PostRunner_spec.rb.swp
Binary files differ
diff --git a/test/FlexiTable_spec.rb b/test/FlexiTable_spec.rb
new file mode 100644
index 0000000..dedc9cb
--- /dev/null
+++ b/test/FlexiTable_spec.rb
@@ -0,0 +1,14 @@
+require 'postrunner/FlexiTable'
+
+describe PostRunner::FlexiTable do
+
+ it 'should create a simple ASCII table' do
+ t = PostRunner::FlexiTable.new do
+ row(%w( a bb ))
+ row(%w( ccc ddddd ))
+ end
+ puts t.to_s
+ end
+
+end
+
diff --git a/test/PostRunner_spec.rb b/test/PostRunner_spec.rb
new file mode 100644
index 0000000..31e1510
--- /dev/null
+++ b/test/PostRunner_spec.rb
@@ -0,0 +1,69 @@
+require 'fileutils'
+
+require 'postrunner/Main'
+
+describe PostRunner::Main do
+
+ def postrunner(args)
+ args = [ '--dbdir', @db_dir ] + args
+ old_stdout = $stdout
+ $stdout = (stdout = StringIO.new)
+ PostRunner::Main.new(args)
+ $stdout = old_stdout
+ stdout.string
+ end
+
+ def create_fit_file(name, date)
+ a = Fit4Ruby::Activity.new
+ a.start_time = Time.parse(date)
+ a.duration = 30 * 60
+ Fit4Ruby.write(name, a)
+ end
+
+ before(:all) do
+ @db_dir = File.join(File.dirname(__FILE__), '.postrunner')
+ FileUtils.rm_rf(@db_dir)
+ FileUtils.rm_rf('FILE1.FIT')
+ create_fit_file('FILE1.FIT', '2014-07-01-8:00')
+ #create_fit_file('FILE2.FIT', '2014-07-02-8:00')
+ end
+
+ after(:all) do
+ FileUtils.rm_rf(@db_dir)
+ FileUtils.rm_rf('FILE1.FIT')
+ end
+
+ it 'should abort without arguments' do
+ lambda { postrunner([]) }.should raise_error SystemExit
+ end
+
+ it 'should abort with bad command' do
+ lambda { postrunner(%w( foobar)) }.should raise_error SystemExit
+ end
+
+ it 'should support the -v option' do
+ postrunner(%w( --version ))
+ end
+
+ it 'should check a FIT file' do
+ postrunner(%w( check FILE1.FIT ))
+ end
+
+ it 'should list and empty archive' do
+ postrunner(%w( list ))
+ end
+
+ it 'should import a FIT file' do
+ postrunner(%w( import FILE1.FIT ))
+ end
+
+ it 'should check the imported file' do
+ postrunner(%w( check :1 ))
+ end
+
+ it 'should list the imported file' do
+ postrunner(%w( list )).index('FILE1.FIT').should be_a(Fixnum)
+ end
+
+end
+