module Boson::Commands::WebCore extend self def config #:nodoc: commands = { 'get'=>{ :desc=>"Gets the body of a url", :args=>[['url'],['options', {}]]}, 'post'=>{ :desc=>'Posts to a url', :args=>[['url'],['options', {}]]}, 'build_url'=>{ :desc=>"Builds a url, escaping the given params", :args=>[['url'],['params']]}, 'browser'=>{ :desc=>"Opens urls in a browser on a Mac"}, 'install'=>{ :desc=>"Installs a library by url. Library should then be loaded with load_library.", :args=>[['url'],['options', {}]], :options=> { :name=>{:type=>:string, :desc=>"Library name to save to"}, :force=>{:type=>:boolean, :desc=>'Overwrites an existing library'}, :default=>{:type=>:boolean, :desc=>'Adds library as a default library to main config file'}, :module_wrap=>{:type=>:boolean, :desc=>"Wraps a module around install using library name"}, :method_wrap=>{:type=>:boolean, :desc=>"Wraps a method and module around installed library using library name"}} } } {:library_file=>File.expand_path(__FILE__), :commands=>commands, :namespace=>false} end # Requires libraries only once before defining method with given block def self.def_which_requires(meth, *libs, &block) define_method(meth) do |*args| libs.each {|e| require e } define_method(meth, block).call(*args) end end def_which_requires(:get, 'net/https') do |*args| url, options = args[0], args[1] || {} url = build_url(url, options[:params]) if options[:params] Get.new(url).request(options) end def_which_requires(:build_url, 'cgi') do |url, params| url + (url[/\?/] ? '&' : "?") + params.map {|k,v| v = v.is_a?(Array) ? v.join(' ') : v.to_s "#{k}=#{CGI.escape(v)}" }.join("&") end def_which_requires(:post, 'uri', 'net/http') do |*args| url, options = args[0], args[1] || {} (res = Net::HTTP.post_form(URI.parse(url), options)) && res.body end def install(url, options={}) #:nodoc: options[:name] ||= strip_name_from_url(url) return puts("Please give a library name for this url.") if options[:name].empty? filename = File.join ::Boson.repo.commands_dir, "#{options[:name]}.rb" return puts("Library name #{options[:name]} already exists. Try a different name.") if File.exists?(filename) && !options[:force] file_string = get(url) or raise "Unable to fetch url" file_string = "# Originally from #{url}\n"+file_string file_string = wrap_install(file_string, options) if options[:method_wrap] || options[:module_wrap] File.open(filename, 'w') {|f| f.write file_string } Boson.repo.update_config {|c| (c[:defaults] ||= []) << options[:name] } if options[:default] puts "Saved to #{filename}." end # non-mac users should override this with the launchy gem def browser(*urls) system('open', *urls) end private def wrap_install(file_string, options) indent = " " unless (mod_name = ::Boson::Util.camelize(options[:name])) return puts("Can't wrap install with name #{options[:name]}") end file_string.gsub!(/(^)/,'\1'+indent) file_string = "def #{options[:name]}\n#{file_string}\nend".gsub(/(^)/,'\1'+indent) if options[:method_wrap] "module #{mod_name}\n#{file_string}\nend" end def strip_name_from_url(url) url[/\/([^\/.]+)(\.[a-z]+)?$/, 1].to_s.gsub('-', '_').gsub(/[^a-zA-Z_]/, '') end # Used by the get command to make get requests and optionally parse json and yaml. # Ruby 1.8.x is dependent on json gem for parsing json. # See Get.request for options a request can take. class Get FORMAT_HEADERS = { :json=>%w{application/json text/json application/javascript text/javascript}, :yaml=>%w{application/x-yaml text/yaml} } #:nodoc: def initialize(url, options={}) @url, @options = url, options end # Returns the response body string or a parsed data structure. Returns nil if request fails. By default expects response # to be 200. # ==== Options: # [:any_response] Returns body string for any response code. Default is false. # [:parse] Parse the body into either json or yaml. Expects a valid format or if true autodetects one. # Default is false. # [:raise_error] Raises any original errors when parsing or fetching url instead of handling errors silently. def request(options={}) @options.merge! options body = get_body body && @options[:parse] ? parse_body(body) : body end private # Returns body string if successful or nil if not. def get_body uri = URI.parse(@url) @response = get_response(uri) (@options[:any_response] || @response.code == '200') ? @response.body : nil rescue @options[:raise_error] ? raise : puts("Error: GET '#{@url}' -> #{$!.class}: #{$!.message}") end def get_response(uri) net = Net::HTTP.new(uri.host, uri.port) net.verify_mode = OpenSSL::SSL::VERIFY_NONE if uri.scheme == 'https' net.use_ssl = true if uri.scheme == 'https' net.start {|http| http.request_get(uri.request_uri) } end # Returns nil if dependencies or parsing fails def parse_body(body) format = determine_format(@options[:parse]) case format when :json unless ::Boson::Util.safe_require 'json' return puts("Install the json gem to parse json: sudo gem install json") end JSON.parse body when :yaml YAML::load body else puts "Can't parse this format." end rescue @options[:raise_error] ? raise : puts("Error while parsing #{format} response of '#{@url}': #{$!.class}") end def determine_format(format) return format.to_sym if %w{json yaml}.include?(format.to_s) return :json if FORMAT_HEADERS[:json].include?(@response.content_type) return :yaml if FORMAT_HEADERS[:yaml].include?(@response.content_type) nil end end end