urban_dictionary 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MmQ5NjJjOTM5OTBmYWRkMjI0Nzc5ODE5ZGNlZGZiYTljZTNmZjFjZA==
5
+ data.tar.gz: !binary |-
6
+ MzQxMDhhNDMxZmNlM2U1ZWUyNjI4OGNmMWEwMjkwOWRlYzQxMjhhZg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NGRmMWY0Y2MwMWM2NWMyYmMwMjI3NTU2ZDcyNjJjOWJkNGI2NDFlZjNlOGVl
10
+ MGI1ODIyZTAyN2FhODNkYjIyODhiNGQ5NWEzMmI0OTNlZGQ3Yzg4ZDA5Mzg2
11
+ Y2M3ZjQ4ZDY2ZjZlNDhjNDE4NWEyOWI2YTU0MjI2OWJkMjI2M2Y=
12
+ data.tar.gz: !binary |-
13
+ MjZlMWZhMjA2OWMzY2NhZGI0YzFlZGRmNmIwNTA4YzNlZDQyNTFjMzAxODY0
14
+ NmZiNmJkOWU5MWI0ZDgxNjFmNTQxNzFiYTRjMTM1NDQzZTg5MTYyZDRhNzdl
15
+ MWVjZTM5ZjVmMmM3NDExOGRmMGY3MzM5ZjRkOTA2YjBhYTZhNmQ=
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  *.gem
2
+ Gemfile.lock
2
3
  pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ - 2.2.4
5
+ - 2.1.8
6
+ - 2.0.0
7
+ - 1.9.3
8
+ - jruby-19mode # JRuby in 1.9 mode
9
+ - rbx-3.14
10
+ before_install:
11
+ - gem install bundler
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 - 2016-02-15
4
+ - Fixed broken parsing of Urban Dictionary's new layout
5
+ - Fixed bug where undefined words are displayed as the shrug emoticon (`¯\_(ツ)_/¯`)
6
+ - Added support for --format=json to output JSON
7
+ - Added MIT License
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in urban_dictionary.gemspec
4
4
  gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ryan Greenberg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  `urban_dictionary` is a Ruby gem to access word definitions and examples from [Urban Dictionary](http://www.urbandictionary.com/). It also provides a command-line tool for getting definitions.
4
4
 
5
+ [![Build Status](https://travis-ci.org/ryangreenberg/urban_dictionary.svg?branch=master)](https://travis-ci.org/ryangreenberg/urban_dictionary)
6
+
5
7
  ## Installation ##
6
8
 
7
9
  Run `gem install urban_dictionary`.
@@ -35,9 +37,21 @@ You can use the `--random` flag to get the definition of random word:
35
37
 
36
38
  > urban_dictionary --random
37
39
 
40
+ Specify the output format with `--format`. The default is `plain`; `json` is also supported.
41
+
42
+ > urban_dictionary --format=json one hundred | jq .
43
+ {
44
+ "word": "one hundred",
45
+ "entries": [
46
+ {
47
+ "definition": "keepin it real to the fullest and super tight.",
48
+ "example": "end of the convo\rLuke: aight man, peace\rQ: kool homie, keep it one hundred (100)"
49
+ },
50
+ ...
51
+ }
52
+
38
53
  ## Tests ##
39
54
 
40
55
  Run examples with:
41
56
 
42
57
  rspec
43
-
data/Rakefile CHANGED
@@ -1 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
data/bin/urban_dictionary CHANGED
@@ -1,38 +1,5 @@
1
1
  #!/usr/bin/env ruby -KU
2
- require 'urban_dictionary'
3
- require 'optparse'
2
+ require_relative '../lib/urban_dictionary'
4
3
 
5
- options = {}
6
- option_parser = OptionParser.new do |opts|
7
- opts.banner = "Usage: urban_dictionary <word or phrase>"
8
- opts.version = UrbanDictionary::VERSION
9
-
10
- opts.on("-r", "--random", "Define a random word") do |r|
11
- options[:random] = r
12
- end
13
- end
14
- option_parser.parse(ARGV)
15
-
16
- if ARGV.empty? && !options[:random]
17
- puts option_parser.help
18
- exit(0)
19
- end
20
-
21
- word = if options[:random]
22
- UrbanDictionary.random_word
23
- else
24
- UrbanDictionary.define(ARGV.join(" "))
25
- end
26
-
27
- output = []
28
- output << word
29
- output << '-' * word.size
30
- output << ''
31
- word.entries.each_with_index do |entry, i|
32
- output << "#{i + 1}. #{entry.definition}"
33
- output << ""
34
- output << "Example: #{entry.example}"
35
- output << ""
36
- output << ""
37
- end
38
- puts output.join("\n")
4
+ config = UrbanDictionary::CLI::Config.new(:args => ARGV)
5
+ UrbanDictionary::CLI.new(config).run
@@ -0,0 +1,106 @@
1
+ require 'optparse'
2
+
3
+ module UrbanDictionary
4
+ class CLI
5
+ class Config
6
+ # Constructor accepts an option hash with the following properties:
7
+ PROPERTIES = [
8
+ :args, # ARGV input from the user (required)
9
+ :stdout, # IO object for writing to stdout (optional)
10
+ :stderr, # IO object for writing to stderr (optional)
11
+ :dictionary, # Word-lookup object that implements .random_word and .define (optional)
12
+ ]
13
+
14
+ attr_reader *PROPERTIES
15
+
16
+ def initialize(options)
17
+ required(:args, options)
18
+ optional(:stdout, options, STDOUT)
19
+ optional(:stderr, options, STDERR)
20
+ optional(:dictionary, options, UrbanDictionary)
21
+ end
22
+
23
+ def update(property, value)
24
+ if PROPERTIES.include?(property)
25
+ set(property, value)
26
+ else
27
+ raise ArgumentError, "#{property} is not a valid property (#{PROPERTIES.inspect})."
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def required(property, hsh)
34
+ unless hsh.include?(property)
35
+ raise ArgumentError, "#{hsh.inspect} does not include required property #{property}"
36
+ end
37
+ set(property, hsh[property])
38
+ end
39
+
40
+ def optional(property, hsh, default)
41
+ value = hsh.include?(property) ? hsh[property] : default
42
+ set(property, value)
43
+ end
44
+
45
+ def set(property, value)
46
+ instance_variable_set("@#{property}", value)
47
+ end
48
+ end
49
+
50
+ DEFAULT_FORMAT = :plain
51
+
52
+ attr_reader :options
53
+
54
+ def initialize(config)
55
+ @args = config.args
56
+ @stdout = config.stdout
57
+ @stderr = config.stderr
58
+ @dictionary = config.dictionary
59
+ @options = {}
60
+ end
61
+
62
+ def option_parser
63
+ @option_parser ||= OptionParser.new do |opts|
64
+ opts.banner = "Usage: urban_dictionary <word or phrase>"
65
+ opts.version = UrbanDictionary::VERSION
66
+
67
+ opts.on("-r", "--random", "Define a random word") do |r|
68
+ options[:random] = r
69
+ end
70
+
71
+ options[:format] = DEFAULT_FORMAT
72
+ opts.on("-f", "--format=FORMAT", "Output format (plain, json)") do |f|
73
+ format = f.downcase.to_sym
74
+ unless UrbanDictionary::Formatter.registered.include?(format)
75
+ raise OptionParser::InvalidOption, "#{f} is not a valid format"
76
+ end
77
+ options[:format] = format
78
+ end
79
+ end
80
+ end
81
+
82
+ def run
83
+ options[:remaining] = option_parser.parse(@args)
84
+
85
+ if options[:remaining].empty? && !options[:random]
86
+ @stdout.puts option_parser.help
87
+ return
88
+ end
89
+
90
+ term = options[:remaining].join(" ")
91
+ word = if options[:random]
92
+ @dictionary.random_word
93
+ else
94
+ @dictionary.define(term)
95
+ end
96
+
97
+ if word.nil?
98
+ @stderr.puts "No definition found for '#{term}'"
99
+ exit(1)
100
+ end
101
+
102
+ formatter = UrbanDictionary::Formatter.for(options[:format])
103
+ @stdout.puts(formatter.format(word))
104
+ end
105
+ end
106
+ end
@@ -10,5 +10,9 @@ module UrbanDictionary
10
10
  def to_s
11
11
  "#{definition}\n#{example}"
12
12
  end
13
+
14
+ def inspect
15
+ "<#{self.class} @definition=#{definition.inspect}, @example=#{example.inspect}>"
16
+ end
13
17
  end
14
18
  end
@@ -0,0 +1,58 @@
1
+ require 'multi_json'
2
+
3
+ module UrbanDictionary
4
+ class Formatter
5
+ def self.register(name, klass)
6
+ @formatters ||= {}
7
+ if @formatters.include?(name)
8
+ raise RuntimeError, "Formatter #{@formatters[name]} already registered for '#{name}'"
9
+ else
10
+ @formatters[name] = klass
11
+ end
12
+ end
13
+
14
+ def self.for(name)
15
+ @formatters[name]
16
+ end
17
+
18
+ def self.registered
19
+ @formatters.keys.map()
20
+ end
21
+
22
+ def self.format(word)
23
+ raise NotImplementedError, "#format has not been implemented by #{self}"
24
+ end
25
+ end
26
+
27
+ class PlainFormatter < Formatter
28
+ def self.format(word)
29
+ output = []
30
+ output << word
31
+ output << '-' * word.size
32
+ output << ''
33
+ word.entries.each_with_index do |entry, i|
34
+ output << "#{i + 1}. #{entry.definition}"
35
+ output << ""
36
+ output << "Example: #{entry.example}"
37
+ output << ""
38
+ output << ""
39
+ end
40
+ output.join("\n")
41
+ end
42
+ end
43
+
44
+ class JsonFormatter < Formatter
45
+ def self.format(word)
46
+ hsh = {
47
+ :word => word.word,
48
+ :entries => word.entries.map do |entry|
49
+ {
50
+ :definition => entry.definition,
51
+ :example => entry.example
52
+ }
53
+ end
54
+ }
55
+ MultiJson.dump(hsh)
56
+ end
57
+ end
58
+ end
@@ -1,3 +1,3 @@
1
1
  module UrbanDictionary
2
- VERSION = "0.0.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'nokogiri'
2
4
  require 'open-uri'
3
5
 
@@ -5,20 +7,36 @@ module UrbanDictionary
5
7
  class Word
6
8
  attr_reader :word, :entries
7
9
 
8
- # Can raise SocketError if unable to connect to specified URL
10
+ # Can raise SocketError if unable to connect to specified URL
9
11
  def self.from_url(url)
10
- html = open(url).read
11
- doc = Nokogiri::HTML(html)
12
+ html = open(url) {|f| f.read }
13
+ from_html(html)
14
+ end
15
+
16
+ def self.from_html(html)
17
+ doc = Nokogiri.HTML(html)
18
+ words = doc.css('.word')
19
+ return nil if words.empty?
20
+
21
+ word = words.first.content.strip
22
+ definitions = doc.css('div.meaning').map {|d| d.content.strip }
23
+ examples = doc.css('.example').map {|e| e.content.strip }
24
+ entries = definitions.zip(examples).map {|d,e| Entry.new(d, e) }
12
25
 
13
- if doc.css('.word').any?
14
- word = doc.css('.word').first.content.strip
15
- definitions = doc.css('.definition').map{|d| d.content.strip }
16
- examples = doc.css('.example').map{|e| e.content.strip }
17
- entries = definitions.zip(examples).map{|d,e| Entry.new(d, e)}
26
+ defined_word?(word, definitions) ? Word.new(word, entries) : nil
27
+ end
18
28
 
19
- Word.new(word, entries)
20
- end
29
+ # Currently when a word has no definition the result page returns the
30
+ # shrug emoticon (¯\_(ツ)_/¯) and the text "There aren't any definitions
31
+ # for [word] yet."
32
+ def self.defined_word?(word, definitions)
33
+ undefined_word = (
34
+ definitions.size == 1 &&
35
+ definitions[0] =~ /^There aren't any definitions for/
36
+ )
37
+ !undefined_word
21
38
  end
39
+ private_class_method :defined_word?
22
40
 
23
41
  def initialize(word, entries)
24
42
  @word = word
@@ -29,9 +47,14 @@ module UrbanDictionary
29
47
  @word
30
48
  end
31
49
 
50
+ def inspect
51
+ "<#{self.class} @word=#{word.inspect}, @entries=#{entries.inspect}>"
52
+ end
53
+
32
54
  def size
33
55
  @word.size
34
56
  end
35
57
  alias :length :size
36
58
  end
37
- end
59
+ end
60
+
@@ -1,9 +1,11 @@
1
1
  require 'uri'
2
2
  require 'net/http'
3
3
 
4
- require 'urban_dictionary/version'
5
- require 'urban_dictionary/word'
6
- require 'urban_dictionary/entry'
4
+ require_relative 'urban_dictionary/version'
5
+ require_relative 'urban_dictionary/cli'
6
+ require_relative 'urban_dictionary/entry'
7
+ require_relative 'urban_dictionary/formatters'
8
+ require_relative 'urban_dictionary/word'
7
9
 
8
10
  module UrbanDictionary
9
11
  DEFINE_URL = 'http://www.urbandictionary.com/define.php'
@@ -16,10 +18,13 @@ module UrbanDictionary
16
18
  def self.random_word
17
19
  url = URI.parse(RANDOM_URL)
18
20
  req = Net::HTTP::Get.new(url.path)
19
- rsp = Net::HTTP.start(url.host, url.port) {|http|
21
+ rsp = Net::HTTP.start(url.host, url.port) do |http|
20
22
  http.request(req)
21
- }
23
+ end
22
24
 
23
- define(rsp['location'])
25
+ Word.from_url(rsp['location'])
24
26
  end
25
- end
27
+ end
28
+
29
+ UrbanDictionary::Formatter.register(:json, UrbanDictionary::JsonFormatter)
30
+ UrbanDictionary::Formatter.register(:plain, UrbanDictionary::PlainFormatter)
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Save a given URL as a test case in the spec/html directory
4
+
5
+ require 'nokogiri'
6
+ require 'open-uri'
7
+ require 'uri'
8
+ require 'cgi'
9
+ require 'time'
10
+
11
+ USAGE = "Usage: #{$PROGRAM_NAME} url"
12
+
13
+ url = ARGV[0]
14
+ abort USAGE unless url
15
+
16
+ html = open(url) {|f| f.read }
17
+ file_name = begin
18
+ uri = URI.parse(url)
19
+ params = CGI.parse(uri.query || "")
20
+ base_name = if params.include?('term')
21
+ params['term'][0]
22
+ else
23
+ "#{uri.host}#{uri.path}"
24
+ end
25
+
26
+ %|#{base_name.downcase.gsub(/[^a-z0-9]/i, '_')}_#{Time.now.strftime("%Y-%m-%d")}.html|
27
+ end
28
+ file_path = File.join(File.expand_path('..', __FILE__), file_name)
29
+
30
+ doc = Nokogiri.HTML(html)
31
+
32
+ # Remove script tags and SVG elements to cut down on size
33
+ doc.css('script,g').remove
34
+
35
+ File.open(file_path, 'w') { |f| f << doc.to_html }