diff options
-rw-r--r-- | lib/postrunner.rb | 1 | ||||
-rw-r--r-- | lib/postrunner/ActivitiesDB.rb | 57 | ||||
-rw-r--r-- | lib/postrunner/Activity.rb | 38 | ||||
-rw-r--r-- | lib/postrunner/FlexiTable.rb | 81 | ||||
-rw-r--r-- | lib/postrunner/Main.rb | 94 | ||||
-rw-r--r-- | lib/postrunner/RuntimeConfig.rb | 20 | ||||
-rw-r--r-- | test/.PostRunner_spec.rb.swp | bin | 0 -> 12288 bytes | |||
-rw-r--r-- | test/FlexiTable_spec.rb | 14 | ||||
-rw-r--r-- | test/PostRunner_spec.rb | 69 |
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 Binary files differnew file mode 100644 index 0000000..db98879 --- /dev/null +++ b/test/.PostRunner_spec.rb.swp 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 + |