Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#!/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