diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84e21c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +*.rspec diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..a57eed4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org + +gem 'rspec' +gem 'nokogiri' diff --git a/bin/macbeth_analyzer.rb b/bin/macbeth_analyzer.rb new file mode 100644 index 0000000..69bbf84 --- /dev/null +++ b/bin/macbeth_analyzer.rb @@ -0,0 +1,3 @@ +require_relative '../lib/macbeth_line_analyzer' + +puts MacbethLineAnalyzer.new().analyze diff --git a/lib/macbeth_line_analyzer.rb b/lib/macbeth_line_analyzer.rb new file mode 100644 index 0000000..5f9c504 --- /dev/null +++ b/lib/macbeth_line_analyzer.rb @@ -0,0 +1,23 @@ +require 'open-uri' +require_relative 'shakespeare_line_analysis' +require_relative 'terminal_output' + +# This is the entry class for the analysis +# it is doing a bit too much, but, being the entry point, +# something has to have the knowledge of what to send where +class MacbethLineAnalyzer + WEB_SOURCE_LOCATION = "http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml" + + def initialize(source = nil) + source ||= File.read(open(WEB_SOURCE_LOCATION)) + @analyzer = ShakespeareLineAnalysis.new(source, TerminalOutput.new) + end + + def analyze + @analyzer.print_character_line_count + end + + def default_source_location + WEB_SOURCE_LOCATION + end +end diff --git a/lib/output_formatter.rb b/lib/output_formatter.rb new file mode 100644 index 0000000..ac55688 --- /dev/null +++ b/lib/output_formatter.rb @@ -0,0 +1,13 @@ +class OutputFormatter + def initialize(output) + @output = output + end + + def print_values_descending(hash) # sort it so that they are in descending order by key (line count) + sorted_hash = Hash[hash.sort_by { |key, value| value }.reverse] + + sorted_hash.each do |key, value| + @output.add_new_line "#{value} #{key.capitalize}" + end + end +end diff --git a/lib/shakespeare_line_analysis.rb b/lib/shakespeare_line_analysis.rb new file mode 100644 index 0000000..b7942cd --- /dev/null +++ b/lib/shakespeare_line_analysis.rb @@ -0,0 +1,19 @@ +require_relative 'shakespeare_parser' +require_relative 'output_formatter' + +class ShakespeareLineAnalysis + def initialize(source, output) + @source = source + @output = output + end + + def print_character_line_count + parser = ShakespeareParser.new(@source) + speakers = parser.speaker_line_count + + formatter = OutputFormatter.new(@output) + formatter.print_values_descending(speakers) + + @output.print + end +end diff --git a/lib/shakespeare_parser.rb b/lib/shakespeare_parser.rb new file mode 100644 index 0000000..a371809 --- /dev/null +++ b/lib/shakespeare_parser.rb @@ -0,0 +1,18 @@ +require 'nokogiri' + +class ShakespeareParser + def initialize(xml_source_string) + @xml_doc = Nokogiri::XML(xml_source_string) { |config| config.strict } + end + + def speaker_line_count + speakers = Hash.new + + @xml_doc.xpath("//SPEAKER").each do |node| + siblings = @xml_doc.search("SPEAKER[text()='#{node.content}'] ~ *").map &:text + speakers[node.content] = siblings.length + end + + speakers + end +end diff --git a/lib/terminal_output.rb b/lib/terminal_output.rb new file mode 100644 index 0000000..f245f26 --- /dev/null +++ b/lib/terminal_output.rb @@ -0,0 +1,14 @@ +class TerminalOutput + def initialize + @buffer = StringIO.new + end + + def add_new_line(text) + @buffer.puts text + end + + def print + @buffer.seek(0) + @buffer.read.chomp + end +end diff --git a/spec/macbeth_line_analyzer_spec.rb b/spec/macbeth_line_analyzer_spec.rb new file mode 100644 index 0000000..8d060d2 --- /dev/null +++ b/spec/macbeth_line_analyzer_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require_relative '../lib/macbeth_line_analyzer' + +describe 'displays number of total lines spoken by characters in Macbeth' do + it 'prints the correct number of lines for the characters with the specified document' do + source = File.read('spec/sample_data.xml') + analyzer = MacbethLineAnalyzer.new(source) + results = analyzer.analyze + expect(results).to eq "11 Macbeth\n5 Malcolm\n2 All" + end + + it 'has a default source pointing to a web location' do + analyzer = MacbethLineAnalyzer.new + expect(analyzer.default_source_location).to eq "http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml" + end + + it 'returns an error for malformed xml input' do + analyzer = MacbethLineAnalyzer.new("asdsada") + expect{analyzer.analyze}.to raise_error + end +end diff --git a/spec/output_formatter_spec.rb b/spec/output_formatter_spec.rb new file mode 100644 index 0000000..28dbdf2 --- /dev/null +++ b/spec/output_formatter_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' +require_relative '../lib/terminal_output' +require_relative '../lib/output_formatter' + +describe '.print_values_descending' do + it 'fills output in correct order' do + output_buffer = TerminalOutput.new + formatter = OutputFormatter.new(output_buffer) + h = Hash["mark" => 100, "steve" => 12, "kevin" => 200] + formatter.print_values_descending(h) + expect(output_buffer.print).to eq "200 Kevin\n100 Mark\n12 Steve" + end +end diff --git a/spec/sample_data.xml b/spec/sample_data.xml new file mode 100644 index 0000000..647818e --- /dev/null +++ b/spec/sample_data.xml @@ -0,0 +1,101 @@ + + + +The Tragedy of Macbeth + + +

Text placed in the public domain by Moby Lexical Tools, 1992.

+

SGML markup by Jon Bosak, 1992-1994.

+

XML version by Jon Bosak, 1996-1998.

+

This work may be freely copied and distributed worldwide.

+
+ + + +Dramatis Personae + +DUNCAN, king of Scotland. + + +MALCOLM +DONALBAIN +his sons. + + + + +MACBETH +BANQUO +generals of the king's army. + + + + +MACDUFF +LENNOX +ROSS +MENTEITH +ANGUS +CAITHNESS +noblemen of Scotland. + + +FLEANCE, son to Banquo. +SIWARD, Earl of Northumberland, general of the English forces. +YOUNG SIWARD, his son. +SEYTON, an officer attending on Macbeth. +Boy, son to Macduff. +An English Doctor. +A Scotch Doctor. +A Soldier. +A Porter. +An Old Man. +LADY MACBETH +LADY MACDUFF +Gentlewoman attending on Lady Macbeth. +HECATE +Three Witches. +Apparitions. +Lords, Gentlemen, Officers, Soldiers, Murderers, Attendants, and Messengers. + + +SCENE Scotland: England. + +MACBETH + +ACT I + +MACBETH +Into the air; and what seem'd corporal melted +As breath into the wind. Would they had stay'd! + + + +ALL +Fair is foul, and foul is fair: +Hover through the fog and filthy air. + + + +MALCOLM +This is the sergeant +Who like a good and hardy soldier fought +'Gainst my captivity. Hail, brave friend! +Say to the king the knowledge of the broil +As thou didst leave it. + + + +MACBETH +Stay, you imperfect speakers, tell me more: +By Sinel's death I know I am thane of Glamis; +But how of Cawdor? the thane of Cawdor lives, +A prosperous gentleman; and to be king +Stands not within the prospect of belief, +No more than to be Cawdor. Say from whence +You owe this strange intelligence? or why +Upon this blasted heath you stop our way +With such prophetic greeting? Speak, I charge you. + + +
diff --git a/spec/shakespeare_line_analysis_spec.rb b/spec/shakespeare_line_analysis_spec.rb new file mode 100644 index 0000000..b227f9a --- /dev/null +++ b/spec/shakespeare_line_analysis_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' +require_relative '../lib/terminal_output' +require_relative '../lib/shakespeare_line_analysis' + +describe '#print_character_line_count' do + it 'prints the number of lines for each character' do + source = File.open('spec/sample_data.xml') + output = TerminalOutput.new + analyzer = ShakespeareLineAnalysis.new(source, output) + results = analyzer.print_character_line_count + expect(results).to eq "11 Macbeth\n5 Malcolm\n2 All" + end +end diff --git a/spec/shakespeare_parser_spec.rb b/spec/shakespeare_parser_spec.rb new file mode 100644 index 0000000..2858b6d --- /dev/null +++ b/spec/shakespeare_parser_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' +require_relative '../lib/shakespeare_parser' + +describe '.speaker_line_count' do + it 'creates a hash of characters and their line numbers' do + source = File.read('spec/sample_data.xml') + parser = ShakespeareParser.new(source) + result = parser.speaker_line_count + expect(result['MACBETH']).to eq 11 + expect(result['MALCOLM']).to eq 5 + end + + it 'throws an error for malformed xml' do + expect{ShakespeareParser.new("asdas")}.to raise_error + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..6b4fc65 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,23 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# Require this file using `require "spec_helper"` to ensure that it is only +# loaded once. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # Limit the spec run to only specs with the focus metadata. If no specs have + # the filtering metadata and `run_all_when_everything_filtered = true` then + # all specs will run. + #config.filter_run :focus + + # Run all specs when none match the provided filter. This works well in + # conjunction with `config.filter_run :focus`, as it will run the entire + # suite when no specs have `:filter` metadata. + #config.run_all_when_everything_filtered = true + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + #config.order = 'random' +end diff --git a/spec/terminal_output_spec.rb b/spec/terminal_output_spec.rb new file mode 100644 index 0000000..8942589 --- /dev/null +++ b/spec/terminal_output_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' +require_relative '../lib/terminal_output' + +describe '#add_new_line' do + it 'adds a new line to the printed output on the console' do + output = TerminalOutput.new + output.add_new_line "test" + output.add_new_line "print" + expect(output.print).to eq "test\nprint" + end +end + +describe '#print' do + it 'displays what has been added to the buffer' do + output = TerminalOutput.new + output.add_new_line "foo" + output.add_new_line "bar" + expect(output.print).to eq "foo\nbar" + end +end