#!/usr/bin/env perl

use strict;
use warnings;

use App::lookup;

App::lookup->run

__END__

=pod

=head1 NAME

lookup - search the internet from your terminal


=head1 VERSION

lookup version 0.01

=head1 USAGE

  lookup [--help] [--man] [--abbrevs] [--config-file FILE] [--sites]
         [--web-browser COMMAND] [--abbrevs] SITE QUERY


=head1 EXAMPLE

  # search google for the phrase 'perl is pretty awesome'
  lookup google 'perl is pretty awesome'

  # same thing, with abbreviated SITE g (or go, or goo, or goog
  # or ... you get the idea)
  lookup g 'perl is pretty awesome'

  # ditto, but arguments to QUERY are not quoted
  lookup google perl is pretty awesome

=head1 DESCRIPTION

This is a simple command line program which lets you search the internet from
the terminal. It's not exactly the most original idea out there. I personally
have used various programs which pretty much do the same thing, (most of these
even offer better user interface): custom web search feature in Firefox,
similar feature in L<Quicksilver|http://www.qsapp.com/> and
L<Launchy|http://www.launchy.net/>, a fairly sophisticated command line
program called L<googlecl|http://code.google.com/p/googlecl/>, and an emacs
lisp library called L<webjump|http://www.emacswiki.org/emacs/WebJump>.

But this raises quiet a few problems, namely:

=over

=item * The inability to share my own custom sites defined in one program with
another.

=item * Some programs are tied to a specific platform, e.g. Quicksilver on
OSX, Launchy on Windows, and webjump on emacs :p. See also: problem #1.

=back

This program offers a simple solution to said problems. It uses a plain text
file configuration which you can use to add your own custom sites (and
aliases, but more on that later). You can also use it to interface with other
programs, see the section L</INTEGRATION WITH EMACS> for an example.

By default you're only able to search with google. You can search with more
sites by putting the following in the file F<~/.lookuprc> (see
L</CONFIGURATION FILE>).

  [sites]
  bing = http://www.bing.com/search?q=%(query)

Assuming that's the only thing you put in the config file (to make sure that
the abbreviations don't clash), you'll be able to search with
L<bing|http://www.bing.com> with either one of the following commands:

  lookup bing foobar
  lookup bin foobar
  lookup bi foobar
  lookup b foobar

=head1 OPTIONS

=head2 REQUIRED

=over

=item SITE

The site name, which can be abbreviated as long as the name remains unique.

=item QUERY

The query string. For convenience, the arguments do not have to be
quoted/protected from the shell since the rest of the arguments (after
B<SITE>) are assumed to be the query string and eventually will be joined with
the space character. These two commands work exactly the same:

  lookup google 'foo bar baz'

vs

  lookup google foo bar baz

=back

=head2 OPTIONAL

=over

=item -h, --help

Print short help message and exit.

=item -m, --man

Print the full documentation and exit. Make sure to read this document if you
want to add your own sites or aliases.

=item -v, --version

Print this program version and exit.

=item -c, --config-file FILE

Use a different configuration file. Default location is F<~/.lookuprc>.

=item -a, --abbrevs

List all sites with their valid abbreviations and exit.

=item -s, --sites

List all site names with their corresponding url and exit.

=item -w, --web-browser COMMAND

Specify a different command to run the web browser. By default this program
uses whatever command does the module L<Browser::Open> return to open the web
browser. You can use this option to specify other command to execute.

B<NOTE:> if you're using cygwin, you currently must specify this option (see
L</CAVEATS, BUGS AND TODOS>).

  lookup google foo -w 'cygstart'

Other example:

  lookup google foo -w 'open -a Safari'

On OSX, the above command will make this program use the application C<Safari>
(obviously it won't work on other platforms).

=back

=head1 CONFIGURATION FILE

You can manage user-defined sites and their aliases by putting the information
in the configuration file, by default located at F<~/.lookuprc> (although you
can change it with the option B<--config-file>).

It uses the ubiquitous INI file syntax. Sites information should be put under
the heading called C<[sites]> and aliases under the heading C<[alias]>. Lines
that are starting with the characters C<#> or C<;> are ignored. Whitespace in
either the key or the value is legal.

=head2 SITES

The section C<[sites]> is where we defined our own sites. Each item contains a
pair of key and value, where the key is the name of the site, and the value is
the url. For example:

  [sites]
  metacpan     = https://metacpan.org/search?q=%(query)

This add a new site called C<metacpan> which is associated with the url C<<
https://metacpan.org/search?q=%(query) >>. The string C<%(query)> will be
replaced with the query string supplied to the option B<QUERY>.

=head2 ALIASES

Aliases let us shorten the site name, where the key is the alias name and the
value is an already existing site name (this program will print a warning
message if you specify an alias that doesn't point to a valid site).

Continuing from the previous example, we can shorten the name C<metacpan> into
C<m> by putting the following lines in the configuration file:

  [alias]
  m = metacpan

Now we can search for module called C<Some::Module> on
L<metacpan|http://metacpan.org> by invoking the command:

  lookup m Some::Module

=head2 EXAMPLE OF CONFIGURATION FILE

Here's an example of configuration file, feel free to use it as a starting
point.

  [sites]

  duckduckgo   = http://duckduckgo.com/?q=%(query)
  emacswiki    = http://google.com/search?q=site:emacswiki.org %(query)
  github       = https://github.com/search?q=%(query)
  google lucky = http://google.com/search?btnI=1&q=%(query)
  imdb         = http://www.imdb.com/find?q=%(query)
  metacpan     = https://metacpan.org/search?q=%(query)
  perldoc      = http://perldoc.perl.org/search.html?q=%(query)
  search cpan  = http://search.cpan.org/search?query=%(query)&mode=all
  wikipedia    = http://en.wikipedia.org/?search=%(query)
  youtube      = http://www.youtube.com/results?q=%(query)

  [alias]

  ddg   = duckduckgo
  g     = google
  lucky = google lucky
  wi    = wikipedia
  y     = youtube

=head1 EXTRAS

=head2 INTEGRATION WITH EMACS

Short demo <http://i.minus.com/inRg3aL2wGGQ5.gif>.

This is my preferred way of using this program, so I guess bit makes sense
that this section is a bit long (mostly filled with emacs lisp code). You can
safely ignore this section if you don't use emacs.

  (require 'thingatpt)

  (defgroup lookup nil
    "Run the command lookup"
    :prefix "lookup-"
    :group 'tools
    :group 'processes)

  (defcustom lookup-command "lookup"
    "The lookup command.

  If you're using cygwin, you should modify this variable like so
  (see the CAVEATS section in lookup documentation):

      (when (eq 'system-type 'cygwin)
        (setq lookup-command \"lookup -w 'cygstart'\"))"
    :group 'lookup :type 'string)

  (defcustom lookup-prompt-function
    (if (fboundp 'ido-completing-read)
        'ido-completing-read
      'completing-read)
    "The function used to prompt user for lookup sites")

  (defcustom lookup-sites-mode-alist nil
    "Alist mapping major-modes to their associated sites.
  Each cons cell in this alist contains the name of the major-mode
  as the associated key and the name of the sites (multiple sites
  are accepted) as the associated value.

  For example, to associate `ruby-mode' with the sites rubygems and
  ruby-doc (assuming both sites are defined in the configuration
  file ~/.lookuprc):

    (add-to-list 'lookup-sites-mode-alist '(ruby-mode \"rubygems\" \"ruby-doc\")"
    :group 'lookup :type 'alist)

  (defvar lookup-hist nil
    "History of previous queries to the command lookup")

  (defvar lookup-sites-cache nil
    "Store the list of sites accepted by lookup")

  (defun lookup--get-sites (&optional recache)
    "Get a list of sites accepted by lookup by returning the value of variable `lookup-sites-cache'.
  If it's not been set yet (or if the optional argument RECACHE is
  non-nil), set its value by parsing the output of the command
  `lookup --sites'"
    (when (or recache
              (not lookup-sites-cache))
      (message "Please wait, caching sites...")
      (let ((output (shell-command-to-string
                     (format "%s --sites" lookup-command)))
            sites)
        (with-temp-buffer
          (insert output)
          (sort-lines t (point-min) (point-max))
          (goto-char (point-min))
          (while (re-search-forward "^- \\(.+\\)\s+:" nil t)
            (push (match-string 1) sites)))
          ;; TODO: make the regexp above less stupid, so we don't have to strip
          ;; the trailing whitespace
        (setq lookup-sites-cache (mapcar
                                  (lambda (site)
                                    (replace-regexp-in-string
                                     "\s*$" "" site))
                                  sites))))
    lookup-sites-cache)

  (defun lookup--prompt-for-query (&optional site)
    "Prompt user for query argument to the command lookup.
  The prompt message is adjusted depending on the existence of
  symbol under point and whether the argument SITE is supplied or
  not."
    (let* ((word (thing-at-point 'symbol))
           (query (read-string
                   (cond ((and word site)
                          (format "Query %s (default %s): " site word))
                         (site
                          (format "Query %s: " site))
                         (word
                          (format "Query (default %s): " word))
                         (t
                          "Query: "))
                   nil 'lookup-hist word)))
      (when (string= "" query)
        (error "The query can not be empty"))
      query))

  (defvar lookup-sites-hist nil
    "History of previously searched sites")

  (defun lookup--prompt-for-sites (sites)
    (funcall lookup-prompt-function "Site: " sites nil t nil 'lookup-sites-hist))

  (defun lookup-quick ()
    "Call `lookup' with predefined sites based on current `major-mode'.
  This command will complain if current `major-mode' is not
  associated with any sites (see the variable
  `lookup-sites-mode-alist' on how to associate a major-mode with
  lookup sites)"
    (interactive)
    (let ((possible-sites (cdr (assoc major-mode lookup-sites-mode-alist)))
          query sitename)
      (unless possible-sites
        (error "%s is not associated with any site" major-mode))
      (setq sitename
            (if (> (length possible-sites) 1)
                (lookup--prompt-for-sites possible-sites)
              (car possible-sites)))
      (setq query (lookup--prompt-for-query sitename))
      (lookup sitename query)))

  (defun lookup (sitename query &optional recache)
    "Interact with the program lookup within emacs.
  SITENAME and QUERY will be passed to lookup as its command line
  arguments.

  When used interactively, prompt user the list of available sites
  for SITENAME and the query string QUERY (defaults to word at
  point). With one prefix-arg (the argument RECACHE if called from
  lisp), recache the sites first."
    (interactive
     (let ((sites (lookup--get-sites current-prefix-arg)))
       (list
        (lookup--prompt-for-sites sites)
        (lookup--prompt-for-query)
        current-prefix-arg)))
    (shell-command
     (format "%s '%s' %s" lookup-command sitename query)))

Evaluate it, and optionally assign the command C<lookup> and C<lookup-quick>
to handy keybindings:

  ;; mnemonic: w for web, (yes, the command "lookup-quick" uses the capital
  ;; "w")
  (global-set-key (kbd "C-c w") 'lookup)
  (global-set-key (kbd "C-c W") 'lookup-quick)

=head1 CAVEATS, BUGS, AND TODOS

=over

=item * Opening web browser in cygwin does not work

Due to this L<bug|https://rt.cpan.org/Public/Bug/Display.html?id=85972> in
L<Browser::Open>.

=back

=head1 AUTHOR

Ahmad Syaltut <Isyaltut@gmail.com>
