├── .gitignore ├── .rspec ├── Rakefile ├── Gemfile ├── .yardopts ├── specifications ├── spec_helper.rb ├── response_spec.rb └── client_spec.rb ├── library ├── wolfram-alpha.rb └── wolfram-alpha │ ├── subpod.rb │ ├── response.rb │ ├── pod.rb │ └── client.rb ├── wolfram-alpha.gemspec └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | documentation/ 2 | executables/ 3 | .yardoc 4 | Gemfile.lock 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format nested 3 | --default-path specifications 4 | -I specifications 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | RSpec::Core::RakeTask.new :spec 4 | 5 | task default: :spec 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "nokogiri" 4 | 5 | group :development do 6 | gem "rspec" 7 | end 8 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --output-dir documentation/ 2 | --readme README.md 3 | --title Wolfram-Alpha 4 | --protected 5 | library/**/*.rb 6 | -------------------------------------------------------------------------------- /specifications/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | $:.unshift File.dirname(__FILE__) + '/../library' 4 | require 'wolfram-alpha' 5 | 6 | unless ENV['WALPHA'] 7 | warn "WARNING: WALPHA environment variable not set, specs will fail!" 8 | end 9 | -------------------------------------------------------------------------------- /specifications/response_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WolframAlpha::Response do 4 | subject do 5 | WolframAlpha::Response.new Nokogiri::XML::Document.new 6 | end 7 | 8 | describe "default attributes" do 9 | it "should have a list of pods" do 10 | expect(subject.pods).to be_an_instance_of Array 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /library/wolfram-alpha.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'cgi' 4 | require 'net/http' 5 | require 'nokogiri' 6 | 7 | require 'wolfram-alpha/pod' 8 | require 'wolfram-alpha/client' 9 | require 'wolfram-alpha/subpod' 10 | require 'wolfram-alpha/response' 11 | 12 | # Wolfram|Alpha introduces a fundamentally new way to get knowledge and answers— 13 | # not by searching the web, but by doing dynamic computations based on a vast collection of 14 | # built-in data, algorithms, and methods. 15 | # 16 | # @author Mikkel Kroman 17 | module WolframAlpha 18 | # The current version of the WolframAlpha library. 19 | Version = "0.3.1" 20 | 21 | # The API request-uri. 22 | RequestURI = URI "http://api.wolframalpha.com/v2/query" 23 | end 24 | -------------------------------------------------------------------------------- /library/wolfram-alpha/subpod.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WolframAlpha 4 | class Subpod 5 | # Construct a new pod with an assigned element. 6 | def initialize parent, element 7 | @parent = parent 8 | @element = element 9 | end 10 | 11 | # Returns whether the subpod contains a plaintext element. 12 | def plaintext? 13 | not plaintext.nil? 14 | end 15 | 16 | # Returns the plaintext element as text. 17 | # 18 | # @return [String] the plain text. 19 | def plaintext 20 | @plaintext ||= @element.at_css("plaintext").text 21 | end 22 | 23 | # Inspect the subpod. 24 | def inspect 25 | %{#<#{self.class.name} plaintext: #{plaintext.inspect}>} 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /wolfram-alpha.gemspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gem build 2 | # encoding: utf-8 3 | 4 | Gem::Specification.new do |spec| 5 | spec.name = "wolfram-alpha" 6 | spec.version = "0.3.1" 7 | spec.summary = "Wolfram|Alpha introduces a fundamentally new way to get knowledge and answers — not by searching the web, but by doing dynamic computations based on a vast collection of built-in data, algorithms, and methods." 8 | 9 | spec.homepage = "https://github.com/mkroman/wolfram-alpha" 10 | spec.license = "MIT" 11 | spec.author = "Mikkel Kroman" 12 | spec.email = "mk@uplink.io" 13 | spec.files = Dir['library/**/*.rb'] 14 | 15 | spec.add_dependency "nokogiri" 16 | spec.add_development_dependency "rspec" 17 | 18 | spec.require_path = "library" 19 | spec.required_ruby_version = ">= 1.9.1" 20 | end 21 | -------------------------------------------------------------------------------- /library/wolfram-alpha/response.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WolframAlpha 4 | # This is a response object that wraps the response data in a 5 | # easily managable object. 6 | class Response 7 | include Enumerable 8 | attr_reader :pods 9 | 10 | # Construct a new response for a +document+. 11 | def initialize document 12 | @pods = document.css("pod").map { |element| Pod.new element } 13 | end 14 | 15 | # Return the first occurence of a pod with the id of +id+. 16 | def [] id 17 | find { |pod| pod.id == id } 18 | end 19 | 20 | # Calls the given block once for each element in self, passing that element as a parameter. 21 | # 22 | # An Enumerator is returned if no block is given. 23 | def each 24 | return @pods.to_enum unless block_given? 25 | 26 | @pods.each do |pod| 27 | yield pod 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /library/wolfram-alpha/pod.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WolframAlpha 4 | # A wrapper for the pod element. 5 | class Pod 6 | # @return [Array] an list of sub-pods in this pod. 7 | attr_reader :subpods 8 | 9 | # Construct a new pod with an assigned element, then extract all subpods from it. 10 | def initialize element 11 | @element = element 12 | 13 | extract_subpods 14 | end 15 | 16 | # Returns whether the pod has any subpods. 17 | def subpods? 18 | @subpods and @subpods.any? 19 | end 20 | 21 | # Returns the pod id. 22 | # 23 | # @return [String] the pod id. 24 | def id 25 | @element["id"] 26 | end 27 | 28 | # Returns the pod title. 29 | # 30 | # @return [String] the pod title. 31 | def title 32 | @element["title"] 33 | end 34 | 35 | # Returns the pod scanner. 36 | # 37 | # @return [String] the pod scanner. 38 | def scanner 39 | @element["scanner"] 40 | end 41 | 42 | # Returns the pod position. 43 | # 44 | # @return [Fixnum] the pod position. 45 | def position 46 | @element["position"].to_i 47 | end 48 | 49 | # Inspect the pod. 50 | def inspect 51 | %{#<#{self.class.name} id: #{id.inspect} title: #{title.inspect} scanner: #{scanner.inspect} position: #{position.inspect} @subpods=#{@subpods.inspect}>} 52 | end 53 | 54 | private 55 | 56 | # Extract the subpods. 57 | def extract_subpods 58 | @subpods = @element.css("subpod").map { |element| Subpod.new self, element } 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /specifications/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WolframAlpha::Client do 4 | subject do 5 | WolframAlpha::Client.new ENV['WALPHA'] || 'my-app-token' 6 | end 7 | 8 | describe "default attributes" do 9 | before do 10 | class WolframAlpha::Client 11 | attr_accessor :http, :options 12 | end 13 | end 14 | 15 | it "should have a token" do 16 | expect(subject.token).to eq 'my-app-token' 17 | end 18 | 19 | it "should have a http instance" do 20 | expect(subject.http).to be_an_instance_of Net::HTTP 21 | end 22 | 23 | it "should have an options hash" do 24 | expect(subject.options).to be_a Hash 25 | end 26 | 27 | it "should merge options with default options" do 28 | expect(subject.options).to include :timeout 29 | end 30 | 31 | it "should throw an error when no token specified" do 32 | expect { subject.class.new }.to raise_error 33 | end 34 | end 35 | 36 | describe "#request_url" do 37 | let(:simple_query) { "1 + 1" } 38 | 39 | it "should return a uri" do 40 | result = subject.send :request_url, simple_query 41 | 42 | expect(result).to be_an_kind_of URI 43 | end 44 | 45 | it "should merge query parameters with input, app id and options" do 46 | result = subject.send :request_url, simple_query 47 | 48 | # note: result.query returns a string 49 | expect(result.query).to include "appid", "input", "timeout" 50 | end 51 | end 52 | 53 | describe "querying wolfram-alpha" do 54 | it "should return a response" do 55 | response = subject.query "1 + 1" 56 | 57 | expect(response).to be_an_instance_of WolframAlpha::Response 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /library/wolfram-alpha/client.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WolframAlpha 4 | class Client 5 | # The Wolfram|Alpha API application id, which is set upon initialization. 6 | attr_accessor :token 7 | 8 | # The default options for a new client, it is merged with the options 9 | # set upon initialization. 10 | Options = { 11 | timeout: 15 12 | } 13 | 14 | # Initialize a new client to communicate with the Wolfram|Alpha API. 15 | # 16 | # @param [String] token The developers API-id that can be freely 17 | # obtained from http://products.wolframalpha.com/api/ 18 | # @param [Hash] options A set of options that may be put in the request. 19 | # 20 | # @see DefaultOptions 21 | def initialize token, options = {} 22 | @http = Net::HTTP.new RequestURI.host, RequestURI.port 23 | @token = token or raise "Invalid token" 24 | @options = Options.merge options 25 | end 26 | 27 | # Compute the result of +input+, and return a new response. 28 | # 29 | # @param [String] input The input query. 30 | # 31 | # @return [Response] The parsed response object. 32 | def query input 33 | response = @http.get request_url(input).request_uri 34 | document = Nokogiri::XML response.body 35 | 36 | if document.root.name == "queryresult" 37 | Response.new document 38 | else 39 | raise "Invalid response" 40 | end 41 | end 42 | 43 | private 44 | 45 | # Mash together the request url, this is where the request-uri is modified 46 | # according to the client options. 47 | # 48 | # @param [String] input The input query. 49 | # 50 | # @return [String] The complete, formatted uri. 51 | def request_url input 52 | query = { "appid" => @token, "input" => "#{input}" }.merge @options 53 | 54 | RequestURI.dup.tap do |this| 55 | this.query = URI.encode_www_form query 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WolframAlpha 2 | 3 | Wolfram|Alpha introduces a fundamentally new way to get knowledge and answers— not by searching the web, but by doing dynamic computations based on a vast collection of built-in data, algorithms, and methods. 4 | 5 | ## Installation 6 | 7 | `gem install wolfram-alpha` 8 | 9 | And that's it! 10 | 11 | ## Usage 12 | 13 | Creating a client instance: 14 | 15 | require 'wolfram-alpha' 16 | 17 | options = { "format" => "plaintext" } # see the reference appendix in the documentation.[1] 18 | 19 | client = WolframAlpha::Client.new "MY-API-ID", options 20 | 21 | **External links:** 22 | 23 | 1. [WolframAlpha API: Documentation](http://products.wolframalpha.com/api/documentation.html) 24 | 25 | Querying the engine: 26 | 27 | response = client.query "5 largest countries" 28 | 29 | Handling responses: 30 | 31 | input = response["Input"] # Get the input interpretation pod. 32 | result = response.find { |pod| pod.title == "Result" } # Get the result pod. 33 | 34 | puts "#{input.subpods[0].plaintext} = #{result.subpods[0].plaintext}" 35 | 36 | # 5 largest countries | by total area = 1 | Russia | 1.708×10^7 km^2 | 37 | # 2 | Canada | 9.985×10^6 km^2 | 38 | # 3 | United States | 9.631×10^6 km^2 | 39 | # 4 | China | 9.597×10^6 km^2 | 40 | # 5 | Brazil | 8.515×10^6 km^2 | 41 | # (vertical ellipsis) | | | 42 | 43 | Beware that `WolframAlpha::Response#[]` is finding a pod by its id, and not by its title. 44 | 45 | ## Contributing 46 | 47 | 1. Fork it! 48 | 2. Create your feature branch: `git checkout -b my-new-feature` 49 | 3. Commit your changes: `git commit -am 'Add some feature'` 50 | 4. Push to the branch: `git push origin my-new-feature` 51 | 5. Submit a pull request 52 | 53 | ## History 54 | 55 | __v0.3__ 56 | 57 | + Refactored the API. 58 | + Tidied up the code. 59 | 60 | __v0.2__ 61 | 62 | + Fixed query escaping. 63 | + Added documentation. 64 | 65 | __v0.1__ 66 | 67 | + Initial release. 68 | 69 | ## Credits 70 | 71 | Mikkel Kroman <> 72 | 73 | ## License 74 | Copyright (c) 2013 Mikkel Kroman 75 | 76 | Permission to use, copy, modify, and distribute this software for any 77 | purpose with or without fee is hereby granted, provided that the above 78 | copyright notice and this permission notice appear in all copies. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 81 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 82 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 83 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 84 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 85 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 86 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 87 | --------------------------------------------------------------------------------