diff --git a/README.md b/README.md index 11daf64..abf363b 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,11 @@ -***An exercise for Prime subscribers. Visit http://learn.thoughtbot.com/prime to learn more.*** +# Shakespeare Play Analyzer +(like wc for theater) -### Difficulty level: intermediate. -## Your Task +#### How Can I Use This Wonderful Library? -As a Shakespeare buff, statistics junkie, and unix lover, Ben finds himself wanting a command-line tool for analyzing Macbeth. - -Write a command-line program that prints the number of lines spoken by each character in the play. - -Sample usage/output (using made-up numbers): - - $ ruby macbeth_analyzer.rb - 543 Macbeth - 345 Banquo - 220 Duncan - (etc.) - -You can find an XML-encoded version of Macbeth here: http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml. Your program should download and parse this file at runtime. - -Your solution must be tested, preferably via TDD. - -## Working/Submitting - -1. To work on this exercise, fork the repo and begin implementing your solution. -2. When you are done, copy the output of your program into a file in this repository. -3. Create a pull request so your code can be reviewed. -4. Perform a code review on at least one other person's solution. Your comments should follow our code review guidelines: https://github.com/thoughtbot/guides/tree/master/code-review. Most important: be friendly. Make suggestions, not demands. -5. Improve your solution based on the comments you've received and approaches you've learned from reviewing others' attempts. - -## Bounty - -While knowledge and skill improvement are their own rewards, the author with the best solution (as judged by thoughtbot) will receive a cool thoughtbot t-shirt. +``` +git clone https://github.com/shreve/shakespeare_analyzer.git +cd shakespeare_analyzer +ruby app.rb +``` \ No newline at end of file diff --git a/app b/app new file mode 100755 index 0000000..8019590 --- /dev/null +++ b/app @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +require_relative 'lib/shakespeare_analyzer.rb' + +MACBETH_URL = "http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml" +HAMLET_URL = "http://www.ibiblio.org/xml/examples/shakespeare/hamlet.xml" + +macbeth_analyzer = ShakespeareAnalyzer.new MACBETH_URL +hamlet_analyzer = ShakespeareAnalyzer.new HAMLET_URL + +puts "Lines Speaker" +puts "Macbeth" +print macbeth_analyzer.lines_per_speaker + +puts "Hamlet" +print hamlet_analyzer.lines_per_speaker diff --git a/lib/play.rb b/lib/play.rb new file mode 100644 index 0000000..00cd7d1 --- /dev/null +++ b/lib/play.rb @@ -0,0 +1,40 @@ +require 'nokogiri' +require 'open-uri' +require_relative 'speech' + +class Play + attr_accessor :document + + def initialize(play_location) + @play_location = play_location + end + + def speeches + document.css('SPEECH').map do |speech| + Speech.new speech + end + end + + def document + @document ||= Nokogiri::XML(play_data) + end + + private + def play_data + if location_is_a_file? + @play_location + elsif location_is_a_url? + open(@play_location) + else + File.open(@play_location, 'r') + end + end + + def location_is_a_file? + @play_location.is_a? File + end + + def location_is_a_url? + @play_location.match(/^http/) + end +end diff --git a/lib/shakespeare_analyzer.rb b/lib/shakespeare_analyzer.rb new file mode 100644 index 0000000..1b9e6bc --- /dev/null +++ b/lib/shakespeare_analyzer.rb @@ -0,0 +1,34 @@ +require_relative 'play' + +class ShakespeareAnalyzer + attr_accessor :play, :speeches, :speakers + + def initialize(play_location) + @play = Play.new(play_location) + @speakers = Hash.new(0) + end + + def lines_per_speaker + most_verbose_speakers.map { |name, line_count| "#{line_count}\t#{name}\n" }.join + end + + private + def most_verbose_speakers + analyze_speeches + @speakers.sort_by { |name, line_count| -line_count } + end + + def get_speeches + @speeches ||= @play.speeches + end + + def analyze_speeches + get_speeches.each do |speech| + # nested iterators :/ + speech.speakers.each do |speaker| + # new value is previous value or zero plus the number of lines in the speech + @speakers[speaker] += speech.lines.count + end + end + end +end diff --git a/lib/speech.rb b/lib/speech.rb new file mode 100644 index 0000000..8926890 --- /dev/null +++ b/lib/speech.rb @@ -0,0 +1,11 @@ +class Speech + attr_accessor :speakers, :lines + + def initialize(speech_xml) + @speakers = speech_xml.css('SPEAKER').map do |speaker| + # I'd do map(&:content), but I hate CAPSLOCKED SPEAKER NAMES + speaker.content.downcase.split.map(&:capitalize).join(' ') + end + @lines = speech_xml.css('LINE').map(&:content) + end +end diff --git a/shakespeare_analyzer_out.txt b/shakespeare_analyzer_out.txt new file mode 100644 index 0000000..218fd46 --- /dev/null +++ b/shakespeare_analyzer_out.txt @@ -0,0 +1,41 @@ +719 Macbeth +265 Lady Macbeth +212 Malcolm +180 Macduff +135 Ross +113 Banquo +74 Lennox +70 Duncan +62 First Witch +46 Porter +45 Doctor +41 Lady Macduff +39 Hecate +35 Sergeant +30 Siward +30 First Murderer +27 Third Witch +27 Second Witch +24 All +23 Gentlewoman +23 Messenger +21 Angus +21 Lord +20 Son +15 Second Murderer +12 Menteith +11 Old Man +11 Caithness +10 Donalbain +8 Third Murderer +7 Young Siward +5 Third Apparition +5 Seyton +5 Servant +4 Second Apparition +3 Lords +2 First Apparition +2 Both Murderers +2 Fleance +1 Attendant +1 Soldiers diff --git a/test/example_play.xml b/test/example_play.xml new file mode 100644 index 0000000..28b7cd9 --- /dev/null +++ b/test/example_play.xml @@ -0,0 +1,22 @@ + + A Tale of Two Rubyists: The Chunkiest Bacon + + ACT I + + SCENE I. An Introduction + + FOX 1 + Lorem ipsum dolor sit amet... + + + FOX 2 + This is boring, I'm out. + + + FOX 1 + FOX 2 + CHUNKY BACON! + + + + diff --git a/test/play_test.rb b/test/play_test.rb new file mode 100644 index 0000000..39856c9 --- /dev/null +++ b/test/play_test.rb @@ -0,0 +1,27 @@ +class TestPlay < MiniTest::Unit::TestCase + def setup + @play = Play.new(example_play) + end + + def test_play_initializes_with_file_path + play = Play.new(example_play_path) + assert play.document, "play was unable to initialize when given a file path as a string" + end + + def test_play_initializes_with_file_instance + assert @play.document, "play was unable to initialize when given a File object" + end + +# This passed, but my internet sucks, so I don't +# want to fetch the play every time I run my tests + def test_play_initializes_with_url + play = Play.new("http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml") + assert play.document, "play was unable to initialize when given a url" + end + + def test_speeches_initializes_an_array_of_speech_objects + speeches = @play.speeches + assert speeches.is_a? Array + assert speeches.first.is_a? Speech + end +end diff --git a/test/shakespeare_analyzer_test.rb b/test/shakespeare_analyzer_test.rb new file mode 100644 index 0000000..507afb9 --- /dev/null +++ b/test/shakespeare_analyzer_test.rb @@ -0,0 +1,16 @@ +require_relative '../lib/shakespeare_analyzer.rb' +require 'minitest/autorun' +require_relative 'test_helper' +require_relative 'play_test' +require_relative 'speech_test' + +class TestShakespeareAnalyzer < MiniTest::Unit::TestCase + def setup + @analyzer = ShakespeareAnalyzer.new example_play + end + + def test_lines_per_speaker_outputs_data_correctly + assert_equal "2\tFox 1\n2\tFox 2\n", @analyzer.lines_per_speaker, + "lines_per_speaker output didn't match expected" + end +end diff --git a/test/speech_test.rb b/test/speech_test.rb new file mode 100644 index 0000000..10733b8 --- /dev/null +++ b/test/speech_test.rb @@ -0,0 +1,23 @@ +require 'nokogiri' + +class TestSpeech < MiniTest::Unit::TestCase + def setup + @speech = Speech.new(speech_xml.first) + end + + def test_speech_initializes_with_attributes + assert @speech.speakers && @speech.lines + end + + def test_speakers_are_properly_capitalized + assert_equal "Fox 1", @speech.speakers.first + end + + def test_lines_are_properly_added + assert_equal 1, @speech.lines.count + end + + def speech_xml + nk_example_play.css('SPEECH') + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..e2041e5 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,11 @@ +def example_play_path + File.join(File.dirname(__FILE__), 'example_play.xml') +end + +def example_play + File.open(example_play_path, 'r') +end + +def nk_example_play + Nokogiri::XML(example_play) +end