Scripting Razor Runner - Razor-qt/razor-qt GitHub Wiki

Razor runner scripting

Note: This is feature is expected to be in Razor-qt 0.6.0, once it is released. Until then you can try i out by getting Razor from git

What is this?

Razor-runner is Razor-Qt''s application launcher. Out of the box, it will match what you type against installed desktop-applications, and show what it finds in a list, from where you can choose.

It is possible to extend what razor-runner suggest with scripts. Here we describe how.

How it works

A razor-runner script should read search-terms from standard input (line by line) - and write suggestions to standard out.

Here''s an example script. With this script added to razor-runner, razor-runner will suggest folders below your home directory. If you click one of the suggestions, the folder will open in you default file manager.

#!/usr/bin/env bash
cd $HOME
while read searchterm; do
    echo "---"
    if [ ${#searchterm} -ge 3 ](/Razor-qt/razor-qt/wiki/-${#searchterm}--ge-3-); then 
        find $HOME -maxdepth 3 -type d -iname "*${searchterm}*" | while read pathtodir; do
            dirname=`basename "$pathtodir"` 
            echo "- icon : folder"
            echo "  title : $dirname"
            echo "  comment: $pathtodir"
            echo "  tooltip : Open '$pathtodir' in filemanager" 
            echo "  command : xdg-open \"$pathtodir\""
        done;
    fi
    echo "..."
done

The script reads input, line by line, and searches from $HOME and 3 levels down for a folder with a matching name. For each folder it finds, it outputs data that can be used to form an item in razor-runner

If this script is run from the command line and given the input: 'razor-qt\n', it could output something like this:

---
- icon : folder
  title : backup-2013-04-28-razor-qt
  comment: /home/christian/projekter/backup-2013-04-28-razor-qt
  tooltip : Open '/home/christian/projekter/backup-2013-04-28-razor-qt' in filemanager
  command : xdg-open "/home/christian/projekter/backup-2013-04-28-razor-qt"
- icon : folder
  title : backup-razor-qt
  comment: /home/christian/projekter/backup-razor-qt
  tooltip : Open '/home/christian/projekter/backup-razor-qt' in filemanager
  command : xdg-open "/home/christian/projekter/backup-razor-qt"
- icon : folder
  title : razor-qt
  comment: /home/christian/projekter/razor-qt
  tooltip : Open '/home/christian/projekter/razor-qt' in filemanager
  command : xdg-open "/home/christian/projekter/razor-qt"
- icon : folder
  title : razor-qt-backup-2013-04-22
  comment: /home/christian/projekter/razor-qt-backup-2013-04-22
  tooltip : Open '/home/christian/projekter/razor-qt-backup-2013-04-22' in filemanager
  command : xdg-open "/home/christian/projekter/razor-qt-backup-2013-04-22"
- icon : folder
  title : razor-qt-sided
  comment: /home/christian/projekter/razor-qt-sided
  tooltip : Open '/home/christian/projekter/razor-qt-sided' in filemanager
  command : xdg-open "/home/christian/projekter/razor-qt-sided"
- icon : folder
  title : razor-qt-gem
  comment: /home/christian/projekter/razor-qt-gem
  tooltip : Open '/home/christian/projekter/razor-qt-gem' in filemanager
  command : xdg-open "/home/christian/projekter/razor-qt-gem"
...

Notes:

  • The output is a YAML document, and it contains a list of maps.

  • Each map will become a line (or an item) in razor-runners drop-down list.

  • Each map contains: 'icon', 'title', 'comment', 'tooltip' and 'command'. The meaning of those are:

    • icon: The icon that is shown for the item in razor-runner's drop-down list. It can be an absolute path (i.e. /path/to/my/icon.png) or just an icon name, in which case it will be sought in the users icon-theme.
    • title: The short description of the item
    • comment: The long description of the item
    • tooltip: Shown in a bubble when the user puts the mouse-cursor over the item
    • command: The command that is executed if the item is selected (clicked).

Configuring scripts

To make razor-runner run a script, it has to be configured in razor-runner.conf. In our example, razor-runner.conf should contain a section like this:

[external-providers]
1\name=Folders
1\executable=/home/christian/projekter/RunnerPlugins/folders.sh
size=1

The section 'external-providers' contains an array - one element for each script. Here we have one script, hence the key 'size' has value 1. Element number 1 has name=Folders which is used by razor-runner to reference the script. It has executable=/home/christian/projekter/RunnerPlugins/folders.sh which is the full path to the script.

We do not (yet) have an gui-application to do this configuration, so razor-runner.conf will have to be edited by hand.

The contract

  • Razor runner writes the search terms to the scripts standard-input. Every time the content of razor-runners search bar changes, the script will be fed a line containing the search term as it now looks.

So if the user types:

    razor 

in the input-field, the script will get these 5 lines in standard input:

    r\n
    ra\n
    raz\n
    razo\n
    razor\n
  • The script must produce a YAML document containing one list, containing maps. Each map must be string to string and should contain the five keys: icon, * title, comment, tooltip and command. The meaning of these are as explained above. The keys title and command are mandatory. icon, comment and tooltip may be omitted.

  • The maps can contain other keys. They will be ignored. In the future the set of keys recognized by razor-runner may be extended.

  • When the script has produced a document, the items of that document stays in razor-runners dropdown list until the script produces a new document. To eraze all items previously produced, the script can send an empty document (see below).

  • The YAML document must start with the line
    ---
    and end with the line
    ...

  • If the script has no suggestions to offer, it signals this with an empty YAML document:

    ...  
    \---
    

More examples

Below are 2 more examples of what you can do with razor-runner scripting. One in bash and one in ruby. Of course you can use any programming language you wish.

Search google

#!/usr/bin/env bash
# If searchterm starts with 'g:' produce an item
# that offers search for whats after 'g:'
# Example: 'g:razor qt' --> search google for 'razor qt'
while read line; do
    echo "---"
    if [ "g:" == ${line:0:2} && ${#line} -gt 2 ](/Razor-qt/razor-qt/wiki/-"g:"-==-${line:0:2}-&&-${#line}--gt-2-); then
        escaped_line=`echo "${line:2}" | sed -e 's/\s\s*/%20/g'`
        echo "- title: $line"
        echo "  comment: Search google for '${line:2}'"
        echo "  tooltip: Search google for '${line:2}'"
        echo "  command: xdg-open http://www.google.com/search?q=${escaped_line}"
        echo "  icon: /home/christian/projekter/RunnerPlugins/google.png"
    fi
    echo "..."
done

Search chromium history

#!/usr/bin/env ruby
# For this to work, sqlite3 must be installed (and chromium - obviously). It is assumed that 
# chromium keeps it's history in $HOME/.config/chromium/Default/History
# Oh, and you'll need ruby :-)

STDOUT.sync = true

require 'sqlite3'
require 'fileutils'
require 'yaml'
require 'set'

class HistorySearcher

    def establish_db_connection
        puts "Connecting..."

        @history_db.close if @history_db
        
        # This sucks. Chromium keeps a lock on it's history database while running, and I've not been
        # able to figure out a way to read it while that lock is on, so we have to make a full copy.
        dbfile = File.join(ENV['HOME'], '.config/chromium/Default/History')
        dbfilecopy = File.join(ENV['HOME'], '.cache/razor_runner_chromium_history_copy');
        FileUtils.copy(dbfile, dbfilecopy)

        @history_db = SQLite3::Database.new(dbfilecopy)
        @connection_time = Time.new
    end

    # Returns a list of items. If called with razor-qt, it could be something like:
    #
    # [{ 'title' => 'razor-qt.org/',
    #     'comment' => 'Razor-qt',
    #     'icon' => 'text-html',
    #     'tooltip' => 'open "http://razor-qt.org/" in browser',
    #     'command' => 'xdg-open "http://razor-qt.org/' },
    #  { 'title' => 'razor-qt.org/install/',
    #     'comment' => 'Razor-qt: Download',
    #     'icon' => 'text-html',
    #     'tooltip' => "open 'http://razor-qt.org/install/' in browser",
    #     'command' => "xdg-open 'http://razor-qt.org/install'" },
    #   .
    #   .
    #   . 
    # ]
    def searchForTerm(term) 
        listOfItems = [] 
        if term.length >= 3 
            # We look for urls in history containing term, ordering
            # so that shortest urls are preferred (putting very long urls that contain 
            # lots of query parameters behind) 
        
            query = <<-EndOfSelect
                SELECT DISTINCT title, url 
                FROM urls 
                WHERE url LIKE 'http://#{term}%' 
                OR url LIKE 'https://#{term}%'
                OR url LIKE 'http://www.#{term}%'
                OR url LIKE 'https://www.#{term}%'
                ORDER BY length(url) 
                LIMIT 5
            EndOfSelect
            
            @history_db.execute(query).map do |title, url|
                puts url + "-->" + title.dump
                listOfItems << 
                    {
                        'title' => url.gsub(/^https?:\/\/(www.)?/, ""), # Strip leading http(s)://(www.) 
                        'comment' => title.gsub(/\n/, " "),
                        'icon' => 'text-html',
                        'tooltip' => "open '#{url}' in browser",
                        'command' => "xdg-open \"#{url}\""
                    }
            end
        end

        return listOfItems
    end
    
    def main
        @connection_time = Time.new - 700; 
    
    while term = gets do
            establish_db_connection if (Time.new - @connection_time) > 600 # Get a new copy of history-db every 10 minutes...
            
            # Ok with a db-copy in place and an open connection we're ready to consume input. 
            term = term.downcase.gsub(/\n$/, "")
            
            STDOUT.print(YAML::dump(searchForTerm(term)))
            
            # Ruby YAML does not end documents with '...', but razor-runner needs that
            # as a syncronizing token, so we add it.
            STDOUT.print("...\n")
        end
    end
end

HistorySearcher.new.main