#!/usr/bin/env ruby | |
# A toy program demonstrating consuming REST API and quering and | |
# inserting documents to MongoDB database with Ruby and its standard | |
# library without extra dependencies. | |
# | |
# Usage: | |
# | |
# $ mongod # leave it running | |
# | |
# $ which mongo # the program uses this tool to access MongoDB | |
# /usr/local/bin/mongo | |
# | |
# $ ./define.rb groggy | |
# New term, searching definition from DuckDuckGo... | |
# groggy: weak and unsteady on the feet or in action. | |
# | |
# $ ./define.rb groggy | |
# groggy: weak and unsteady on the feet or in action. | |
require 'cgi' | |
require 'net/http' | |
require 'json' | |
module CLI | |
class << self | |
def exit_with_usage | |
warn "Usage: #{File.basename($0)} term\n\nRequires mongod to be running locally, accessible without authentication." | |
abort | |
end | |
def parse(argv) | |
exit_with_usage if argv.empty? | |
argv.first | |
end | |
def print_definition(definition) | |
puts "#{definition['term']}: #{definition['description']}\n\n" | |
end | |
def print_searching_web | |
warn "New term, searching definition from DuckDuckGo..." | |
end | |
def print_not_found(term) | |
warn "Term not found: #{term}" | |
end | |
end | |
end | |
module HTTP | |
def self.get_json(uri) | |
response = Net::HTTP.get_response(uri) | |
if response.is_a?(Net::HTTPSuccess) | |
JSON.parse(response.body) | |
else | |
fail "Get failed for #{uri}:\n#{response.code} #{response.message}\n#{response.body}" | |
end | |
rescue JSON::ParserError => e | |
fail "Invalid response body, not JSON: #{e}" | |
end | |
end | |
module Mongo | |
class << self | |
def query(db, cmd) | |
result = nil | |
connect(db) do |input, output| | |
input.puts(cmd) | |
input.close | |
result = output.read | |
end | |
result | |
end | |
def insert(db, *cmds) | |
connect(db) do |input| | |
cmds.each { |cmd| input.puts(cmd) } | |
end | |
end | |
private | |
def connect(db, &block) | |
cmd = "mongo --quiet #{db}" | |
status = piped_spawn(cmd, &block) | |
fail "Mongo command failed with exit status #{status.exitstatus}: #{cmd}" if status.exitstatus != 0 | |
end | |
def piped_spawn(cmd) | |
out_rd, out_wr = IO.pipe | |
in_rd, in_wr = IO.pipe | |
pid = spawn(cmd, in: in_rd, out: out_wr) | |
in_rd.close | |
out_wr.close | |
yield in_wr, out_rd | |
ensure_closed(in_wr, out_rd) | |
_, status = Process.waitpid2(pid) | |
status | |
ensure | |
ensure_closed(in_rd, in_wr, out_rd, out_wr) | |
end | |
def ensure_closed(*ios) | |
ios.each { |io| io.close unless io.closed? } | |
end | |
end | |
end | |
module DuckDuckGo | |
NOT_FOUND = {} | |
class << self | |
def define(term) | |
query = CGI.escape("define #{term}") | |
uri = URI("http://api.duckduckgo.com/?q=#{query}&format=json") | |
result = HTTP.get_json(uri) | |
parse_definition(result) | |
end | |
private | |
def parse_definition(response) | |
definition = response["Definition"] | |
if definition && !definition.empty? | |
term, description = definition.split(/\s+definition:\s+/, 2) | |
{'term' => term, 'description' => description} | |
else | |
NOT_FOUND | |
end | |
end | |
end | |
end | |
module Definitions | |
DB_NAME = 'definitions' | |
class << self | |
def find(term) | |
mongo_query = <<-END | |
var regex = new RegExp(RegExp.escape(#{term.to_json})) | |
var result = db.definitions.find({term: regex}, {_id: 0, term: 1, description: 1}).toArray() | |
printjson(result) | |
END | |
result = Mongo.query(DB_NAME, mongo_query) | |
JSON.parse(result) | |
end | |
def add(definition) | |
mongo_insert = %|db.definitions.insert(#{definition.to_json})| | |
Mongo.insert(DB_NAME, mongo_insert) | |
end | |
end | |
end | |
term = CLI.parse(ARGV) | |
found = Definitions.find(term) | |
unless found.empty? | |
found.each { |definition| CLI.print_definition(definition) } | |
exit | |
end | |
CLI.print_searching_web | |
new_definition = DuckDuckGo.define(term) | |
if new_definition.empty? | |
CLI.print_not_found(term) | |
abort | |
end | |
Definitions.add(new_definition) | |
CLI.print_definition(new_definition) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment