Skip to content

Instantly share code, notes, and snippets.

@gxespino
Created July 23, 2015 18:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gxespino/298494ec0917ee0c793b to your computer and use it in GitHub Desktop.
Save gxespino/298494ec0917ee0c793b to your computer and use it in GitHub Desktop.
Analyzing Shakespeare
--color
--require spec_helper
--format documentation
class Analyzer
attr_reader :line_hash_array
def initialize(line_hash_array)
@line_hash_array = line_hash_array
@sorted_hash = Hash.new(0)
end
def analyze
hash = line_hash_array.reduce(Hash.new, :merge)
@sorted_hash = hash.sort_by { |_key, value| value }.reverse.to_h.each do |k, v|
puts "#{v} #{k}"
end
@sorted_hash
end
end
require 'spec_helper'
describe Analyzer do
let(:sample_xml) { File.open("spec/fixtures/sample.xml") }
let(:parser) { Parser.new(sample_xml)}
it "prints the correct number of lines for characters in an descending fashion" do
result = { "Sergeant"=>17, "DUNCAN"=>6, "MALCOLM"=>5 }
hashed = parser.speakers_and_lines
counter = Counter.new(hashed)
counted_lines = counter.line_count_hash
analyzer = Analyzer.new(counted_lines).analyze
expect(analyzer).to eq result
end
end
class Counter
attr_reader :parsed_hash
def initialize(parsed_hash)
@parsed_hash = parsed_hash
end
def line_count(speaker)
consolidate_lines[speaker].count
end
def consolidate_lines
parsed_hash.inject { |speaker, lines| speaker.merge(lines) { |x, val_1, val_2 | val_1 + val_2 }}
end
def line_count_hash
consolidate_lines.map do |speaker, lines_array|
{ speaker => consolidate_lines[speaker].count }
end
end
end
require "spec_helper"
describe Counter do
let(:sample_xml) { File.open("spec/fixtures/sample.xml") }
let(:parser) { Parser.new(sample_xml) }
it "counts the number of lines for a speaker" do
hashed = parser.speakers_and_lines
counter = Counter.new(hashed)
expect(counter.line_count("DUNCAN")).to eq 6
expect(counter.line_count("MALCOLM")).to eq 5
expect(counter.line_count("Sergeant")).to eq 17
end
end
require 'net/http'
class Downloader
URL = 'http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml'
def download
Net::HTTP.get(URI.parse(URL))
end
end
require 'spec_helper'
describe Downloader do
it "downloads the MacBeth file from the internet" do
response = "<xml>"
stub = stub_request(:get, "http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml").
to_return(:body => response)
file = Downloader.new
expect(file.download).to eq response
end
end
source "https://rubygems.org"
gem "nokogiri"
gem "rspec"
gem "rake"
gem "webmock"
gem "byebug"
require "./lib/analyzer"
require "./lib/downloader"
require "./lib/parser"
require "./lib/counter"
xml = Downloader.new.download
hashed = Parser.new(xml).speakers_and_lines
count = Counter.new(hashed).line_count_hash
Analyzer.new(count).analyze
require 'nokogiri'
class Parser
attr_reader :file
attr_reader :result
def initialize(file)
@file = Nokogiri::XML(file)
end
def speakers_and_lines
search_for(file, "SPEECH").map do |speech|
speaker = search_for(speech, "SPEAKER").text
lines = search_for(speech, "LINE").map(&:text)
{
speaker => lines
}
end
end
private
def search_for(source, text)
source.search(text)
end
end
require 'spec_helper'
describe Parser do
let(:sample_xml) { File.open("spec/fixtures/sample.xml") }
let(:parser) { Parser.new(sample_xml) }
it "parses an xml file for speakers and their lines" do
duncan_lines = {
"DUNCAN" => [ "What bloody man is that? He can report,",
"As seemeth by his plight, of the revolt",
"The newest state." ]
}
malcolm_lines = {
"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." ]
}
expect(parser.speakers_and_lines[0]).to eq duncan_lines
expect(parser.speakers_and_lines[1]).to eq malcolm_lines
end
end
<?xml version="1.0"?>
<!DOCTYPE PLAY SYSTEM "play.dtd">
<PLAY>
<TITLE>The Tragedy of Macbeth</TITLE>
<FM>
<P>Text placed in the public domain by Moby Lexical Tools, 1992.</P>
<P>SGML markup by Jon Bosak, 1992-1994.</P>
<P>XML version by Jon Bosak, 1996-1998.</P>
<P>This work may be freely copied and distributed worldwide.</P>
</FM>
<PERSONAE>
<TITLE>Dramatis Personae</TITLE>
<PERSONA>DUNCAN, king of Scotland.</PERSONA>
<PGROUP>
<PERSONA>MALCOLM</PERSONA>
<PERSONA>DONALBAIN</PERSONA>
<GRPDESCR>his sons.</GRPDESCR>
</PGROUP>
<PGROUP>
<PERSONA>MACBETH</PERSONA>
<PERSONA>BANQUO</PERSONA>
<GRPDESCR>generals of the king's army.</GRPDESCR>
</PGROUP>
<PGROUP>
<PERSONA>MACDUFF</PERSONA>
<PERSONA>LENNOX</PERSONA>
<PERSONA>ROSS</PERSONA>
<PERSONA>MENTEITH</PERSONA>
<PERSONA>ANGUS</PERSONA>
<PERSONA>CAITHNESS</PERSONA>
<GRPDESCR>noblemen of Scotland.</GRPDESCR>
</PGROUP>
<PERSONA>FLEANCE, son to Banquo.</PERSONA>
<PERSONA>SIWARD, Earl of Northumberland, general of the English forces.</PERSONA>
<PERSONA>YOUNG SIWARD, his son.</PERSONA>
<PERSONA>SEYTON, an officer attending on Macbeth.</PERSONA>
<PERSONA>Boy, son to Macduff. </PERSONA>
<PERSONA>An English Doctor. </PERSONA>
<PERSONA>A Scotch Doctor. </PERSONA>
<PERSONA>A Soldier.</PERSONA>
<PERSONA>A Porter.</PERSONA>
<PERSONA>An Old Man.</PERSONA>
<PERSONA>LADY MACBETH</PERSONA>
<PERSONA>LADY MACDUFF</PERSONA>
<PERSONA>Gentlewoman attending on Lady Macbeth. </PERSONA>
<PERSONA>HECATE</PERSONA>
<PERSONA>Three Witches.</PERSONA>
<PERSONA>Apparitions.</PERSONA>
<PERSONA>Lords, Gentlemen, Officers, Soldiers, Murderers, Attendants, and Messengers. </PERSONA>
</PERSONAE>
<SCNDESCR>SCENE Scotland: England.</SCNDESCR>
<PLAYSUBT>MACBETH</PLAYSUBT>
<ACT><TITLE>ACT I</TITLE>
<SCENE><TITLE>SCENE II. A camp near Forres.</TITLE>
<STAGEDIR>Alarum within. Enter DUNCAN, MALCOLM, DONALBAIN,
LENNOX, with Attendants, meeting a bleeding Sergeant</STAGEDIR>
<SPEECH>
<SPEAKER>DUNCAN</SPEAKER>
<LINE>What bloody man is that? He can report,</LINE>
<LINE>As seemeth by his plight, of the revolt</LINE>
<LINE>The newest state.</LINE>
</SPEECH>
<SPEECH>
<SPEAKER>MALCOLM</SPEAKER>
<LINE>This is the sergeant</LINE>
<LINE>Who like a good and hardy soldier fought</LINE>
<LINE>'Gainst my captivity. Hail, brave friend!</LINE>
<LINE>Say to the king the knowledge of the broil</LINE>
<LINE>As thou didst leave it.</LINE>
</SPEECH>
<SPEECH>
<SPEAKER>Sergeant</SPEAKER>
<LINE>Doubtful it stood;</LINE>
<LINE>As two spent swimmers, that do cling together</LINE>
<LINE>And choke their art. The merciless Macdonwald--</LINE>
<LINE>Worthy to be a rebel, for to that</LINE>
<LINE>The multiplying villanies of nature</LINE>
<LINE>Do swarm upon him--from the western isles</LINE>
<LINE>Of kerns and gallowglasses is supplied;</LINE>
<LINE>And fortune, on his damned quarrel smiling,</LINE>
<LINE>Show'd like a rebel's whore: but all's too weak:</LINE>
<LINE>For brave Macbeth--well he deserves that name--</LINE>
<LINE>Disdaining fortune, with his brandish'd steel,</LINE>
<LINE>Which smoked with bloody execution,</LINE>
<LINE>Like valour's minion carved out his passage</LINE>
<LINE>Till he faced the slave;</LINE>
<LINE>Which ne'er shook hands, nor bade farewell to him,</LINE>
<LINE>Till he unseam'd him from the nave to the chaps,</LINE>
<LINE>And fix'd his head upon our battlements.</LINE>
</SPEECH>
<SPEECH>
<SPEAKER>DUNCAN</SPEAKER>
<LINE>What bloody man is that? He can report,</LINE>
<LINE>As seemeth by his plight, of the revolt</LINE>
<LINE>The newest state.</LINE>
</SPEECH>
$LOAD_PATH << File.dirname(File.dirname(__FILE__))
require "macbeth_analyzer"
require "webmock/rspec"
WebMock.disable_net_connect!(allow_localhost: true)
Dir["spec/support/*.rb"].each { |f| require f }
Copy link

ghost commented Jul 23, 2015

Well, there are not so many things to talk about:

  1. analyzer.rb:10 - why not just {}? And prefer inject over reduce
  2. analyzer_spec.rb: too many local variables (i.e. parsed_result / expected_result). Move them to let blocks or just rethink your architecture
  3. counter.rb - the name of the class is horrible (you 100% will get name conflicts with another project). Change to WordCounter or whatever... (I've seen a project that has a Day class that was some kind of json serializer, it had only a huge constructor and #to_json method - almost the same story here). Or move all your classes to some namespace, it will save from conflicts.
  4. counter_spec.rb - repeating of File.read(path/to/xml) - move to helper
spec/support/fixtures_helper.rb
module FixturesHelper
  def sample_xml
    File.open("spec/fixtures/sample.xml")
  end
end

RSpec.configure do |config|
  config.include FixturesHelper, :fixture
end

Probably you can also make it more configurable like

# 'fixture' method will be pretty much like 'sample_xml'
# from previous snippet, but with parameter (use File.join to build a full path)
let(:xml) { fixture(:sample_xml) }
  1. downloader.rb - probably you should organize this class as a singleton. I would make it in the following way:
# module, not class, because it can't be instantiated
module Downloader
  DEFAULT_URL = 'http://url.com'
  def self.download(url = DEFAULT_URL)
    # same logic as before
  end
end
# And move url to parameter with default value, you can use it tests in order to simplify the process of mocking
  1. macbeth_analyzer.rb - too many ugly require calls. Read about $LOAD_PATH and relative_require (or, probably Kernel.autoload, but then you need a namespace module)
  2. parser.rb - probably you can organize your hash as a one-liner { key => value }
  3. parser_spec.rb - same as before, move constants to let, write some shared helper for fixtures.
  4. Just finished looking at your spec_helper. So, you know what is $LOAD_PATH and support helper, but still write it in the wrong way, right? 😄

It seems that you were trying to write everything in a proper OO-way, but Ruby supports multiple paradigms, do not use only OOP. And I don't see any common class that wraps all the functionality (I mean, it would be nice to write it).

I think that's all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment