urban_dictionary 0.0.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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 }
|