Permalink
Browse files

first version of the Animal

  • Loading branch information...
1 parent 884100f commit a29547a561def74f71f84d93bafab40bb108eb47 @rklemme committed Jun 22, 2009
View
2 README
@@ -8,3 +8,5 @@ bin/ -- binary executables
test/JavaAllocationTest -- test to demonstrate the overhead of
allocating two additional objects per record
+lib -- Implementation of the Animal
+
View
@@ -0,0 +1,39 @@
+#!/usr/local/bin/ruby19 -w
+
+# Implementation of the parser for the sample
+# generator script test-gen.rb.
+$: << File.join(File.dirname(File.dirname($0)), "lib")
+
+require 'time'
+require 'animal'
+
+# main defines the custom parser class!
+Animal.main do
+
+ TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%N'.freeze
+
+ attr_accessor :year
+ attr_reader :interaction_id, :time_stamp
+
+ def parse(line)
+ if %r{
+ ^
+ ( \d{4}-\d{2}-\d{2} \s \d{2}:\d{2}:\d{2}(?:\.\d+)? )
+ \s+
+ (\S+) # interaction_id
+ \s+
+ }x =~ line
+ @time_stamp = Time.strptime $1, TIME_FORMAT
+ @interaction_id = $2
+ else
+ @time_stamp = nil
+ @interaction_id = nil
+ end
+ end
+
+ def initial_line?
+ time_stamp
+ end
+end
+
+# EOF
View
@@ -0,0 +1,45 @@
+
+require 'ostruct'
+
+# Namespace for all the Animal related classes
+# Animal is the project on Ruby Best Practices
+# blog which demonstrates the thought process
+# of writing an application
+module Animal
+
+ # Parse the given command line and return an option instance.
+ # Options are removed from the argument so what is left in
+ # there must be file names.
+ def self.parse_command_line(argv = ::ARGV)
+ o = OpenStruct.new(:output_dir => ".")
+ # parse
+ o
+ end
+
+ # This metho allows to write extremely short applications
+ # because it accepts a block which is used to define the
+ # parser class. Alternatively users can provide a parser
+ # instance. The default command line is added implicitly
+ # and will be option parsed and the whole processing will
+ # start automatically.
+ def self.main(parser = nil, argv = ::ARGV, &class_body)
+ $stderr.puts 'WARNING: ignoring class body' if parser && class_body
+ parser ||= Class.new(&class_body).new
+ options = parse_command_line(argv)
+ coord = Coordinator.new
+ coord.parser = parser
+ coord.options = options
+ coord.process_files argv
+ end
+
+ # autoload init
+ %w{
+ Coordinator
+ ProcessingStorage
+ FileStatistics
+ InteractionProcessor
+ }.each do |cl|
+ autoload cl, "animal/#{cl.gsub(/([a-z])([A-Z])/, '\\1_\\2').downcase}"
+ end
+
+end
View
@@ -0,0 +1,79 @@
+
+# The coordinator is the main instance which coordinates
+# processing of log files.
+module Animal
+ class Coordinator
+
+ YES = lambda {|processor| true}
+
+ attr_reader :options
+ attr_accessor :parser
+
+ def initialize
+ @filter = YES
+ @processors = Hash.new do |h, id|
+ id.freeze
+ h[id] = InteractionProcessor.new(id, self)
+ end
+ end
+
+ def options=(opts)
+ @options = opts
+
+ # set filter from options
+ @filter = YES
+ end
+
+ def process_file(file)
+ if file == '-'
+ process_impl(file, $stdin)
+ else
+ File.open(file, "r") do |io|
+ process_impl(file, io)
+ end
+ end
+ end
+
+ def process_files(files = ::ARGV)
+ files.each do |f|
+ process_file(f)
+ end
+ finish
+ self
+ end
+
+ # Trigger finish processing after a set of
+ # files has been processed.
+ def finish
+ @processors.each do |id, pr|
+ pr.finish
+ end
+ self
+ end
+
+ private
+
+ # Determine the year of the given log file
+ # in case timestamps do not have a year.
+ def determine_year(file, io)
+ # fallback
+ Time.now.year
+ end
+
+ def process_impl(name, io)
+ parser.year = determine_year(name, io)
+ last_proc = nil
+
+ io.each do |line|
+ parser.parse line
+
+ if parser.initial_line?
+ last_proc = @processors[parser.interaction_id]
+ last_proc.process(parser.time_stamp, line)
+ else
+ last_proc.append_line line
+ end
+ end
+ end
+ end
+end
No changes.
@@ -0,0 +1,57 @@
+
+require 'time'
+require 'fileutils'
+
+# An interaction processor is responsible for processing
+# all log entries of a single interaction
+module Animal
+
+ # An entry in the list of log records
+ Entry = Struct.new :time_stamp, :line
+
+ # processor of a single interaction from the log
+ class InteractionProcessor
+
+ attr_reader :id, :coord, :entries
+
+ def initialize(id, coordinator)
+ @id = id
+ @coord = coordinator
+ @entries = []
+ end
+
+ # Process an initial line
+ def process(time_stamp, line)
+ # unfiltered for now
+ @entries << Entry.new(time_stamp, line)
+ end
+
+ # Append a continuation line to the last entry
+ def append_line(line)
+ l = @entries.last and l.line << line
+ end
+
+ def finish
+ # write out to file...
+ unless @entries.empty?
+ fn = file_name
+ FileUtils.mkdir_p(File.dirname(fn))
+ File.open(fn, "w") do |io|
+ io.puts @entries.map(&:line)
+ end
+ end
+ end
+
+ private
+
+ # calculate the file name, this fails if
+ # there are no entries!
+ def file_name
+ ts = @entries.first.time_stamp
+ File.join(@coord.options.output_dir,
+ ts.strftime('%Y-%m-%d'),
+ ts.strftime('%H-%M'),
+ ts.strftime('%S.%3N-') + id)
+ end
+ end
+end
No changes.

0 comments on commit a29547a

Please sign in to comment.