#!/usr/bin/env ruby require "rubygems" require "net/http" require "ostruct" require "abbrev" # Example # browse/sort-by-votes - All commands sorted by votes # tagged/163/grep - Commands tagged with 'grep', sorted by date (the default sort order) # matching/ssh/c3No - Search results for the query 'ssh' (note that the final segment is a base64-encoding of the search query) # api_comand_set = [ :browse, :tagged, :matching ] # api_format = [ :plaintext, :json, :rss ] # api_url = "http://www.commandlinefu.com/commands///" def try_require(lib, msg=nil) begin require lib rescue LoadError => e warn "can't load #{lib}: #{e}" warn "\n#{msg}\n" end end try_require "colored" class CommandLineFu API_URL = 'http://www.commandlinefu.com/commands' DEFAULT_OPT = { :page => 1, :command => "browse", :format => "plaintext", :sort_order => "sort-by-votes", :tag_file => "~/.cmdline-fu.tags", :tag_expire_days => 3, :open_browser => false, :colorize => true, # 'black' => 30, # 'red' => 31, # 'green' => 32, # 'yellow' => 33, # 'blue' => 34, # 'magenta' => 35, # 'cyan' => 36, # 'white' => 37 :color_match => 'yellow', :color_desc => 'cyan', :color_footer => 'green' } attr_reader :opt def initialize(user_opt) @opt = OpenStruct.new(DEFAULT_OPT.update(user_config).update(user_opt)) # p @opt # exit end def user_config user_config = File.expand_path "~/.cmdline-fu.conf" if File.exist? user_config return(eval File.read(user_config)) else return {} end end def result if %w(list_tag renew_tag version).include? opt.command return send(opt.command) end page_idx = (opt.page - 1) * 25 url = "#{api_url}/#{page_idx}" if opt.open_browser system("#{browse_cmd} '#{url}'") return end result = open_url(url) return "#{opt.search} NOT FOUND" if result.code != "200" # each entry is separated by blank line , and we need to -1 for page headers. b = result.body.split("\n\n") header = b.shift num_of_entries = b.size body = b.join("\n\n") unless num_of_entries.zero? footer = "\n\n## Page(#{opt.page}):#{page_idx}-#{page_idx+num_of_entries} #{url}\n#{header}" p opt.colorize if opt.colorize body = colorize(body) footer = footer.send(opt.color_footer) end body + footer end end def open_url(url) Net::HTTP.version_1_2 uri = URI.parse(url) proxy = URI.parse(ENV['HTTP_PROXY'] || opt.http_proxy_|| "") http = Net::HTTP::Proxy(proxy.host, proxy.port).start(uri.host, uri.port) request = Net::HTTP::Get.new(uri.request_uri) http.request(request) end private def browse_cmd return opt.browse_cmd if opt.browse_cmd case RUBY_PLATFORM.downcase when /darwin/ then "open" when /linux/ then "firefox" when /mswin/ then 'echo "NOT SUPPORTED for browse command"' end end def api_url command_part = command_set_for(opt.command) url = "#{API_URL}/#{command_part}/#{opt.sort_order}" url << "/#{opt.format}" if opt.format url end def command_set_for(command) { "version" => "version", "browse" => "browse", "using" => "using/#{opt.search}", "by" => "by/#{opt.search}", "matching" => lambda { b64text = [opt.search].pack("m").chomp search = opt.search.tr(' ','-') "matching/#{search}/#{b64text}" }.call, "tagged" => lambda { unless tag_id = tags[opt.search] raise "Tag not found" end "tagged/#{tag_id}/#{opt.search}" }.call }[command] end def version return File.read(File.dirname(File.expand_path(__FILE__)) + "/../VERSION" ) end def colorize(string) string.split("\n").map do |e| e.chomp! (e =~ /^#/) ? e.send(opt.color_desc) : e.gsub(/#{opt.search}/){|m| Colored.colorize m, :foreground => opt.color_match } end.join("\n") end def list_tag candidate = tags.keys.grep(/#{opt.search}/) result = if candidate.size >= 1 colorize(candidate.join("\n")) elsif candidate.size == 0 tags.keys end result end def tags @tags ||= load_tags end def build_tag_file tag_file = File.expand_path opt.tag_file File.open(tag_file, "wb"){ |f| Marshal.dump(scrape_tag, f) } return end alias_method :renew_tag, :build_tag_file # def extract_tag_nokogiri # require 'nokogiri' # h = {} # url = 'http://www.commandlinefu.com/commands/browse' # Nokogiri::HTML(open_url(url).body).xpath('//div//ul/li/a').map { |link| # link['href'] # }.grep(%r|commands\/tagged|).each { |e| # e.scan(%r|/commands/tagged/(\d+)/(.*)|) do |id, name| # h[name] = id # end # } # h # end def scrape_tag ret = try_require "hpricot", <<-EOS if you want to TAG based query, install 'hpricot' gem install hpricot EOS unless ret exit end h = {} url = 'http://www.commandlinefu.com/commands/browse' Hpricot(open_url(url).body). search("div ul li a").map {|e| e.attributes['href'] }.each{ |e| e.scan(%r|/commands/tagged/(\d+)/(.*)|) do |id, name| h[name] = id end } h end def load_tags tag_file = File.expand_path opt.tag_file unless File.exist?(tag_file) build_tag_file end File.open(tag_file, "rb"){ |f| Marshal.load f } end end # TEST # [ # {:command => "browse"}, # {:command => "using", :search =>'find'}, # {:command => "by", :search =>'atoponce'}, # {:command => "matching", :search =>'find'}, # ].each do |opt| # puts "#### #{opt[:command]}" # puts CommandLineFu.new(opt).api_url # end command_table = Abbrev.abbrev(%w(list_tag browse using by tagged matching renew_tag version)) user_opt = {} user_opt[:colorize] = false if ARGV.delete('-n') user_opt[:command] = command_table[ARGV.shift] if ARGV.last == 'o' user_opt[:open_browser] = true user_opt[:format] = false ARGV.pop end page = ARGV.last.to_i user_opt[:page] = page.zero? ? 1 : page user_opt[:search] = ARGV.shift PROGRAM_NAME = File.basename $0 if user_opt[:command].nil? puts <<-EOS #{"Usage".bold} #{PROGRAM_NAME} COMMAND [PAGE] [o] [-n] COMMAND: list_tag [MATCHER], browse, using WORD, by USER, tagged TAG, matching WORD PAGE: 1-999 (defaut: 1) o: open in browser -n: not colorize #{"Example".bold} #{PROGRAM_NAME} renew_tag #{PROGRAM_NAME} list_tag #{PROGRAM_NAME} list_tag vm #{PROGRAM_NAME} browse #{PROGRAM_NAME} browse o #{PROGRAM_NAME} using find #{PROGRAM_NAME} by t9md #{PROGRAM_NAME} tagged install #{PROGRAM_NAME} matching find #{"Abbreviation".bold} Unique abbreviation for command is supported. #{PROGRAM_NAME} r #{PROGRAM_NAME} l #{PROGRAM_NAME} l vm #{PROGRAM_NAME} br #{PROGRAM_NAME} u find 2 #{PROGRAM_NAME} u find 2 o #{PROGRAM_NAME} by t9md #{PROGRAM_NAME} t install #{PROGRAM_NAME} m find EOS exit end begin fu = CommandLineFu.new user_opt ret = fu.result puts ret if ret rescue => e puts e exit end