Mctrain's Blog

What I learned in IT, as well as thought about life

My First Alfred 2 Workflow by Ruby

| Comments

These two days I’m studying how to effiently use Alfred 2. Yesterday I bought its Powerpack Single License (17 Euro), so that I can import or create my own Workflow!

I should say it is really fun to do such things, it makes me believing that I can take more control over my own Mac Air.

In this post I will show how to write a simple workflow using ruby. The code can be downloaded from my github page, it is a very simple program that can translate between each character and its corresponding ASCII code, just like this:

You can translate from an array of decimal ASCII code to their characters:

ascii effect 1

Or from hexadecimal ASCII code to their characters:

ascii effect 2

Or from characters to their corresponding ASCII codes:

ascii effect 3

So now let’s begin!

Firstly I should say why I use ruby is just because I like it! Thanks for zhaocai who have implemented the alfred2-ruby-template, which makes creating Alfred2 Workflow using ruby much more simpler.

At first, you should download the template from github, and rename it whatever you like:

$ git clone https://github.com/zhaocai/alfred2-ruby-template.git
$ mv alfred2-ruby-template alfred2-ascii-translator
$ cd alfred2-ascii-translator

update the domain, id, path attribute in config.yml file:

config.yml
1
2
3
4
5
6
7
8
9
# bundle_id = "domain.id"
# path is the relative path to the workflow in the project root
---
path: ascii
domain: me.ytliu
id: alfred2-ascii-translator
# If you are using Alfred's advanced Dropbox sync, indicate the path shown in
# Alfred Preferences > Advanced > Syncing:
dropbox: ~/Dropbox/Alfred

the last option: dropbox is when you use Alfred’s advance Dropbox sync, as explained above, you can find in Alfred Preferences > Advanced > Syncing

alfred dropbox sync

After that, we can change the main folder to what we want it to be (actually here you can remain as before: workflow, but make sure the path attribute in config.yml should be consistent with it).

$ mv workflow ascii

Now we should setup our ruby environment.

Firstly, we can create a new gemset (I’ve been stuck in the gemset problem for a long time, where there’re many versions of gem packages and make me confused. So here just create a new one, and make it clean).

$ rvm gemset create alfred_ruby_set
$ rvm use 2.0.0@alfred_ruby_set

Note here the ruby version I’m using is 2.0.0, I think other version can also be ok.

At the very beginning of installing all other gems, we should install the plist and bundler gem:

$ gem install plist
$ gem install bundler

Then install other gems using bundle:install:

$ rake bundle:install

Normally it will install all the gems you need. The gems are list in the Gemfile file (e.g., here it is the ascii/Gemfile).

$ cat ascii/Gemfile
ascii/Gemfile
1
2
3
4
source "https://rubygems.org"

gem "plist"
gem "alfred-workflow"

Now we can install the template workflow, we just type:

$ rake install

or if you are using Dropbox sync as mentioned before, you can type:

$ rake dbxinstall

this will create a soft link in the right place, for my case, it just execute:

ln -sf /Users/luisleo/Software/AlfredWorkflow/alfred2-ascii-translator/workflow /Users/luisleo/Dropbox/Alfred/Alfred.alfredpreferences/workflows/me.ytliu.alfred2-ascii-translator`

Then when you open the Alfred Preference > Workflow, you can see a new workflow added:

alfred workflow list

Here you can double click it to change some of the information like name, description and so on:

alfred workflow 1

You can also double click the panel with “test feedback”, to see the keywords which can trigger the workflow:

alfred workflow 2

You can also change the icon of your workflow. For my example, I just change it to what I need:

my alfred workflow

You can see that in the Script, the content is /usr/bin/ruby ./main.rb {query}, which means when triger this action, it will execute this script. Now let’s see what happens in the main.rb located in ascii folder:

ascii/main.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env ruby
# encoding: utf-8

require "./bundle/bundler/setup"
require "alfred"

Alfred.with_friendly_error do |alfred|
  fb = alfred.feedback

  # add an arbitrary feedback
  fb.add_item({
    :uid      => ""                     ,
    :title    => "Just a Test"          ,
    :subtitle => "feedback item"        ,
    :arg      => "A test feedback Item" ,
    :valid    => "yes"                  ,
  })

  if ARGV[0].eql? "failed"
    alfred.with_rescue_feedback = true
    raise Alfred::NoBundleIDError, "Wrong Bundle ID Test!"
  end

  puts fb.to_xml
end

From this code, you can see that all code are put in the block:

ascii/main.rb
1
2
3
Alfred.with_friendly_error do |alfred|

end

The input parameters can be retrieved from ARGV. If we want to output something, we can use the fb.add_item API:

ascii/main.rb
1
2
3
4
5
6
7
8
9
# add an arbitrary feedback
fb.add_item({
  :uid      => ""                     ,
  :title    => "Just a Test"          ,
  :subtitle => "feedback item"        ,
  :arg      => "A test feedback Item" ,
  :valid    => "yes"                  ,
})
puts fb.to_xml

So when you open Alfred, input test feedback, you can see like this:

test feedback example

That’s quite simple.

So here come to my ASCII Translator:

ascii/main.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/env ruby
# encoding: utf-8

require 'rubygems' unless defined? Gem # rubygems is only needed in 1.8
require "./bundle/bundler/setup"
require "alfred"

def show_chars(fb, result)
  fb.add_item({
    :uid      => "",
    :title    => "#{result}",
    :subtitle => "",
    :arg      => "",
    :valid    => "yes",
  })
end

special_ascii = ['NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL', 'BS', 'TAB', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI', 'DLE', 'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB', 'CAN', 'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US']
special_ascii_7f = 'del'

Alfred.with_friendly_error do |alfred|
  alfred.with_rescue_feedback = true
  fb = alfred.feedback

  type = ARGV[0]

  if (type =~ /[d|h|c]/) == nil
    raise Alfred::InvalidFormat, "Usage: ascii [d|h|c] args"
  end

  if type == 'd'
    query = ARGV[1..-1].map(&:to_i)
  elsif type == 'h'
    query = ARGV[1..-1].map(&:hex)
  else
    query = ARGV[1..-1].join(" ")
  end

  result = Array.new
  if type == 'c'
    result = query.bytes.map { |c| "0x#{c.to_s(16)}" }
  else
    query.each do |q|
      if q < 0x20
        result << special_ascii[q]
      elsif q == 0x7f
        result << special_ascii_7f
      elsif q < 0x7f
        result << q.chr
      else
        result << 'undefined'
      end
    end
  end

  for i in (0..result.size/10)
    show_chars(fb, result[i*10..[result.size-1, i*10+9].min].join(" ").strip)
  end
  puts fb.to_xml
end

If you can read ruby, you can surely read my code. Here I just don’t explain, you can see the README in github page, to have a idea of how to use it.

Comments