PopClip: Scripting Extensions
PopClip is a great utility program that, once you get used to it, is very hard to live without. This tutorial is going to show how to write a scripting extension by making an example extension.
In this tutorial I will show you how to create a PopClip extension by building an extension that will look for a Bible reference to be selected, lookup the reference in English or Thai using a Web API, and paste the appropriate verse(s) in place. You will learn to create a PopClip extension that uses a PHP script, regular expression for activation, setting preferences that the user can edit, and using hotkeys to override the preferences.
Defining Your Extension
The first thing that should be done is to decide what the extension is going to do. You should always create each extension with a single functionality in mind. Since I am a missionary, I write notes on Bible verses all the time. I would like to have an extension that will take a Bible reference and paste that verse or verses into my document in both Thai and English. Therefore, the extension will do just that!
But, sometimes I might just need one or the other language. Therefore, the extension needs to have a preference panel and hotkeys to override the preferences. That might sound like a lot of work, but PopClip makes it easy.
I found a great website, Ephesians 4:14, that allows for the downloading of verses from both the King James Bible in English and the Thai King James Bible. Since I work with the Thai language, these are the only Bibles I need. They also have many other languages. Therefore, you can add whatever language might interest you.
Popclip Resources
If you just recently installed PopClip, please read the PopClip User Guide first. The tutorial, Create Your Own Custom Extension for PopClip, gives a great introduction to PopClip and how to make extensions that do not call for any programming. This tutorial will focus on script programming extensions for PopClip.
Pilotmoon gives a complete list of plist configuration file options. Always refer to this for up to date documentation of the extensions interface for PopClip.
The extension will be written in PHP. If you do not know how to program in PHP, an excellent video course can be found on Tuts+: PHP Fundamentals.
The File Structure
Wherever you will be working on your computer, create a directory called popclipBible.popclipext. That directory will look like a normal file, but when you right click on it and select the popup menu Show Package Contents, it will show the contents of that directory. You need to create the files Config.plist, bible.php, and bible.png in to this directory.
Config.plist is the plist file described in the next section. bible.php will contain the PHP script to be executed. bible.png is the graphic file for the extension. All of these are in the download file at the top.
The Plist
All extensions for PopClip start with a plist file called Config.plist. This file tells PopClip how the extension fits in to the world of PopClip. The full plist for this extension is:
1 |
|
2 |
<?xml version="1.0" encoding="UTF-8"?>
|
3 |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
4 |
<plist version="1.0"> |
5 |
<dict>
|
6 |
<key>Actions</key> |
7 |
<array>
|
8 |
<dict>
|
9 |
<key>After</key> |
10 |
<string>paste-result</string> |
11 |
<key>Image File</key> |
12 |
<string>bible.png</string> |
13 |
<key>Script Interpreter</key> |
14 |
<string>/usr/bin/php</string> |
15 |
<key>Shell Script File</key> |
16 |
<string>bible.php</string> |
17 |
<key>Title</key> |
18 |
<string>Bible</string> |
19 |
</dict>
|
20 |
</array>
|
21 |
<key>Options</key> |
22 |
<array>
|
23 |
<dict>
|
24 |
<key>Option Identifier</key> |
25 |
<string>biblekjv</string> |
26 |
<key>Option Type</key> |
27 |
<string>boolean</string> |
28 |
<key>Option Label</key> |
29 |
<string>KJB</string> |
30 |
</dict>
|
31 |
<dict>
|
32 |
<key>Option Identifier</key> |
33 |
<string>biblethaikjv</string> |
34 |
<key>Option Type</key> |
35 |
<string>boolean</string> |
36 |
<key>Option Label</key> |
37 |
<string>Thai KJV</string> |
38 |
</dict>
|
39 |
</array>
|
40 |
<key>Extension Description</key> |
41 |
<string>Lookup Bible verses in Popclip.</string> |
42 |
<key>Extension Identifier</key> |
43 |
<string>com.customct.popclip.extension.bible</string> |
44 |
<key>Extension Name</key> |
45 |
<string>Bible</string> |
46 |
<key>Long Running</key> |
47 |
<true/>
|
48 |
<key>Regular Expression</key> |
49 |
<string>(.*\d+\:\d+(\-\d+)*)</string> |
50 |
<key>Required Software Version</key> |
51 |
<integer>701</integer> |
52 |
</dict>
|
53 |
</plist>
|
As can be seen, it is a XML formated data file. This data file tells PopClip all it needs to know to run the extension.
This is the bare bones plist needed for this extension. The main dictionary has the keys Actions and Options. These are the main entries for providing the functionality of the extension. I will describe them in more detail below. The other keys are:
Extension Description
This key gives a description of what the extension will do. This needs to be short, but descriptive.
Extension Identifier
This key gives a unique identifier for your extension. I based it off of my website for complete uniqueness. It can by any alpha-numeric sequense, but something readable is always nice.
Extension Name
This key is the name of the extension. I simply call it Bible.
Long Running
This key is set to true since it will be making a Web API call that take a while to finish due to net speed. This simply has PopClip display a wait icon while performing the action.
If the extension does not take long, it should be false.
Regular Expression
This key's value is a regular expression that will allow the launching of the extension only if it asserts true for the currently selected text. By writing a regular expression that will only match a bible reference, it will not show the Bible extension on just any text selection.
If you are a little rusty on writing regular expressions, then you should read this article on Net Tuts+.
Required Software Version
This key specifies the version of PopClip that this extension needs.
Actions
The Action keys are for defining the action that the extension will perform. There are a lot of possibilities, but this tutorial will only focus on the items needed. They are as follows:
After
This specifies the action to take after running the script. paste-result will paste into the top most application whatever text the script echos out.
Image File
This key specifies the icon used for this extension. Since the icon in the same area as the extension, you can simply refer to the file: bible.png. If you wanted to keep the icons in a sub-directory of the extension, you would use a relative reference to that file.
Script Interpreter
This key defines the interpreter will be used to process the script. Since I am doing this program in PHP, it needs to reference the default PHP interpreter on all Macs: /usr/bin/php. You can place the full path to any interpreter you want to use to write extensions.
Shell Script File
This key defines the file in the directory of the extension to run. The filename is: bible.php.
Title
This key is the title of the extension: Bible.
Options
The Options key contains an array of dictionaries. One dictionary for each option in the preference panel for the extension. For this extension, the user should have the option of pasting from the KJV Bible or the Thai KJV Bible. Since I have two options for this extension, I will describe each key in the Options dictionary and tell what I used for each option.
Option Identifier
This key give PopClip a unique name for the option you need for your extension. This is used in the environment variable that is passed to your program. For the two options, these ids will be used: biblekjv and biblethaikjv.
Option Type
This key defines what type of option it is. It can be either boolean (yes or no), string (any string), or multiple (You give a list of options and the user can pick from them). The boolean type is what is needed for this extension.
Option Label
This key defines the text to use in the options panel for the extension. For this extension, the labels KJB and Thai KJV will work.
With these options defined in the plist, a preference panel will be created for editing the preferences. This is what it will look like:

The Script
Now, the script needs to be written. The full script is:
1 |
|
2 |
<?php
|
3 |
//
|
4 |
// Array for converting English Bible book names
|
5 |
// to thai. Not eloquent, but works.
|
6 |
//
|
7 |
$book = array(); |
8 |
$book['Genesis']='ปฐมกาล'; |
9 |
$book['Exodus']='อพยพ'; |
10 |
$book['Leviticus']='เลวีนิติ'; |
11 |
$book['Numbers']='กันดารวิถี'; |
12 |
$book['Deuteronomy']='เฉลยธรรมบัญญัติ'; |
13 |
$book['Joshua']='โยชูวา'; |
14 |
$book['Judges']='ผู้วินิจฉัย'; |
15 |
$book['Ruth']='นางรูธ'; |
16 |
$book['1Samuel']='1ซามูเอล'; |
17 |
$book['2Samuel']='2ซามูเอล'; |
18 |
$book['1Kings']='1พงศ์กษัตริย์'; |
19 |
$book['2Kings']='2พงศ์กษัตริย์'; |
20 |
$book['1Chronicles']='1พงศาวดาร'; |
21 |
$book['2Chronicles']='2พงศาวดาร'; |
22 |
$book['Ezra']='เอสรา'; |
23 |
$book['Nehemiah']='เนหะมีย์'; |
24 |
$book['Esther']='เอสเธอร์'; |
25 |
$book['Job']='โยบ'; |
26 |
$book['Psalms']='สดุดี'; |
27 |
$book['Proverbs']='สุภาษิต'; |
28 |
$book['Ecclesiastes']='ปัญญาจารย์'; |
29 |
$book['SongofSongs']='เพลงซาโลมอน'; |
30 |
$book['Isaiah']='อิสยาห์'; |
31 |
$book['Jeremiah']='เยเรมีย์'; |
32 |
$book['Lamentations']='เพลงคร่ำครวญ'; |
33 |
$book['Ezekiel']='เอเสเคียล'; |
34 |
$book['Daniel']='ดาเนียล'; |
35 |
$book['Hosea']='โฮเชยา'; |
36 |
$book['Joel']='โยเอล'; |
37 |
$book['Amos']='อาโมส'; |
38 |
$book['Obadiah']='โอบาดีย์'; |
39 |
$book['Jonah']='โยนาห์'; |
40 |
$book['Micah']='มีคาห์'; |
41 |
$book['Nahum']='นาฮูม'; |
42 |
$book['Habakkuk']='ฮาบากุก'; |
43 |
$book['Zephaniah']='เศฟันยาห์'; |
44 |
$book['Haggai']='ฮักกัย'; |
45 |
$book['Zechariah']='เศคาริยาห์'; |
46 |
$book['Malachi']='มาลาคี'; |
47 |
$book['Matthew']='มัทธิว'; |
48 |
$book['Mark']='มาระโก'; |
49 |
$book['Luke']='ลูกา'; |
50 |
$book['John']='ยอห์น'; |
51 |
$book['Acts']='กิจการของอัครทูต'; |
52 |
$book['Romans']='โรม'; |
53 |
$book['1Corinthians']='1โครินธ์'; |
54 |
$book['2Corinthians']='2โครินธ์'; |
55 |
$book['Galatians']='กาลาเทีย'; |
56 |
$book['Ephesians']='เอเฟซัส'; |
57 |
$book['Philippians']='ฟีลิปปี'; |
58 |
$book['Colossians']='โคโลสี'; |
59 |
$book['1Thessalonians']='1เธสะโลนิกา'; |
60 |
$book['2Thessalonians']='2เธสะโลนิกา'; |
61 |
$book['1Timothy']='1ทิโมธี'; |
62 |
$book['2Timothy']='2ทิโมธี'; |
63 |
$book['Titus']='ทิตัส'; |
64 |
$book['Philemon']='ฟีเลโมน'; |
65 |
$book['Hebrews']='ฮีบรู'; |
66 |
$book['James']='ยากอบ'; |
67 |
$book['1Peter']='1เปโตร'; |
68 |
$book['2Peter']='2เปโตร'; |
69 |
$book['1John']='1ยอห์น'; |
70 |
$book['2John']='2ยอห์น'; |
71 |
$book['3John']='3ยอห์น'; |
72 |
$book['Jude']='ยูดา'; |
73 |
$book['Revelation']='วิวรณ์'; |
74 |
|
75 |
//
|
76 |
// Send a GET requst using cURL
|
77 |
// @param string $url to request
|
78 |
// @param array $get values to send
|
79 |
// @param array $options for cURL
|
80 |
// @return string
|
81 |
//
|
82 |
function curl_get($url, array $options = array()) |
83 |
{
|
84 |
$defaults = array( |
85 |
CURLOPT_URL => $url, |
86 |
CURLOPT_HEADER => 0, |
87 |
CURLOPT_RETURNTRANSFER => TRUE, |
88 |
CURLOPT_TIMEOUT => 4 |
89 |
);
|
90 |
|
91 |
$ch = curl_init(); |
92 |
curl_setopt_array($ch, ($options + $defaults)); |
93 |
if( ! $result = curl_exec($ch)) |
94 |
{
|
95 |
trigger_error(curl_error($ch)); |
96 |
}
|
97 |
curl_close($ch); |
98 |
return $result; |
99 |
}
|
100 |
|
101 |
//
|
102 |
// Function: getBibleVerse
|
103 |
//
|
104 |
// Description: This function is for retrieving a Bible
|
105 |
// verse from the api.preachingcentral.com
|
106 |
// web site.
|
107 |
//
|
108 |
// Inputs:
|
109 |
// $verse The verses to be searched for.
|
110 |
// $version The version of the bible to use.
|
111 |
//
|
112 |
function getBibleVerse($verse, $version) { |
113 |
global $book; |
114 |
|
115 |
$result = ""; |
116 |
$raw = urlencode($verse); |
117 |
$xml = curl_get("http://api.preachingcentral.com/bible.php?passage=$raw&version=$version"); |
118 |
$xml_parser = xml_parser_create(); |
119 |
xml_parse_into_struct($xml_parser, $xml, $vals, $index); |
120 |
xml_parser_free($xml_parser); |
121 |
|
122 |
$first = 0; |
123 |
$quote = 0; |
124 |
|
125 |
foreach ($vals as $xml_elem) { |
126 |
if(strcmp($xml_elem['tag'],"TEXT") === 0) { |
127 |
if($quote == 0) { |
128 |
$result = $result . ' "' . $xml_elem['value']; |
129 |
$quote = 1; |
130 |
} else { |
131 |
$result = $result . " " . $xml_elem['value']; |
132 |
}
|
133 |
}
|
134 |
if(strcmp($xml_elem['tag'],"RESULT") === 0) { |
135 |
if($first == 0) { |
136 |
if(strcmp($version, "thai") === 0) { |
137 |
//
|
138 |
// The site only return book names in English. Translate them
|
139 |
// to Thai.
|
140 |
//
|
141 |
$blist = explode(" ", $xml_elem['value']); |
142 |
$bname = ''; |
143 |
$bver = ''; |
144 |
if(count($blist) == 3) { |
145 |
$bname = $blist[0] . $blist[1]; |
146 |
$bver = $blist[2]; |
147 |
} elseif (count($blist) == 4) { |
148 |
$bname = $blist[0] . $blist[1] . $blist[2]; |
149 |
$bver = $blist[3]; |
150 |
} else { |
151 |
$bname = $blist[0]; |
152 |
$bver = $blist[1]; |
153 |
}
|
154 |
$result = $book[$bname] . " " . $bver; |
155 |
} else { |
156 |
//
|
157 |
// English is fine here.
|
158 |
//
|
159 |
$result = $xml_elem['value']; |
160 |
}
|
161 |
$first = 1; |
162 |
} else { |
163 |
$result = $result . '"' . "\n\n" . $xml_elem['value']; |
164 |
}
|
165 |
$quote = 0; |
166 |
}
|
167 |
}
|
168 |
return $result . '"'; |
169 |
}
|
170 |
|
171 |
//
|
172 |
// Get the PopClip Environment variables for the
|
173 |
// extension.
|
174 |
//
|
175 |
$verse = trim(getenv('POPCLIP_TEXT')); |
176 |
$qKJV = getenv('POPCLIP_OPTION_BIBLEKJV'); |
177 |
$qThaiKJV = getenv("POPCLIP_OPTION_BIBLETHAIKJV"); |
178 |
$keycode = intval(getenv('POPCLIP_MODIFIER_FLAGS')); |
179 |
$results = ""; |
180 |
|
181 |
|
182 |
//
|
183 |
// If the preference is set to KJV or the command key
|
184 |
// is pressed, then get the verse from the English KJV
|
185 |
// and add it to the result. If both the command key
|
186 |
// and the control key is pressed, then get the KJV also.
|
187 |
//
|
188 |
if(($qKJV[0] == '1')||($keycode == 1048576)||($keycode == 1310720)) { |
189 |
$results .= getBibleVerse($verse, "kjv") . "\n" ; |
190 |
}
|
191 |
|
192 |
//
|
193 |
// If the preference is set to the Thai KJV or the
|
194 |
// control key is press then get the verse from the
|
195 |
// Thai KJV and add it to the result. If both the
|
196 |
// command key and the control key is pressed,
|
197 |
// then get the Thai version also.
|
198 |
//
|
199 |
if(($qThaiKJV[0] == '1')||($keycode == 262144)||($keycode == 1310720)) { |
200 |
$results .= getBibleVerse($verse, "thai") . "\n"; |
201 |
}
|
202 |
|
203 |
//
|
204 |
// Anything echoed from the script will be pasted in
|
205 |
// to the top most application by PopClip. If the results
|
206 |
// is nothing, then return the verse.
|
207 |
//
|
208 |
if(strcmp($results,"")===0) { |
209 |
echo $verse; |
210 |
} else { |
211 |
echo $results; |
212 |
}
|
213 |
|
214 |
?>
|
The first thing in the script is an array of the names of the books in the Bible. This array is for translating the English book names to their Thai equivalent. The web api that the extension uses only gives references in English. So, the English Bible book names needs to be translated to Thai.
The first function is a helper function. It is a PHP routine for requesting information from a web site using curl and returning the results to the calling program. The input is the url of the request.
The second function is the main function for processing the Bible verse. It will request the Bible verse and process the resulting XML to a more readable format for pasting into the text editor or what ever program is currently active. The parameters are the actual Bible verse and the designator for which Bible to request. For the King James Bible, it is kjv. For the Thai Bible, it is thai. You can get other codes from the site link above.
Getting Information From the Environment
PopClip sends information to the script through environment variables. It is a simple way to communicate. This is how you access that information.
1 |
|
2 |
//
|
3 |
// Get the PopClip Environment variables for the
|
4 |
// extension.
|
5 |
//
|
6 |
$verse = trim(getenv('POPCLIP_TEXT')); |
7 |
$qKJV = getenv('POPCLIP_OPTION_BIBLEKJV'); |
8 |
$qThaiKJV = getenv("POPCLIP_OPTION_BIBLETHAIKJV"); |
9 |
$keycode = intval(getenv('POPCLIP_MODIFIER_FLAGS')); |
10 |
$results = ""; |
The PHP function getenv()
will retrieve the environment variable given in the string for the function call. All of the environment variables are strings and need to be used as such. The different environment variables are:
POPCLIP_TEXT
This variable is the selection when PopClip was invoked. Since the user of the extension could select extra white space, I always like to use the trim()
function to remove any extra white space.
POPCLIP_OPTION_BIBLEKJV
This variable is the option about getting the verse from the King James version. It is a string containing the character 1 if true, otherwise it contains the character 0.
POPCLIP_OPTION_BIBLETHAIKJV
This variable is the option about getting the verse from the Thai King James version. It is a string containing the character 1 if true, otherwise it contains the character 0.
POPCLIP_MODIFIER_FLAGS
This tells use what keys were pressed while the PopClip action was selected. Everything passed in an environment variable is a string. Therefore, the strings need to be translated to an easy to use format as well. I use the intval()
function to convert the string to an integer.
All the different key code values are shown in the PopClip Extensions GitHub.
As can be seen, every option in the preferences for the extension has it’s own corresponding environment variable.
The $result variable is also set to an empty string for the next two sections to fill up.
Processing the Environment Variables
The main part of any PopClip extension is to take the variables passed in the environment and make something useful with it.
1 |
|
2 |
//
|
3 |
// If the preference is set to KJV or the command key
|
4 |
// is pressed, then get the verse from the English KJV
|
5 |
// and add it to the result.
|
6 |
//
|
7 |
if(($qKJV[0] == '1')||($keycode == 1048576)||($keycode == 1310720)) { |
8 |
$results .= getBibleVerse($verse, "kjv") . "\n" ; |
9 |
}
|
This section of code will fetch the verse from the King James version if it was set in the preferences (the $qKJV variable) or the command key was pressed while selecting the PopClip action ($keycode is 1048576). It will also request it if both control and command keys are pressed ($keycode is 1310720). Since the $qKJV variable is a string that is 1 or 0, you can check for the first character to be a character 1. That way, it does not have to process to a number. That can save some time!
If the condition is true, then the verses are retrieved using the getBibleVerse()
function and appended to the $result string.
1 |
|
2 |
//
|
3 |
// If the preference is set to the Thai KJV or the
|
4 |
// control key is press then get the verse from the
|
5 |
// Thai KJV and add it to the result.
|
6 |
//
|
7 |
if(($qThaiKJV[0] == '1')||($keycode == 262144)||($keycode == 1310720)) { |
8 |
$results .= getBibleVerse($verse, "thai") . "\n"; |
9 |
}
|
Here, it is similar. If the Thai King James version was selected in the preferences ($qThaiKJV) or the control key was pressed while selecting the PopClip action ($keycode is 262144), then the Thai King James version will be requested. It will also request it if both control and command keys are pressed ($keycode is 1310720).
If the condition is true, then the verses are retrieved using the getBibleVerse()
function and appended to the $result string.
1 |
|
2 |
//
|
3 |
// Anything echoed from the script will be pasted in
|
4 |
// to the top most application by PopClip. If the results
|
5 |
// is nothing, then return the verse.
|
6 |
//
|
7 |
if(strcmp($results,"")===0) { |
8 |
echo $verse; |
9 |
} else { |
10 |
echo $results; |
11 |
}
|
Here, the results need to be echoed. But, if the results buffer is empty, then the verse should be echoed. If an empty string is echoed, it effectively deletes what was selected. Since everything is an option, it is possible to get here with the $results
variable empty. Good programming practices tells us to always take each possibility into account.
Usage
Once the extension is loaded into PopClip and the preferences are set, then the extension is easy to use. Just select some text that is a Bible reference.



When the PopClip bar appears, select the white bible and the selection will be changed into the verse. Here, the preferences were set up for both KJV and ThaiKJV versions to be printed.



If the text does not match the regular expression for a Bible reference, then the Bible icon will not be shown.



This helps to save real-estate on the PopClip bar. Whenever you create an extension, think about how to minimize unnecessary appearances of your extension.
Summary
Now you have a neat little PopClip extension for getting verses from a web site and pasting them in to your application. Even better than that, you now know how to create a preferences panel for your extension, get to that information from your extension, manipulate the selected text, and paste it back into the top most application. You can take this code and expand it or use it as a skeleton for your own PopClip extension. Let everyone know how you use it in the comments!