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 +15 -0
- data/.gitignore +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +1 -1
- data/LICENSE.md +21 -0
- data/README.md +15 -1
- data/Rakefile +4 -0
- data/bin/urban_dictionary +3 -36
- data/lib/urban_dictionary/cli.rb +106 -0
- data/lib/urban_dictionary/entry.rb +4 -0
- data/lib/urban_dictionary/formatters.rb +58 -0
- data/lib/urban_dictionary/version.rb +1 -1
- data/lib/urban_dictionary/word.rb +34 -11
- data/lib/urban_dictionary.rb +12 -7
- data/spec/html/add_html_example.rb +35 -0
- data/spec/html/on_fleek_2016-02-15.html +1091 -0
- data/spec/html/sisyphus_2016-02-15.html +578 -0
- data/spec/spec_helper.rb +30 -2
- data/spec/urban_dictionary/cli_spec.rb +72 -0
- data/spec/urban_dictionary/entry_spec.rb +1 -1
- data/spec/urban_dictionary/word_spec.rb +26 -18
- data/spec/urban_dictionary_spec.rb +15 -14
- data/urban_dictionary.gemspec +5 -3
- metadata +53 -27
- data/Gemfile.lock +0 -28
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
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
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
data/bin/urban_dictionary
CHANGED
@@ -1,38 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby -KU
|
2
|
-
|
3
|
-
require 'optparse'
|
2
|
+
require_relative '../lib/urban_dictionary'
|
4
3
|
|
5
|
-
|
6
|
-
|
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
|
@@ -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,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
|
-
|
10
|
+
# Can raise SocketError if unable to connect to specified URL
|
9
11
|
def self.from_url(url)
|
10
|
-
html = open(url).read
|
11
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
20
|
-
|
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
|
+
|
data/lib/urban_dictionary.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'net/http'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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)
|
21
|
+
rsp = Net::HTTP.start(url.host, url.port) do |http|
|
20
22
|
http.request(req)
|
21
|
-
|
23
|
+
end
|
22
24
|
|
23
|
-
|
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 }
|