Professional Documents
Culture Documents
r
r
(
e
s
r
l
n
n
Welcome
EDITORIAL
Editor Catherine Emma Ellis
Editor-in-chief Graham Barlow
Art editor Efrain Hernandez-Mendoza
MARKETING
Welcome!
CIRCULATION
Trade marketing manager Juliette Winyard
(07551 150 984)
LICENSING
International director Regina Erak
regina.erak@futurenet.com
+44 (0)1225 442244 Fax +44 (0)1225 732275
MANAGEMENT
Content & marketing director Nial Ferguson
Head of content & marketing, technology Nick Merritt
Group editor-in-chief Paul Newman
Group art director Steve Gotobed
Future is an award-winning international media
group and leading digital business. We reach more
than 49 million international consumers a month
and create world-class content and advertising
solutions for passionate consumers online, on tablet
& smartphone and in print.
Future plc is a public
company quoted
on the London
Stock Exchange
(symbol: FUTR).
www.futureplc.com
Contents
Contents
Code concepts
Types of data ................................................................................................................ 8
More data types..................................................................................................... 10
Abstraction .................................................................................................................. 12
Files and modules................................................................................................ 14
Use an IDE..................................................................................................................... 18
Write a program .................................................................................................... 20
Add features .............................................................................................................. 22
Put it all together .................................................................................................. 24
Data modules........................................................................................................... 26
Data storage.............................................................................................................. 28
Data organisation................................................................................................30
Data encryption..................................................................................................... 32
Spot mistakes..........................................................................................................34
Ruby
Ruby: Master the basics.............................................................................. 38
Ruby: Add a little more polish ............................................................... 42
Ruby: Modules, blocks and gems....................................................49
Ruby on Rails: Web development .................................................... 54
Ruby on Rails: Code testing .................................................................... 58
Ruby on Rails: Site optimisation......................................................... 62
More languages
C and beyond: Code a starfield .......................................................... 68
Scheme: Learn the basics .........................................................................72
Scheme: Recursion............................................................................................76
Scheme: High order procedures ......................................................80
4
Contents
PHP
PHP: Write your first script....................................................................... 86
PHP: Build an online calendar ..............................................................90
PHP: Extend your calendar .....................................................................94
PHP: Get started with MySQL ............................................................. 98
PHP: Do more with MySQL .................................................................. 102
Modern Perl
Modern Perl: Track your reading....................................................108
Modern Perl: Build a web app............................................................. 112
Modern Perl: Adding to our app....................................................... 116
Python
Python: Different types of data ....................................................... 122
Python: Code a system monitor .................................................... 124
Python: Clutter animations .................................................................. 128
Python: Stream video ................................................................................. 132
Python: Code a Gimp plugin ............................................................... 136
Python: Gimp snowflakes ..................................................................... 140
Python: Make a Twitter client............................................................ 144
Minecraft: Start hacking ..........................................................................148
Minecraft: Image wall importing ....................................................150
Minecraft: Make a trebuchet ..............................................................154
Minecraft: Build a cannon ...................................................................... 158
.
.
.
:
n
;
Code concepts
Code concepts
E
Code concepts
Code concepts:
Types of data
Functions tell programs how to work, but its data that they operate
on. Jonathan Roberts explains the basics of data in Python.
What is data?
While were
looking only at
basic data types,
in real programs
getting the
wrong type can
cause problems,
in which case
youll see
a TypeError.
Code concepts
element of a list or the last character in a string, you can use
the same notation with a -1 as the index. -2 will reference the
second-to-last character, -3 the third, etc. Note that when
working backwards, the indexes dont start at 0.
Methods
Lists and strings also have a range of other special
operations, each unique to that particular type. These are
known as methods. Theyre similar to functions such as
type() in that they perform a procedure. What makes them
different is that theyre associated with a particular piece of
data, and hence have a different syntax for execution.
For example, among the list types methods are append
and insert.
>>> list.append(chicken)
>>> list
[banana, cake, tiffin, chicken]
>>> list.insert(1, pasta)
>>> list
[banana, pasta, cake, tiffin, chicken]
As you can see, a method is invoked by placing a period
between the piece of data that youre applying the method to
and the name of the method. Then you pass any arguments
between round brackets, just as you would with a normal
function. It works the same with strings and any other data
object, too:
>>> word = HELLO
>>> word.lower()
hello
There are lots of different methods that can be applied to
lists and strings, and to tuples and dictionaries (which were
about to look at). To see the order of the arguments and the
full range of methods available, youll need to consult the
Python documentation.
Variables
In the previous examples, we used the idea of variables to
make it easier to work with our data. Variables are a way to
name different values different pieces of data. They make it
easy to manage all the bits of data youre working with, and
greatly reduce the complexity of development (when you use
sensible names).
As we saw above, in Python you create a new variable with
an assignment statement. First comes the name of the
variable, then a single equals sign, followed by the piece of
data that you want to assign to that variable.
From that point on, whenever you use the name assigned
to the variable, youre referring to the data that you assigned
to it. In the examples, we saw this in action when we
referenced the second character in a string or the third
element in a list by appending index notation to the variable
name. You can also see this in action if you apply the type()
function to a variable name:
>>> type(word)
<type str>
>>> type(list)
<type list>
The Python
interpreter is a
great place to
experiment with
Python code and
see how different
data types work
together.
Looping sequences
One common operation that you may want to perform on any
of the sequence types is looping over their contents to apply
an operation to every element contained within. Consider this
small Python program:
list = [banana, tiffin, burrito]
for item in list:
print item
First, we created the list as we would normally, then we
used the for in construct to perform the print function
on each item in the list. The second word in that construct
doesnt have to be item, thats just a variable name that
gets assigned temporarily to each element contained within
the sequence specified at the end. We could just as well
have written for letter in word and it would have worked
just as well.
Thats all we have time to cover in this article, but with the
basic data types covered, well be ready to look at how you
can put this knowledge to use when modelling real-world
problems in later articles.
In the meantime, read the Python documentation to
become familiar with some of the other methods that it
provides for the data types weve looked at before. Youll find
lots of useful tools, such as sort and reverse! Q
9
Code concepts
Code concepts:
More data types
Learn how different types of data come together to solve a real
problem as Jonathan Roberts counts some words.
Hardly
surprisingly,
our counting
program, after
being sorted,
finds the to
be the most
common word
in The Time
Machine, by
HG Wells.
10
tm = open(timemachine.txt, r)
In this example, open() is passed two variables. The first is
the name of the file to open; if it were in a different directory
from the Python script, the entire path would have to be
given. The second argument specifies which mode the file
should be opened in: r stands for read, but you can also use
w for write or rw for read-write.
Notice weve also assigned the file to a variable, tm, so we
can refer to it later in the program.
With a reference to the file created, we also need a way to
access its contents. There are several ways to do this, but
today well be using a for in loop. To see how this works,
try opening timemachine.txt in the interactive interpreter
and then typing:
>>> for line in tm:
print line
...
The result should be every line of the file printed to the
screen. By putting this code in to a .py file, say cw.py, weve
got the start of our Python program.
Cleaning up
The program description also specified that we should
exclude punctuation marks, consider the same word but in
different cases as one, and that were counting individual
words, not lines! As it stands, weve been able to read only
entire lines as strings, however, with punctuation, strange
whitespace characters (such as \r\n) and different
cases intact.
Looking at the Python string documentation (http://
docs.python.org/library), we can see that there are four
methods that can help us convert line strings into a format
closer to that specified by the description: strip(),
translate(), lower() and split().
Each of these are methods, and as such theyre functions
that are applied to particular strings using the dot notation.
For example, strip(), which removes specified characters
from the beginning and end of a string, is used like this:
>>> line.strip()
When passed with no arguments, it removes all
whitespace characters, which is one of the jobs we needed to
get done.
The function translate() is a method that can be used for
removing a set of characters, such as all punctuation marks,
from a string. To use it in this capacity, it needs to be passed
two arguments, the first being None and the second being
the list of characters to be deleted.
>>> line.translate(None, !#$%&\()*+,-./:;<=>?@[\\]^_`{|}~)
lower() speaks for itself, really: it converts every character in
Code concepts
a string to lower-case. split() splits distinct elements inside
a string into separate strings, returning them as a list.
By passing an argument to split(), its possible to specify
which character identifies the end of one element and the
start of another.
>>> line.split( )
In this example, weve passed a single space as the
character to split the string around. With all punctuation
removed, this will create a list, with each word in the string
stored as a separate element.
Put all of this in the Python file we started working on
earlier, inside the for loop, and weve made considerable
progress. It should now look like this:
tm = open(timemachine.txt, r)
for line in tm:
line = line.strip()
line = line.translate(None, !#$%&\()*+,-./:;<=>?@
[\\]^_`{|}~)
line = line.lower()
list = line.split( )
Because all of the string methods return a new, modified
string, rather than operating on the existing string, weve
re-assigned the line variable in each line to store the work of
the previous step.
Uniqueness
Phew, look at all that work weve just done with data! By using
the string methods, weve been able to remove all the bits of
data that we werent interested in. Weve also split one large
string, representing a line, into smaller chunks by converting
it to a list, and in the process gotten at the exact, abstract
concept were most interested in: words.
Our stunning progress aside, theres still work to be done.
We now need a way to identify which words are unique and
not just in this line, but in every line contained within the
entire file!
The first thing that should pop in to your head when
thinking about uniqueness is of a dictionary, the key-value
store we saw in the first article (p8). It doesnt allow duplicate
keys, so by entering each word as a key within a dictionary,
were guaranteed there wont be any duplicates.
Whats more, we can use the value to store the number of
times each word has occurred, incrementing it as the
program comes across new instances of each key.
Start by creating the dictionary, and ensuring that it
persists for the entire file not just a single line by placing
this line before the start of the for loop:
dict = {}
This creates an empty dictionary, ready to receive
our words.
Next, we need to think about a way to get each word in to
the dictionary. As we saw last time, ordinarily a simple
assignment statement would be enough to add a new word
to the dictionary. We could then iterate over the list we
created above (using another for loop), adding each entry to
the dictionary with a value of 1 (to represent that it has
occurred once in the file).
for word in list:
dict[word] = 1
But remember, if the key already exists the old value is
overwritten and the count will be reset. To get around this, we
can place an if-else clause inside the loop:
if word in dict:
count = dict[word]
count += 1
dict[word] = count
else:
dict[word] = 1
This is a bit confusing because dict[word] is being used in
two different ways. In the second line, it returns the value and
assigns it to the variable count, while in the fourth and
seventh lines, count and 1 are assigned to that keys
value, respectively.
Notice, too, that if a word is already in the dictionary, we
increment the count by 1, representing another occurrence.
Pythons
Standard Library
reference, http://
docs.python.
org/library, is an
invaluable source
for discovering
what methods
are available and
how to use them.
Putting it together
Another data type wrestled with, another step closer to our
goal. At this point, all thats left to do is insert some code to
print the dictionary and put it all together and run the
program. The print section should look like this and be at the
very end of the file, outside of the line-looping code.
for word,count in dict.iteritems():
print word + : + str(count)
This for loop looks different to what youve seen before.
By using the iteritems method of the dictionary, we can
access both the key (word) and value (count) in a single loop.
Whats more, weve had to use the str() function to convert
count, an integer, into a string, as the + operator cant
concatenate an integer and a string.
Try running it, and you should see your terminal screen
filled with lines like:
...
other: 20
sick: 2
ventilating: 2
...
Data everywhere!
Thats all we planned to achieve in this particular tutorial and
its actually turned out to be quite a lot. As well as having a
chance to see how several different types of data and their
methods can be applied to solve a real problem, we hope
youve noticed how important it is to select the appropriate
type for representing different abstract concepts.
For example, we started off with a single string
representing an entire line, and we eventually split this into
a list of individual strings representing single words. This
made sense until we wanted to consider unique instances, at
which point we put everything into a dictionary.
As a further programming exercise, why not look into
sorting the resulting dictionary in order to see which words
occur most often? You might also want to consider writing
the result to a file, one entry on a line, to save the fruits of
your labour. Q
11
Code concepts
Code concepts:
Abstraction
Jonathan Roberts shows you how creating abstractions can
make code more reliable and easier to maintain.
Square roots
To get our heads around the concept of abstraction, lets start
by thinking about square roots and different techniques for
finding them. One of these was discovered by Newton, and is
thus known as Newtons method.
It says that when trying to find the square root of number
(x), we should start with a guess (y) of its square root. We can
then improve that by averaging our guess (y) with the result
of dividing the number (x) by our guess (y). As we repeat this
procedure, we get closer and closer to the square root. In
most attempts, well never reach a definite result, well only
make our guess more and more accurate. Eventually, well
reach a level of accuracy that is good enough for our needs
and give up. Just to be clear about whats involved, take a look
at the table below for how you would apply this method to
find the square root of 2 (eg, x).
Its a lot of work just to
find the square root of
a number! Imagine if when
you were in school, every
time you had to find a square
root you had to do all these
steps manually. For instance,
solving problems involving Pythagoras theorem would be
much more unwieldly.
Luckily, assuming you were allowed calculators at school,
theres another, much simpler method to find square roots.
Calculators come with a button marked with the square root
symbol, and all you have to do is press this button once
12
2/1 = 2
(2 + 1)/2 = 1.5
1.5
2/1.5 = 1.33
1.4167
2/1.4167 = 1.4118
Code concepts
created, leaving them for you to fill in.
import math:
def square(x):
...
def closeEnough(x, guess):
...
def improveGuess(x, guess):
...
def sqrt(x, guess):
...
def pythag(a, b):
a2b2 = square(a) + square(b)
return sqrt(a2b2)
Here, weve split the code in to several smaller functions,
each of which fulfils a particular role. This has many benefits.
For starters, how much easier is the pythag() function to
read? In the first line, you can see clearly that a2b2 is the
result of squaring two numbers, and everything below that
has been consolidated in to a single function call, the purpose
of which is also obvious.
Whats more, because each part of the code has been split
into a different function, we can easily test it. For example,
testing whether improveGuess() was doing the right thing
would be very easy: come up with a few values for x and
guess, do the improvement by hand, and then compare your
results with those returned by the function.
If pythag() itself was found not to return the correct
result, we could quickly test all these auxiliary functions to
narrow down where the bug was.
And, of course, we can easily reuse any of these new
functions. If you were finding the square root of a number in
a different function, for instance, you could just call the sqrt()
function: six characters instead of four lines means theres far
less opportunity to make mistakes.
One final point: because our sqrt code is now abstracted,
we could change the implementation completely, but so long
Abstraction
Java
C
Assembler
Object code
There are layers of abstraction underneath everything
you do on a PC you just dont often think of them.
Layers of abstraction
Hopefully, this example has demonstrated how powerful
a technique abstraction is. Bear in mind that there are many
layers of abstraction present in everything you do on
a computer that you never think of.
For instance, when youre programming do you know how
Python represents integers in the computers memory? Or
how the CPU performs arithmetic operations such as
addition and subtraction?
The answer is probably no. You just accept the fact that
typing 2 + 3 in to the Python interpreter returns the correct
result, and you never have to worry about how it does this.
You treat it as a black box.
Think how much longer it would take you to program if
you had to manually take care of what data went in which
memory location, to work with binary numbers, and translate
alphabetic characters in to their numeric representations
thank goodness for abstraction! Q
13
THE BEST
Z97 MOBO?
AMAZING DUAL
GPU LAPTOP
ASUS MAXIMUS
VII FORMULA RATED!
17-INCH AORUS X7
V2 ON TEST
4K SCREENS
ON A BUDGET
DOUBLE DISPLAY
REMOTE LOGIN
ACCESS A WINDOWS PC
ANYWHERE ON EARTH
IIYAMA B2888
UHSU REVIEWED
8-CORE SENSATION!
FASTEST
EVER CPU
s for
Includes guide
well!
Windows 8 as
100
SUPER START
WINDOWS
SECRETS
UNLOCKED
BUILD IT!
A FULL HD
GAMING PC
FOR 468
FREE DISC!
2015 GAMES
MED
DIA
767'
PACKED WITH
40 ESSENTIAL APPS
PREVIEW
BEST FOR
ALL NEW PCS
STREA
AM
YOU
UR
6HQGPXVLLFYLG
GHR
DQGSK
KR
KRWRV
DURXQG
G\RXU
U
KRP
PH
PH
ARKHAM KNIGHTS
Windows tutorials
New things to do
Buying advice
SAVE UP TO 45%
SAVE UP TO 45%
SAVE UP TO 40%
SAVE UP TO 70%
SAVE UP TO 50%
FROM 25.49
FROM 23.49
FROM 25.49
FROM 15.99
FROM 23.49
FROM
STUDIO
TO
STAGE
TECHNIQUE
FUTURE
BASS
Create powerful,
complex synth
bass in your DAW
DVD missing?
Ask your vendor
TESTED
Roland System-1
ON STAGE WITH
FACTORY
FLOOR
See the set-up behind the
UKs nest electronic live act
SAVE UP TO 40%
SAVE UP TO 55%
SAVE UP TO 40%
SAVE UP TO 35%
SAVE UP TO 50%
SAVE UP TO 50%
SAVE UP TO 40%
FROM 26.49
FROM 12.99
FROM 17.99
FROM 22.49
FROM 20.99
FROM 21.49
FROM 25.49
Savings compared to buying 2 years worth of full priced issues from UK newsstand. This offer is for new print subscribers only. You will receive 13 issues in a year. Full details of the Direct Debit guarDQWHHDUHDYDLODEOHXSRQUHTXHVW,I\RXDUHGLVVDWLVHGLQDQ\ZD\\RXFDQZULWHWRXVRUFDOOXVWRFDQFHO\RXUVXEVFULSWLRQDWDQ\WLPHDQGZHZLOOUHIXQG\RXIRUDOOXQPDLOHGLVVXHV3ULFHVFRUUHFWDW
point of print and subject to change. For full terms and conditions please visit: myfavm.ag/magterms Offer ends: 31st January 2015
*V]LYPUN
UN [OL
SH[LZ[ UL^
[LJOUVSVN`
VSVN`
PU KLW[O
KL
PU KL[HPS
KL
M\SS` [LZ[LK
NLHY
Code concepts
Code concepts:
Files and modules
Graham Morrison expands your library of functions and grabs
external data with just two lines of Python.
When you
read a file, most
languages will
step through its
data from the
beginning to the
end in chunks
you specify. In
this example,
were reading a
line at a time.
Environment variables
Dealing with paths, folders and file locations can quickly
become complicated, and its one of the more tedious issues
youll face with your own projects. Youll find that different
environments have different solutions for finding files,
with some creating keywords for common locations and
others leaving it to the programmer. This isnt so bad when
you only deal with files created by your projects, but it
becomes difficult when you need to know where to store a
configuration file or load a default icon. These locations may
be different depending on your Linux distribution or desktop,
but with a cross-platform language such as Python, theyll
also be different for each operating system. For that reason,
you might want to consider using environment variables.
These are similar to variables with a global scope in many
16
Code concepts
programming languages, but they apply to any one users
Linux session rather than within your own code. If you type
env on the command line, for instance, youll see a list of the
environmental variables currently set for your terminal
session. Look closely, and youll see a few that apply to default
locations and, most importantly, one called HOME. The value
assigned to this environmental variable will be the location of
your home folder on your Linux system, and if we want to use
this within our Python script, we first need to add a line to
import the operating system-specific module. The line to do
this is:
import os
This command is also opening a file, but not in the same
way we opened list.txt. This file is known as a module in
Python terms, and modules like this import functionality,
including statements and definitions, so that a programmer
doesnt have to keep re-inventing the wheel.
Modules extend the simple constructs of a language to
add portable shortcuts and solutions, which is why other
languages might call them libraries. Libraries and modules
are a little like copying and pasting someones own research
and insight into your own project. Only its better than that,
because modules such as os are used by everyone, turning
the way they do things into a standard.
Binary files
have no context
without an
associated file
type and a way
of handling them,
which is why you
get the raw data
output when you
read one.
The os module
Getting back to our project, the os module is designed to
provide a portable way of accessing operating systemdependent functionality so that you can write multi-platform
applications without worrying about where files should be
placed. This includes knowing where your home directory
might be.
17
Code concepts
Code concepts:
Use an IDE
Lazy Graham Morrison explains why its never too early to start
using a development environment.
Down in Komodo
In almost all the examples weve used in this series of code
concept guides, weve used Python to illustrate the ideas and
concepts covered in the text. Weve even mentioned some
of the Python IDEs available, such as Eric, but weve never
covered any alternative, or how
you use any of them with the
language, and how similar
functions are available for other
IDEs for other languages. There
are many Python IDEs, but
perhaps because of its crossplatform credentials, some of the more popular ones are
commercial. This isnt ideal when youre starting out, so were
going to forgo the commercial options and look at a free
alternative. The one weve chosen is called Komodo Edit.
Its open source and fairly comprehensive, but its also the
little brother of a closed source commercial version called
Komodo IDE, so your skills will be transferable if you need
a more comprehensive solution.
Installation from the download is as easy as untarring the
file and running ./install.sh in the new directory. Most
distributions will now show a shortcut to the IDE on your
desktop, or you can run the bin/komodo executable from
your home directory. Your first view of this application might
be a little overwhelming, as the default configuration offers
a large news pane at the top, mostly containing an
advertisement for the commercial version. But spend a few
moments familiarising yourself, and it will soon feel like home.
The two panels on the left, for instance, contain a simple file
manager at the top and a project view at the bottom.
A project is what an IDE calls the glut of code, configuration
and IDE files that come together to create a single application
or project. You can create a new project by clicking on the
small symbol to the right, and when youve created a new
Syntax highlighting is
as useful for beginners
as it is for experts.
Syntax highlighting and code completion are two of the best reasons for using
an integrated development environment such as Komodo.
18
Code concepts
project, you can drag source files onto the project name, or
right-click on it, to add new files. Wed recommend starting
with a new Python 3-derived template. The New File dialog
allows you to choose between many different languages
supported by Komodo, but all this really does is pre-define an
environmental variable at the top of the file and make sure
the file extension is correct.
Code constructions
With a fresh file ready for your Python code, well now give
some examples of how an IDE will help with code
constructions. Taking a cue from previous tutorials in the
Code Concepts series, type import. As weve covered
previously, this is the command to add extra functionality to
Python by importing code from other modules or libraries.
When using a simple text editor, you had to know the exact
name of the module you wanted to import. With Komodo,
youll be presented with a list of modules that are already
installed, and you just have to choose the one youre after.
Choose math to add the mathematical functions. Now add
the following code to the file:
def square(x):
return x * x
As you might remember, this is a super-simple function
that returns the square of x, and you should have found
typing those two lines easier than with a text editor. The tab
would have been added automatically, for example, and now
when you type print square (10) on a separate line, Komodo
already knows about your new square function and prompts
you to include a value within its brackets. Unfortunately,
Komodo Edit doesnt integrate the running functionality
within the application, which means you need to run your
scripts semi-manually. Pressing Ctrl+R or selecting Tools >
Run Command from the menu opens a small dialog, and into
this you need to type
%(python) %F. All this is
doing is replacing %(python)
with the name of the default
Python executable, as defined
within the Preferences panel,
and %F with the full path to
the script youre currently editing. Running this command will
attempt to execute the script, printing any output into a new
panel that appears below the editor. If there are any errors,
theyll also appear in the Command Output panel and you
can click on the errors to force the editor to jump to their
position within the file youre editing.
Komodo includes many templates for starting a project, but if you find
yourself with the same setup each time, you can also create your own.
Syntax checking
You should also take a look at the Syntax Checking Status
page. This updates in real-time to show you any errors that
creep into your code as youre typing, such as an incorrect
indentation when you create a new function in Python. This is
also why you need to make sure the correct language is preconfigured from the drop-down menu on the bottom-right of
the screen, as this is where Komodo loads all its languagedependent intelligence from. This is set automatically when
you create a Python project. You should also see that when
you do create a function in the editor, small square brackets
in the left encase those sections of code that are logically
disconnected from the main flow of execution. You can click
on the small plus icons to fold these sections away to make
your code easier to read. Many IDEs, and even editors, offer
the same facility. In fact, the simple process weve just
Part-time programmers
Some developers argue that code completion promotes
lazy programming because, they say, you never really learn
a language while you let an application complete function
names for you and highlight any mistakes. We think this is
partly true, but it doesnt take into account hobbyist or parttime programmers. Professionals spend every day of their
working lives surrounded by code, so their best option is
always going to be to master the language they rely on for
a living. It will happen without them trying. But for those of us
who code only occasionally, when our schedules allow, tools
such as code completion and syntax highlighting can make
us more productive. And this is where IDEs can make
a massive difference. Q
19
Code concepts
Code concepts:
Write a program
Jonathan Roberts shows you how to re-implement classic Unix
tools to bolster your Python knowledge and build real programs.
n the next few pages, were aiming to get you writing real
programs. Over the next few tutorials, were going to
create a Python implementation of the popular Unix tool
cat. Like all Unix tools, cat is a great target because its small
and focused on a single task, all the while using several
different operating system features, including accessing files,
pipes, and so on.
This means it wont take too
long to complete, but at the
same time will expose you to
a selection of Pythons core
features in the Standard Library,
and once youve mastered the
basics, its learning the ins-and-outs of your chosen languages
libraries that will let you get on with real work.
Python files
The final program well be implementing. Its not long, but it makes use of a lot
of core language features youll be able to re-use time and again.
20
Code concepts
If you put all this in a file, make it executable and create
a hello.txt file in the same directory, youll see that it works
rather well. There is one oddity, however theres an empty
line between each line of output.
The reason this happens is that print automatically adds
a newline character to the end of each line. Since theres
already a newline character at the end of each line in hello.txt
(there is, even if you cant see it, otherwise everything would
be on one line!), the second newline character leads to an
empty line.
You can fix this by calling print with a second, named
argument such as: print(line, end=). This tells print to put
an empty string, or no character, at the end of each line
instead of a newline character.
Passing arguments
This is perfectly fine, but compared to the real cat command,
theres a glaring omission here: we would have to edit the
program code itself to change which file is being displayed
to standard out. What we need is some way to pass
arguments on the command line, so that we could call our
new program by typing cat.py hello.txt on the command
line. Since Python has batteries included, this is a fairly
straightforward task as well.
The Python interpreter automatically captures all
arguments passed on the command line, and a module
called sys, part of the Standard Library, makes this available
to your code.
Even though sys is part of the standard library, its not
available to your code by default. Instead, you first have to
import it to your program and then access its contents with
dot notation dont worry, well explain this in a moment.
First, to import it to your program, add:
import sys
to the top of your cat.py file.
The part of the sys module that were interested in is the
argv object. This object stores all of the arguments passed on
the command line in a Python list, which means you can
access and manipulate it using various techniques weve
seen in past Code Concepts and will show in future ones.
There are only two things
you really need to know
about this. They are:
The first element of
the list is the name of
the program itself all
arguments follow this.
To access the list, you need to use dot notation that
is to say, argv is stored within sys, so to access it, you type
sys.argv, or sys.argv[1] to get the first argument to
your program.
Knowing this, you should now be able to adjust the code
we created previously by replacing hello.txt with sys.argv[1].
When you call cat.py from the command line, you can then
pass the name of any text file, and it will work just the same.
The output of the real Unix command, cat, and our Python re-implementation,
are exactly the same in this simple example.
Many files
Of course, our program is meant to accept more than one file
and output all their contents to standard output, one after
another, but as things stand, our program can only accept
one file as an argument!
To fix this particular problem, you need to loop over all the
files in the argv list. The only thing you need to be careful of
when you do this is that you exclude the very first element,
21
Code concepts
Code concepts:
Add features
Jonathan Roberts tour of the Python programming language
continues, as we write a clone of the Unix cat command.
The Python language comes with all the bells and whistles you need to write
useful programs. In this example, you can see the replace method applied to
a string in order to remove all white space in the tutorial, we used the rstrip
method for a similar purpose.
22
Code concepts
The Python 3
website provides
excellent
documentation
for a wealth of
built-in functions
and methods.
If you ever
wonder how to
do something
in Python, docs.
python.org/3/
should be your
first port of call.
23
Code concepts
Code concepts:
Put it all together
Jonathan Roberts guide to the Python programming language
continues. In this tutorial, were going to finish our clone of cat.
Objects
Last time, we ended by saying that there are many ways we
could implement the line counting option in our program.
Were going to show you how to do it in an object-oriented
style, as it gives us an excuse to introduce you to this aspect
of Python programming. You could, however, with a bit of
careful thought, implement the same function with some
Python objects
Just to prove that it works, heres our cat implementation, with all of the
options being put to use.
24
Code concepts
Earth that was about. Well, it is the main distinguishing
feature between methods and ordinary functions.
Methods, even those with no other arguments, must have
the self variable. It is an automatically populated variable,
that will always point to the particular instance of the object
that youre working with. So self.count is a count variable
thats exclusive to individual instances of the
catCommand object.
The completed program isnt very long, but it has given us a chance to
introduce you to many different aspects of the Python language.
if len(args) > 1:
for a in args:
f = open(a, r)
c.run(f, options)
else:
c.run(sys.stdin, options)
Weve not filled in the object parsing code from last
time, because that hasnt changed. Whats new is the c =
catCommand() line. This is how we create an instance of a
class, how we create a new object. The c object now
has a variable, count, that is accessible by all its methods
as the self.count variable. This is what will allow us to track
line numbers.
We then check to see whether any arguments have been
passed. If they have, we call the run method of the object
c for each file that was passed as an argument, passing in
any options extracted
by OptParse along the
way. If there werent any
arguments, wed simply
call the run method with
sys.stdin instead of
a file object.
The last thing we need to do is actually call the main
function when the program is run:
if __name__ == __
main__:
main()
These last two lines are the oddest of all, but quite useful
in a lot of circumstances. The name variable is special when
the program is run on the command line, or otherwise as
a standalone application, it is set to main; when its imported
as an external module to other Python programs, its not.
This way, we can automatically execute main when run
as a standalone program, but not when importing it as
a module. Q
25
Code concepts
Code concepts:
Data modules
Jonathan Roberts introduces a way of untangling the mess of
your code and adding structure to your programs.
26
Code concepts
By splitting
your code up
in to smaller
chunks, each
placed in its
own file and
directory, you
can bring order
to your projects
and make future
maintenance
easier.
print food
def show_choc():
food = [snickers, kitkat, dairy milk]
print food
show_choc()
print food
If you run that, youll see that outside the function the
variable food refers to a list of fruit, while inside the function,
it refers to a list of chocolate bars. This small program
demonstrates two different scopes: the global scope of the
current module, in which food refers to a list of fruit, and the
local scope of the function, in which food refers to a list of
chocolate.
When looking up a variable, Python starts with the
innermost variable and works its way out, starting with the
immediately enclosing function, and then any functions
enclosing that, and then the modules global scope, and then
finally it will look at all the built-in names.
In simple, single-file programs, its a bad idea to put
variables in the global scope. It can cause confusion and
subtle errors elsewhere in your program. Modules help with
this problem, because each module has its own global scope.
As we saw above, when we import a module, its contents are
all stored as attributes of the modules name, accessed via
dot notation. This makes global variables less troublesome,
although you should still be careful when using them. Q
Python style
While many people think of Python as
a modern language, its actually been
around since the early 1990s. As with
any programming language thats been
around for any length of time, people
who use it often have learned a lot about
the best ways to do things in the
language in terms of the easiest way
27
Code concepts
Code concepts:
Data storage
Jonathan Roberts explains how to deal with persistent data and
store your files in Python.
Writing to files
Suppose youre writing your own RSS application to replace
Google Reader. Youve already got some way to ask users to
enter in a list of feeds (perhaps using raw_input(), or perhaps
using a web form and CGI), but now you want to store that list
of feeds on disk so you can re-use it later when youre
checking for new updates. At the moment, the feeds are just
stored in a Python list:
feeds = [http://newsrss.bbc.co.uk/rss/newsonline_uk_
edition/front_page/rss.xml, http://www.tuxradar.com/rss]
To get the feeds in to the file is a simple process. Just use
the write method:
for feed in feeds:
file.write({0}\n.format(feed))
28
Serialising
Working with files would be much easier if you didnt have to
worry about converting your list (or dictionary, for that
matter) in to a string first of all for dictionaries in particular,
this could get messy. Fortunately, Python provides two tools
to make this easier.
The first of these tools is the pickle module. Pickle
accepts many different kinds of Python objects, and can then
convert them to a character string and back again. You still
have to do the file opening and closing, but you no longer
have to worry about figuring out an appropriate string
representation for your data:
import pickle
...
with open(lxf-test.txt, a) as file:
pickle.dump(feeds, file)
Code concepts
If youre interested in persistent data in Python, a good next stopping point is the ZODB object database. Its much
easier and more natural in Python than a relational database engine (www.zodb.org).
Shelves
Of course, some code bases have many different objects that
you want to store persistently between runs, and keeping
track of many different pickled files can get tricky. Theres
another Python standard module, however, that uses Pickle
underneath, but makes access to the stored objects more
intuitive and convenient: the Shelve module.
Essentially, a shelf is a persistent dictionary that is to
say, a persistent way to store key-value pairs. The great thing
about shelves, however, is that the value can be any Python
object that Pickle can serialise. Lets take a look at how you
can use it. Thinking back to our RSS reader application,
imagine that as well as the list of feeds to check, you wanted
to keep track of how many unread items each feed had, and
which item was the last to be read. You might do this with a
dictionary, eg,
tracker = { bbc.co.uk:
{ last-read: foo,
num-unread: 10, },
tuxradar.co.uk: { last-read: bar,
num-unread: 5, }}
You could then store the list of feeds and the tracking
details for each in a single file by using the shelve module,
like so:
import shelve
shelf = shelve.open(lxf-test)
shelf[feeds] = feeds
shelf[tracker] = tracker
shelf.close()
There are a few important things that you should be aware
of about the shelve module:
The shelve module has its own operations for opening and
closing files, so you cant just use the standard open function.
To save some data to the shelf, you must first use a
standard Python assignment operation to set the value of
a particular key to the object you want to save.
As with files, you must close the shelf object once finished
with, otherwise your changes may not be stored.
Accessing data inside the shelf is just as easy. Rather than
assigning a key in the shelf dictionary to a value, you assign
a value to that stored in the dictionary at a particular key:
feeds = shelf[feeds]. If you want to modify the data that
was stored in the shelf, modify it in the temporary value you
assigned it to, then re-assign that temporary value back to
the shelf before closing it again.
Thats about all we have space for this tutorial, but keep
reading, as well discuss one final option for persistent data:
relational databases (eg, MySQL). Q
29
Code concepts
Code concepts:
Data organisation
Jonathan Roberts uses SQL and a relational database to add
some structure to his extensive 70s rock collection.
Relational databases
are used to ask complex
questions about data.
Relationships
Lets start by thinking about the information we want to store
in our music collection. A logical place to start might be
thinking about it in terms of the CDs that we own. Each CD
is a single album, and each album can be described by lots
of other information, or attributes, including the artist who
created the album and the tracks that are on it.
We could represent all of this data in one large,
homogeneous table like the one below which is all well
Duplicated data
30
Album
Free At Last
Free At Last
Artist
Free
Free
Track
Travellin Man
Track Time
2:34
3:23
Album Time
65:58
65:58
Year
1972
1972
Band Split
1973
1973
Relational database
Album Name
Free At Last
Running Time
65:58
Year
1972
Artist_id
and good, but very wasteful. For every track on the same
album, we have to duplicate all the information, such as the
album name, its running time, the year it was published, and
all the information about the artist, too, such as their name
and the year they split. As well as being wasteful with storage
space, this also makes the data slower to search, harder to
interpret and more dangerous to modify later.
Relational databases resolve these problems by letting us
split the data and store it in a more efficient, useful form. They
enable us to identify separate entities within the database
that would benefit from being stored in independent tables.
In our example, we might split information about the
album, artist and track into separate tables. We would then
only need to have a single entry for the artist Free (storing
the name and the year they split), a single entry for the
album Free At Last (storing its name, the year published
and the running time), and a single entry for each track in
the database (storing everything else) in each of their
respective tables.
All that duplication is gone, but now all the data has been
separated, what happens when you want to report all the
information about a single track, including the artist who
produced it and the album it appeared on? Thats where the
relational part of relational database comes in.
Every row within a database table must in some way
be unique, either based on a single unique column (eg
unique name for an artist, or unique title for an album), or
a combination of columns (eg album title, year published).
These unique columns form what is known as a primary
key. Where a natural primary key (a natural set of unique
columns) doesnt exist within a table, you can easily
add an artificial one in the form of an automatically
incrementing integer ID.
We can then add an extra column to each of our tables
that references the primary key in another table. For example,
consider the table above. Here, rather than giving all the
information about the artist in the same table, weve simply
specified the unique ID for a row in another table, probably
called Artist. When we want to present this album to a user, in
conjunction with information about the artist who published
Code concepts
it, we can get the information first from this Album table, and
then retrieve the information about the artist, whose ID is 1,
from the Artist table, combining it together for presentation.
SQL
That, in a nutshell, is what relational databases are all about.
Splitting information into manageable, reusable chunks of
data, and describing the relationships between those chunks.
To create these tables within the database, to manage the
relationships, to insert and query data, most relational
databases make use of SQL, and now that you know what a
table and a relationship is, we can show you how to use SQL
to create and use your own.
After logging into the MySQL console, the first thing we
need to do is create a database. The database is the top-level
storage container for bits of related information, so we need
to create it before we can start storing or querying anything
else. To do this, you use the create database statement:
create database lxfmusic;
Notice the semi-colon at the end of the command all
SQL statements must end with a semi-colon. Also notice that
weve used lower-case letters: SQL is not case sensitive, and
you can issue your commands in whatever case you like.
With the database created, you now need to switch to it.
Much as you work within a current working directory on the
Linux console, in MySQL, many commands you issue are
relative to the currently selected database. You can switch
databases with the use command:
use lxfmusic;
Now to create some tables:
create table Album (
Album_id int auto_increment primary key,
name varchar(100)
);
create table Track (
Track_id int auto_increment primary key,
title varchar(100),
running_time int,
Album_id int
);
The most obvious things to note here are that weve
issued two commands, separated by semi-colons, and that
weve split each command over multiple lines. SQL doesnt
care about white space, so you can split your code up
however you like, as long as you put the right punctuation in
the correct places.
As for the command itself, notice how similar it is to the
create database statement. We specify the action we want
to take, the type of object were operating on and then the
properties of that object. With the create database
statement, the only property was the name of the database;
with the create table statement, weve also got a whole load
of extra properties that come inside the parentheses and are
separated by commas.
These are known as column definitions, and each commaseparated entry describes one column in the database. First,
we give the column a name, then we describe the type of data
that is stored in it (this is necessary in most databases), and
then after that we specify any additional properties of that
column, such as whether or not it is part of the primary key.
The auto_increment keyword means that you dont have
to worry about specifying the value of Track_id when inserting
data, as MySQL will ensure that this is an integer that gets
incremented for every row in the database, thus forming a
primary key. You can find out more about the create table
statement in the MySQL documentation at http://dev.
mysql.com/doc/refman/5.5/en/create-table.html.
Code concepts
Code concepts:
Data encryption
Learn the principles behind encryption Ben Everard unpacks
how it prevents snoopers from reading your data.
Symmetric encryption
Here were going to take a look at a simple (and fairly
insecure) method of symmetric encryption (also known as
private key encryption) for text that uses the XOR (exclusive
OR) function. XOR takes two binary digits as an input (which
can each be either 0 or 1). It outputs a 1 if one of the two
inputs is a 1, and a 0 if either none or both is (See the XOR
Truth Table, right).
We can XOR strings of data by XORing each item in turn.
That simple XOR provides all we need for our encryption
method which well get onto in a minute. First well look at the
information were going to encrypt.
Text, like all computer data, is stored in binary as 1s and
0s. ASCII text encoding stores each character as a string of
eight bits of binary information. For example, B is
01000010, e is 01100101 and n is 01101110. ASCII text, then
is just a chain of these characters, each one eight bits long.
Ben, therefore is 010000100110010101101110.
Now, back to our encryption method. Were going to use a
password thats just a single character (we told you it wasnt
going to be secure!). It can be any single character. And our
encryption method is to XOR each letter of our text with our
key (See XORing characters box, above-right).
That ciphertext is our unreadable text. Without knowing
the key, theres no way a program can read it or is there?
We know the key (its A), but how can we use this to get
the original text back? Actually, its really simple. Back in
Asymmetric encryption
Symmetric encryption is great for securing
files, but it has some problems when securing
communication. For starters, you need a secure
way of sharing the keys with everyone. If you wanted
an encrypted communication with, for example,
Google, youd somehow need to obtain a key, and
this key would need to be different for every person
Google communicated with otherwise theyd be
able to eavesdrop.
To get around this, we have asymmetric
encryption (which is also known as public key
encryption). In this method there are two keys,
32
Code concepts
XORing characters
b
Text
Key A
XOR
Cypher text
XOR
0
XOR
1
We can encrypt a stream by XORing each character in turn with our key.
Statistical attacks
We now have our method of encryption and decryption, but
its not very secure. First of all, there are only 256 possible
keys (if we include all eight bit strings, and not just the ones
that have ASCII characters), so its perfectly feasible for an
attacker to check every one in turn. However, it turns out they
dont have to.
In English text, some characters are very common, and
others are quite rare. For example, the space character
usually makes up 1520% of all the characters in a piece of
text and the lower-case E about 10% while lower-case Z can
be as little as 0.02% and capital Z almost never.
Since a given character in our text will always evaluate to
the same character in our cyphertext, these proportions will
come across. For example, given the cyphertext:
00001001 00011110 00000011 01110001 00110010 00111000
00100001 00111001 00110100 00100011 00100010 01111101
01110001 01100001 00100010 01110001 00101000 00111110
00100100 01110001 00100010 00110100 00110100 01111101
01110001 00110000 00100011 00110100 01110001 00111110
00111110 00100101 01110001 00100010 00110100 00110010
00100100 00100011 00110100
We can see that the most common 8-bit string is: 01110001.
Input 2
XOR
Code concepts
Code concepts:
Spot mistakes
Bug reports are useful, but you dont really want to cause too many.
Alex Cox explains what to avoid and how to avoid it.
t doesnt matter how much care you put into writing your
code. Even if youve had four cups of coffee and triplecheck every line you write, sooner or later you are going to
make a mistake. It might be as simple as a typo a missing
bracket or the wrong number, or it could be as complex as
broken logic, memory problems or just inefficient code. Either
way, the results will always be the same at some point, your
program wont do what you wanted it to. This might mean it
crashes and dumps the user back to the command line. But it
could also mean a subtle rounding error in your tax returns
that prompts the Inland Revenue to send you a tax bill for
millions of pounds, forcing you to sell your home and declare
yourself bankrupt.
The IDLE
Python IDE has a
debug mode that
can show how
your variables
change over time.
34
Code concepts
you put into a comment the better, but dont write a book.
Adding comments to code can be tedious when you just want
to get on with programming, so make them as brief as you
can without stopping your flow. If necessary, you can go back
and flesh out your thoughts when you dont feel like writing
code (usually the day before a public release). When you start
to code, youll introduce many errors without realising it. To
begin with, for example, you wont know what is and isnt a
keyword a word used by your chosen language to do
something important. Each language is different, but Pythons
list of keywords is quite manageable, and includes common
language words such as and, if, else, import, class and
break, as well as less obvious words such as yield, lambda,
raise and assert. This is why its often a good idea to create
your own variable names out of composite parts, rather than
go with real words. If youre using an IDE, theres a good
chance that its syntax highlighting will stop you from using a
protected keyword.
Undeclared values
A related problem that doesnt affect Python is using
undeclared values. This happens in C or C++, for instance, if
you use a variable without first saying what type its going to
be, such as int x to declare x an integer. Its only after doing
this you can use the variable in your own code. This is the big
difference between compiled languages and interpreted ones.
However, in both languages, you cant assume a default value
for an uninitialised variable. Typing print (x) in Python, for
instance, will result in an error, but not if you precede the line
with x = 1. This is because the interpreter knows the type of a
variable only after youve assigned it a value. C/C+ can be
even more random, not necessarily generating an error, but
the value held in an uninitialised variable is unpredictable until
youve assigned it a value.
Typos are also common, especially in conditional
statements, where they can go undetected because they are
syntactically correct. Watch out for using a single equals sign
to check for equality, for example although Python is pretty
You have to be
careful in Python
that the colons
and indentation
are in the correct
place, or your
script wont run.
But this does
stop a lot of
runtime errors.
Comment syntax
Different languages mark comments differently, and there seems to be
little consensus on what a comment should look like. However, there are
a couple of rules. Most languages offer both inline and block comments,
for example. Inline are usually for a single line, or a comment after a piece
Bash
BASIC
C
C++
HTML
# A hash is used for comments in many scripting languages. When # is followed by a ! it becomes a shebang # and is used to
tell the system which interpreter to use, for example: #!/usr/bin/bash
REM For many of us, this is the first comment syntax we learn
/* This kind of comment in C can be used to make a block of text span many lines */
// Whereas this kind of comment is used after the // code or for just a single line
<!-- Though not a programming language, weve included this because youre likely to have already seen the syntax, and
therefore comments, in action -->
Java
/** Similar to C, because it can span lines, but with an extra * at the beginning */
Perl
= heading Overview
As well as the hash, in Perl you can also use something called Plain Old Documentation. It has a specific format, but it does
force you to explain your code more thoroughly
=cut
Python
As well as the hash, Python users can denote blocks of comments using a source code literal called a docstring, which is
a convoluted way of saying enclose your text in blocks of triple quotes, like this
35
Ruby
Ruby
I
37
Ruby
Ruby: Master
the basics
Juliet Kemp introduces the ins and outs of the Ruby
programming language enough to write your first program.
Quick
tip
Indentation doesnt
matter from a code
point of view,
but the Ruby
community prefers
two-character
indentation.
the return value is, as expected, our string. Note that IRB is
smart enough to recognise that the method isnt finished
until the end line and doesnt return anything until then.
However, its not necessary to use return to get a return value
from a Ruby method. Try this:
> def hithere2
?> Hello; no return
?> end
> => nil
> hithere2
> => Hello; no return
Ruby methods will automatically return the evaluation of
the last line of the method. So you need return only if you
have multiple possible return values, or to improve code
Install Ruby
RVM (the Ruby Version Manager) is
the easiest way to install Ruby.
Among other things, it allows you to
install and use multiple versions of
Ruby on one machine, which may
come in handy later on in your
Ruby experience. If you have Git
installed, all you need is:
\curl -L https://get.rvm.io | bash -s
stable --ruby
(yes, the backslash is correct). This
38
Ruby
clarity with more complex code. To run code as a file rather
than in IRB, just put the commands in a file with the extension
.rb, and run it with ruby myfile.rb. Alternatively, you can add
a shebang line at the top, make the file executable, and call it
anything you like:
#!/usr/bin/ruby -w
puts I can run Ruby!
Note the -w flag, which turns on warnings this is good
practice. You can run this file (once executable) with
./myfile.rb.
Quick
tip
Ruby treats both
semicolons and
newline as the end
of a statement. An
operator (+, -, \,
etc) at the end of
a line indicates a
continuation. Other
whitespace is
usually ignored
use the -w switch
to flag up the rare
occasions where it
is used to interpret
ambiguous
statements.
Running version one of the code a couple of times; including fixing an error
where I left an old line hanging around at the bottom of the file.
39
Ruby
Create a class
Quick
tip
When looking for
a method, Ruby
will try the named
class first, then its
parent, up the
inheritance chain.
Here, theres only
one ancestor: the
basic Object class
(check this with
Note.superclass).
You can explicitly
call the ancestor of
a method youre
overriding with
super.
Running the
code on the
notes generated
by the first
version has
errors, as there
are no titles;
once thats fixed,
it runs fine. The
second set of
edits allow me to
input a new note.
40
Ruby
Documentation
Its always a good idea to document your code
clearly for others (or for yourself at a later
time!). One popular option for Ruby is TomDoc
(http://tomdoc.org). Heres how that looks
with the final version of our Note class:
# Public: class to define a note. class Note
@@notes = 0
@@notebook_file = notebook.txt
# Public: Initialize a Note.
#
# title - The String title of the Note.
# body - The String body of the Note.
def initialize(title, body)
class Note
@@notes = 0
def initialize(title, body)
@title = title
@body = body
@@notes += 1
end
def Note.total_notes
Total notes: #{@@notes}
end end
Class variables are written as @@foo. We set it at the top
of the class, then increment it in the constructor every time
a new Note is created. To find out the value of a class variable,
we can create a class method, using Class.classmethod, as
here. Add this line to the end of the file, after youve added
your two notes, to call the method:
puts Note.total_notes
You can also refer to a class variable within a regular
instance method, so you could do the same thing with an
instance method:
class Note
def total_notes
Total notes: #{@@notes}
end end
puts myNote.total_notes
However, that means having to call it via a particular note.
Conceptually, it makes more sense to use a class method.
Another way to refer to a class method is to use self.total_
notes. Its just a matter of preference. You may have noticed
that in this version of the program, your notes dont last from
one instance of the program running to the next one. Lets roll
in the File interaction we used before to write out to a file. To
see the version of the code for this last part of the tutorial,
download the archive from www.linuxformat.com/files/
ca2015.zip. Add this method to the Note class:
class Note
@@notebook_file = notebook.txt
def write_to_file
nbk = File.open(@@notebook_file, a)
nbk.puts(@title + , + @body)
nbk.close
end end
myNote = Note.new(Note 1, this is a note)
myNote.write_to_file
# comment out the rest of the file for now, for ease of testing
Our write_to_file method does what it says on the tin:
writes a given note to the end of the general notebook file
(defined as a class variable). Run this, then have a look at
def self.notebook_file
# ... code here ...
end
# Public: Gets/Sets the String title and body of
the Note.
attr_accessor :title, :body
You should state what the method does,
describe any arguments, and give a return value.
Constructor (initialize) and attribute (attr
accessor, etc) methods can be shorthanded as
shown here. TomDoc is designed to be both
human-readable and machine-parsable; check
the webpage out for more information.
Quick
tip
You might want to
error-check here
that you only have
two values in the
array. Try:
if note.length
!= 2
puts There
is a problem:
note has too
many fields!
next
end
Experimentation
Irb, Interactive Ruby, is a great tool for
experimenting and trying out code
snippets. Making good use of irb can
really speed up code production. For
example, if you enter a string, then a
dot, then hit Tab, irb will give you a list
of the methods you can use on a String
object. Since in Ruby everything is an
object, this works for anything you
input. If completion isnt working, try
irb --readline -r irb/completion.
ri provides online Ruby documentation.
41
Ruby
Ruby: Add a
little more polish
Build on your Ruby reorganise your code, learn about modules
and blocks, and introduce a few tests with Juliet Kemp.
42
Ruby
call the module Notebook, and the Note class will be
Notebook::Note.
This also means that its good practice to create a
lib/notebook/ subdirectory to keep the library files in, for
ease of navigation at a later date. A Notebook::Foo module
will then be in lib/notebook/foo.rb, which makes it easy
to find.
So, we can shift the Note class wholesale into lib/
notebook/note.rb, and just add a Module line at the top:
module Notebook
class Note
# see the DVD for the code
end end
Next, well shift the reading-in-from-file code into lib/
notebook/reader.rb. Currently, this code looks like this:
nbk = File.open(Note.notebook_file, r)
while line = nbk.gets do
note = line.split(,)
if note.length != 2
puts There is a problem!
next
end
thisNote = Note.new(note.first, note.last)
puts thisNote.to_s
end
That doesnt look much like a class just yet. What we want
is a Reader constructor that takes a file as its parameter,
then has a read method that reads and outputs the notes
(of course, this isnt the only way to organise this code; you
can probably think of several different ways off the top of your
head. Feel free to play around with them).
Lets rewrite Reader:
module Notebook
# Public: class to read back notes from a file
class Reader
# Public: initialize the reader
#
# file - The String name of the file to read in from
def initialize(file)
@file = file
end
# Public: read back from the notebook file
#
# Returns nothing
def read
File.open(@file, r) do |f|
while line = f.gets do
note = line.split(,)
if note.length != 2
puts There is a problem!
next
end
puts Note.new(note.first, note.last).to_s
end
end
end
end
end
This demonstrates an important feature of Ruby: the
block. The while...do...end is one style of block, which is
probably familiar to you from other languages, and does what
youd expect: execute the code between do and end while
f.gets continues to return lines.
The other block is here:
File.open(@file, r) do |f|
Ruby in blocks
Blocks in Ruby are used to interact with
methods. The basic format is:
variable.method do |n|
# put some code here that operates on
the variable n
end
The variable n is identified by method,
then the code block is passed into
method, and is applied to each n in
turn. So when opening a file, File.
open(@file, r) do |f| opens a file and
creates a filehandle, here f. The code
block after this will then be applied to f.
We wont delve into the code that
makes this possible just yet, but this
Quick
tip
To make the
program create the
file if it doesnt
exist, go back to
Reader.read and
replace notebook
= File.open(@file,
r) with notebook
= File.open(@file,
a+). This is readappend mode and
creates a file if that
file doesnt exist.
43
Ruby
Testing, one, two, three
Quick
tip
Running a test
suite is easy. Just
create test/test_
suite.rb, with the
following lines:
require_relative
test_note
require_relative
test_reader
and run it with
ruby notebook/
test/test_suite.rb.
44
end
should have total_notes return 2 do
assert_equal Total notes: 2, Notebook::Note.total_notes
end
end
end
This generates these tests:
test: With no notes have total_notes return 0
test: With notes should output title and body string for to_s
test: With notes should have last line of file equal to test
note values after write_to_file
test: With notes should have total_notes return 2
The string labels for the context and should blocks are
just labels; they can be whatever you like. Within the context
block, the setup block will be run once for each test. You can
also nest context blocks if you want (see the boxout earlier for
more on blocks).
Testing
assert_equal does what youd expect from the name.
The first argument should be what you expect, and the
second argument what you actually get. Theres a full set
of assertions available via Test::Unit, and documented at
RubyDoc; they include assert_not_equal, assert_raise,
assert_throws, and so on. Shoulda also adds assert_
contains and assert_same_elements, for working
with arrays. Run this with ruby test/test_note.rb. It turns
out that we get a failure:
1) Failure: test: With notes should have last line of file equal
to test note values after write_to_file. (TestNote) [notebook/
test/test_note.rb:32]:
<test_title, test_body> expected but was
<test_title,test_body>.
Theres a misplaced space in there. We need to decide
whether we want the space (in which case we edit the code),
or dont (in which case we edit the test).
Note that the tests run in alphabetical order, first of the
context blocks, and then of the should blocks, within each
context block. This means that our zero notes test, for
example, needs to be alphabetically first, or the class variable
total_notes will already be incremented.
If you have a situation like this, you could consider either
running the tests separately, or numbering the tests to be
clear about the requirement. The same issue happens with
have total_notes return 2; the number returned will depend
on how many other tests are run before this one. In fact, this
also draws attention to a problem with total_notes more
generally: as things stand, it only keeps track of notes created
during this session, rather than tracking how many notes are
in the notebook file. This is a code problem rather than a test
problem, though!
We could also run some more tests for example, we
could change the code to require that either or both of the
note title and body are non-nil, and test accordingly.
Another issue with the tests as they currently stand is that
our test are messing up our actual notebook file! It would
certainly be worth changing the code to use a test notebook
but in fact, we want to be able to specify a notebook file
anyway, so well leave that til the next section.
We can also write some tests for the Reader class:
class TestReader < Test::Unit::TestCase
Ruby
context read do
should return nothing when reading test file do
test_file = testfile
note = Test Note,test body
File.open(test_file, w) {|f| f.write(@note) }
reader = Notebook::Reader.new(test_file)
assert_equal nil, reader.read
File.delete(test_file)
end
end
end
You may have noticed the problem here: the Reader class
currently just outputs to the console, and doesnt return
anything.
There are ways to test console output, but we wont go
into them here because were going to do a bit of rewriting of
this class anyway. Youll see another way of writing a File
block, though:
File.open(test_file, w) {|f| f.write(@note) }
As with the Reader class, this opens the file, runs the block
(in {}) on it, then closes it when the block is finished.
Remember to delete the test file afterwards!
exit
end
opts.parse!(argv)
end
end
end
end
Were going to set up an accessor method for
@notebook, so it can be used elsewhere in the code. Then
initialize() just takes the arguments passed in when creating
the class, and runs the private parse() method. This is where
the work is done. Check out the box for the details on the
various OptionParser methods.
Now we need to fix up Runner to use the options:
require_relative options # as well as other files
class Runner
attr_reader :options
def initialize(argv)
@options = Options.new(argv)
end
def run
reader = Reader.new(@options.notebook)
# ... rest of code as before
end
Parsing options
When creating a new OptionParser, you use
a do block to set up how it behaves in various
situations. (new() yields itself when called with
a block see the other boxout for more
on blocks).
opts.banner() creates a heading banner for
any output produced.
opts.on() adds an option switch and handler
for that switch. The first argument is a short
switch (you could miss this out if you prefer),
45
Ruby
end
And edit notebook/bin/notebook to pass in commandline arguments:
require_relative ../lib/notebook/runner
runner = Notebook::Runner.new(ARGV)
runner.run
If you try running it without an argument, it should now
work; but if you try bin/notebook -n myfile, nothing will be
written to myfile. This is because we still have the notebook
file hard-coded in lib/notebook/note.rb. So lets take out
This or that?
Trying out
various option
switches from
the command
line.
46
OUT
NOW!
DELIVERED DIRECT TO YOUR DOOR
2UGHURQOLQHDWwww.myfavouritemagazines.co.uk
RUQGXVLQ\RXUQHDUHVWVXSHUPDUNHWQHZVDJHQWRUERRNVWRUH
SERIOUS ABOUT
HARDWARE?
NOW
ON APPLE
NEWSSTAND
Download the
day they go
on sale in the
UK!
PERFORMANCE ADVICE
FOR SERIOUS GAMERS
ON SALE EVERY MONTH
MAKE YOUR
OWN GAMES
Build a platform
game in Minecraft
SUPER SIZED
+SUPER FAST
THROW OUT YOUR HARD DRIVE
4K ON A
BUDGET
499 AOC 4K screen
Tweaking for hi-res
High-end gaming rigs
NEXT-GEN CPU
DEVIL'S
CANYON
PLUS
Screenshots that
look awesome
Clean up your audio
Build your own
music server
Stream to Twitch
easily with Raptr
SAMSUNG 850
PRO 512GB
BUILD A
BUDGET
GAMING PC
THE BEST
GAME ENGINES
CREATE CHARACTERS
How games developers turn
concepts into powerful heroes
NVIDIA
SHIELD
NO.1 FOR
REVIEWS
GIGABYTE Z97
GAMING 5
INTEL CORE
i5-4690K
APPLE iMAC
21-INCH
PLEXTOR M6e
M.2
PLUS
Speed up Windows
The best gaming
headsets revealed
Master PC audio
Make awesome
pixel art in GIMP
VS
SCREEN WARS
ASUS RoG
SWIFT & LG
ULTRAWIDE
G-Sync smoothness
takes on cinematic
gaming immersion
FUTURE
PC TECH!
NEXT-GEN SSD
NO.1 FOR
REVIEWS
CRUCIAL MX100
CORSAIR RAPTORSSD
K40
GAMDIAS EROS
GIGABYTE P34G
JUST
IN
TROUBLESHOOTING TIPS
Ruby
Ruby: Modules,
blocks and gems
Learn more about modules and mixins, blocks and yields, and
how to get your code out there, with Juliet Kemp.
end
This creates an array, and adds each Note to it with <<,
the shovel operator, which adds an item to the end of the
array. It then outputs the whole array. Run this, and you
should see an output a bit like this:
Note: argh, bin
Note: a, b
Note: any, ping
Note: my, dog
Note: test_title, test_body
The main issue is that there is no easy way for the user to
reference each note. Replace that notebook.each line with:
notebook.each_with_index { |val, index| puts #{index}:
#{val} }
We dont need to explicitly call the to_s method; since
were referring to our Note in a String context, Ruby will
automatically use the appropriate to_s method (while were
at it, though, edit the to_s method to remove that extraneous
Note: string). Run this, and your notes will have an index
associated with them:
0: argh, bin
1: a, b
2: any, ping
3: my, dog
4: test_title, test_body
But how are we going to interact with these? More
perturbingly, if you start experimenting and try to refer to this
array from another file, youll find its empty. Whats going on?
Flexible initialization
When you use Foo.new in Ruby, it calls Foo.
initialize. In our code so far, we have some
initialize() methods without any arguments,
and one with an argument (Reader.
initialize(file)). If you call Reader.new with no
argument, Ruby will throw an error. But what if
we wanted to set a default value? (in our code
@file = file
end
This will use a variable passed into the
constructor if there is one (Reader.new(foo.
txt)) and notebook.txt if not. You could also set
a constant earlier in the file and use that (def
initialize(file = DEFAULT_NOTEBOOK)).
49
Ruby
Singletons
The problem with the code on page 49 is that every time you
create a new Reader, youll also create a new notebook array,
which will make it impossible to be sure that youre always
referring to the same array (or that the array has any notes
in it).
What we want instead is a Singleton class, which can be
instantiated only once. Happily, Ruby provides a module to do
this. Well create a singleton NoteStore class to go alongside
Reader, and move some of our functionality into that:
require singleton
module Notebook
class NoteStore
include Singleton
attr_accessor :notebook_array
def initialize
@notebook_array = Array.new
end
def add(note)
@notebook_array << note
end
def edit(number)
new_note = @notebook_array[number].edit
@notebook_array[number] = new_note
end
def delete(number)
@notebook_array.delete_at(number)
end
def output
@notebook_array.each_with_index { |val, index| puts
#{index}: #{val} }
end
end
end
You may at this point notice that this all looks quite clear
and straightforward, which is often a good sign that your code
is doing the right thing. The reading in will still be done in
Reader (see below), but this new class is used to store and
access the array data.
Were doing much the same as we were with the array in
Reader. The magic happens with that include Singleton line.
This makes NoteStore use the Singleton module, which is
an example of how Ruby uses modules as mixins, to provide
an inheritance mechanism. See the boxout (Modules, classes
and mixins, oh my, p51) for more on this.
Singularity
What the Singleton module does, among other things, is to
disable the new() method and add an instance() method.
The first time the class is called, a new object will be created;
but the new() method is private, so it cant be called by any
other classes or modules. NoteStore.new would return
an error.
This ensures that there is one, and only one, instance of
the class. It also creates a method called instance(), which
allows you to access this single instance of the class.
To make use of this, Reader now looks like this:
class Reader
# no @notebook or @total_notes variable needed
def read
File.open(@file, a+) do |f|
# while loop as before, but take out total_notes
50
NoteStore.instance.add(Note.new(note.first, note.last))
end
NoteStore.instance.output
end
def write_all
File.open(@file, w) do |f|
NoteStore.instance.notebook_array.each { |x| f.puts(x) }
end
end
def total_notes_string
total_notes = NoteStore.instance.notebook_array.length
Total notes: #{total_notes}
end
end
Reading in from the file adds each element to the array in
our NoteStore instance, then we output it to the user. Writing
the array out again (once weve edited it) overwrites the
existing file content, again accessing the contents of that
NoteStore array. And we can use the array length to get our
total notes info. NoteStore then calls a Note.edit method, so
lets next write that:
class Note
def edit
puts Current title is: #{title}; enter new title or enter to
keep
new_title = gets.chomp
if (new_title != )
@title = new_title
end
puts Current body is: #{body}; enter new body or enter to
keep
new_body = gets.chomp
if (new_body != )
@body = new_body
end
return self
end
end
Again, this is straightforward. If theres a new title or body,
we change the values in the Note, and return the Note itself.
In NoteStore.edit, that new note is used to replace the old
one in the notebook array.
Finally, then, Runner and Options need to be set up to fire
all of this off. First, we add an edit option to Options:
class Options
attr_reader :notebook, :add, :read, :edit, :edit_number
def parse(argv)
# rest of code here as before
@edit = false
OptionParser.new do |opts|
opts.on(-e, --edit NUMBER, Integer, Edit a specific
note) do |number|
@edit = true
@edit_number = number
end
end
end
Now we add code to Runner to handle it:
require_relative notebook
module Notebook
class Runner
Ruby
def run
reader = Reader.new(@options.notebook)
# read and add options as before
if @options.edit
reader.read
if @options.edit_number >= NoteStore.instance.
notebook_array.length
puts No note of that number; cant edit!
return
end
NoteStore.instance.edit(@options.edit_number)
reader.write_all
end
end
end
end
The only thing to really draw your attention to here is the
error-handling; we need to check that the number to edit
actually exists within the array.
Try now with ruby bin/notebook -r to see what notes you
have, then ruby bin/notebook -e 2 to edit the note with
index 2, then ruby bin/notebook -r again to see what you
now have. It should all work as expected.
In fact, as you might already have thought, Reader and
NoteStore could just as well be the same (singleton) class; try
making that change to the code yourself.
Quick
tip
end
things_with_five { |x| puts 3 * x }
This outputs 15. If you swap in 15 / x for 3 * x in the block,
youll get 3. Now try this:
def things_with_five_and_ten
yield 5
yield 10
end
things_with_five_and_ten { |x| puts 3 * x }
Output is 15, then 30. Effectively, what happens is this
pseudocode:
things_with_five_and_ten:
puts 3 * 5
puts 3 * 10
Each time yield takes the block, sticks first 5, then 10, into
the x variable, and runs the block.
The yield statement runs the code you write in the block,
but applies it in the context of its own method.
Now lets write a NoteStore.do_to_all method, which will
apply a change to every single note, a Note.edit_title method
as a test, and an option in Runner to call it:
class NoteStore
def do_to_all
i = 0;
51
Ruby
Blocks in
action!
while @notebook_array[i]
new_note = yield @notebook_array[i]
@notebook_array[i] = new_note
i += 1
end
end end
class Note
def edit_title(title)
@title = title
return self
end end
class Runner
def run
# ... code as before
if @options.all
reader.read
NoteStore.instance.do_to_all do |n|
n.edit_title(@options.all_title)
end
reader.write_all
end
end
end
52
require notebook/notestore
require notebook/options
require notebook/reader
require notebook/runner
This helps to ensure that namespaces work properly and
no one steps on anyone elses toes.
Well also rewrite notebook/bin/notebook just a little to
fit the new gem structure:
#!/usr/bin/env ruby
begin
require notebook
rescue LoadError
require rubygems
require notebook
end
runner = Notebook::Runner.new(ARGV)
runner.run
The begin/rescue/end control structure here avoids
requiring RubyGems. If someone is not using RubyGems to
manage their path, you dont want to force them to do so. But
Ruby
its reasonable to use RubyGems in the rescue__ block, as
a second chance to load your gem if it hasnt been found by
whatever the local path management system is.
Your Gem will need a version number, and best practice is
to store that in lib/notebook/version.rb:
module Notebook
VERSION = 0.0.1
end
Next, we need to write a basic gemspec, notebook.
gemspec, which lives in the top directory of your module.
A gemspec is a specification for your gem, with a list of
attributes, most of which are optional.
The required ones are date, name, summary, and
version. platform and require_paths are technically also
required, but both have defaults that should work fine, so you
neednt specify them yourself. Heres a short gemspec for
our gem:
# -*- encoding: utf-8 -*# lib = File.expand_path(../lib, __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require notebook/version
Gem::Specification.new do |s|
s.name
= notebook
s.version = Notebook::VERSION
s.date
= 2013-02-10
s.summary = Notebook
s.description = A notebook gem to hold single-line notes
s.authors = [Juliet Kemp]
s.email
= juliet@example.com
s.files
= `git ls-files`.split(\n)
s.test_files = `git ls-files test/*`.split(\n)
s.executables = notebook
s.homepage = end
The lines at the top allow your version file to be located.
Check the Rubygem documentation for more gemspec
options you might want. One common one is to specify
runtime dependencies, but as our only external library,
OptionParser, is part of the main Ruby install, theres no need
to include it here.
Git is the strongly recommended way to keep track of the
files in a Gemspec without having to write them all out (some
gem/bundle tools wont work at all if theres no git repo), but
a flat list of files here would of course also do the job. Its
important to list all of the files in your gem; any files not listed
here wont be available to the gem, which will cause it
to break.
Its also good practice to write a README.md file to
demonstrate usage; heres a very brief one:
# Notebook
## Installation
gem install notebook
## Usage
require notebook
notebook -h for help information on the command line
Normally, youd include API information here, but our gem
is currently designed to be used from the command-line
rather than within another piece of Ruby code.
Finally, build and install the gem:
gem build notebook.gemspec
gem install ./notebook-0.0.1.gem
You should now be able to type just notebook -r and have
your notebook read back (one thing to bear in mind is the
current location of your notebook file; you may need to
specify it on the command line if youve moved directories).
Youre now ready to contribute your (ruby) gems of code
wisdom to the community as a whole, as you continue along
your new Ruby programming path. Q
Here were
building and
installing our
gem package.
53
Ruby
Ruby on Rails:
Web development
Gavin Montague introduces us to Ruby on Rails, a powerful
framework that puts programmer happiness first.
Get started
Although your distro almost certainly has packages available
for Ruby, were going to use rbenv to get us up and running.
Rbenv allows us to create and manage entirely isolated
versions of Ruby inside a users home environment. Ruby
developers use this to hop between different versions of Ruby
as projects require, but its main use for us is to avoid touching
the root-owned system and ensure that we all have a
consistent starting point.
Start by installing rbenv and ruby-build via the Git source
code management tool; if you dont have Git, your package
manager will be able to provide it. Open a terminal and type:
$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
Make your shell aware of rbenv by adding it to your
start-up files. If you dont have .bash_profile in your home
directory, change the end of the command to ~/.profile, or
The console
Rails is a web framework, but its not
just accessible in your browser.
bundle exec rails console
This starts the interactive console.
You can execute code directly from
within your Rails environment but
without the need for a browser. For
54
Introducing Ruby
Ruby is intended to be a language that makes programmers
productive and happy. The syntax lends itself to very clear
expression of ideas, and a working grasp of its simple but
powerful features can be picked up in an hour or two.
In technical terms, Ruby is an Object Oriented language:
everything you interact with is treated as a self-contained
box of data that you perform operations on via methods.
For example:
a = a string
a.reverse # gnirts a
array = [3, 2, 1]
array.sort # [1, 2, 3]
5.next # 6
In Ruby, youll spend most of your time writing classes,
best thought of as a blueprint for creating objects. Here, we
create a new class of Person, who knows that they have
a name and how to return a greeting.
class Person
attr_accessor :name
def initialize(name)
self.name = name
end
def greet
Hi, Im #{name}
end
end
bob = Person.new Bob
bob.greet => Hi, Im Bob
An interesting feature of Ruby is that parentheses are
largely optional, and certain non-alphabetic characters can be
Ruby
Set up Rails
Rails is distributed as a gem: the Ruby communitys standard
package format, so lets install it and generate the skeleton
for our application:
$ gem install bundler
$ gem install rails --version=3.2.12
$ rbenv rehash
$ rails new todolist --skip-test-unit
Rails is an opinionated framework. Theres a correct place
for everything in its world, and this is enforced by all projects,
starting with the pre-defined file structure. If you use Git for
source control management, youll also appreciate that Rails
has dropped off appropriate gitkeep and gitignore files.
Dependencies on other Ruby libraries are managed via the
gemfile at the root of your new project. Open it up and add
the following around line three.
gem therubyracer, ~> 0.11.4
group :development, :test do
gem rspec-rails, ~> 2.13.0
end
Then, back in Terminal, well install these gems with
bundler and run some post-install magic. Well use these
libraries next month, when well learn about behaviour-driven
development, but for now they can be ignored.
$ bundler install
$ rbenv rehash
$ rails generate rspec:install
Rails scaffold tool might not produce the prettiest pages in the world, but its
a great way of getting up and running quickly with well-designed code.
55
Ruby
By describing our tables in Ruby rather than SQL, we remain
adaptable. Lets now build the database and start Rails builtin server:
$ bundle exec rake db:migrate
$ bundle exec rails server
Open http://localhost:3000/tasks in your browser to
start interacting with the scaffold by adding, editing and
deleting items.
On the new/edit actions pages, youll see how Rails has
read the format of our database columns and generated
appropriate field types corresponding to their format:
checkbook for boolean, selects for dates, textareas for big
strings. If you need to quit the Rails server, just press
Control+C.
Notice how Rails has also decided your URL structure for
you. By default, Rails adheres to a REST architecture (http://
en.wikipedia.org/wiki/Representational_state_transfer).
In practical terms, this means that all Rails projects
automatically share the same relationships between object,
controllers and URLs. This makes building open-source
extensions easier because all apps will share the same
assumptions about where functionality should live. The file
config/routes.rb is responsible for mapping URLs to
controllers, actions and parameters, as well see later. You can
do an awful lot with Rails routing language, but the default will
serve our purposes today.
The model
Open your text editor inside the ./app directory, and well
start examining models/task.rb.
Not a lot there, is there? Rails ActiveRecord classes take
care of all the interactions with your database and leave you
to think about the behaviour of the object. All our model
initially contains is a list of attributes that can be set by bulkassignment. Rails will infer what attributes a task object
should have, based on the columns of its database table.
We just have to make sure we protect any sensitive fields that
users shouldnt be able to set as they edit records.
Additionally, Rails provides a huge number of commonly
needed functions for database-backed applications, such
as the management of inter-model relationships and
management of validity. A task isnt much use if you dont
know what it is, so lets make sure we always have a title
before allowing tasks to be saved.
Add the following inside the Task class anywhere
between the first line and the final end:
validates :title, :presence=>true
Go back to your browser and try creating a new task
without a title youll be presented with an error state. The
Rails 4
The most recent version of Rails at
the time of writing is 4.1.6, which
includes some exciting features to
make developers and users even
happier. These are:
Support for streaming data to the
browser.
Turbolinks AJAX goodness to speed
up every request.
A common API for background
queuing.
And a lot more!
56
The controller
When you submitted the task form, how did Rails translate it
into data and how did it then know to display the form again
or redirect us off to the show action? To answer that, we look
at controller/tasks_controller.rb
Each method in our tasks_controller becomes the entrypoint for a users browser request. Depending on the URL
and the type of request (POST, GET, etc), Rails routing
system will execute one action on one controller inside your
application and deliver the output back to the browser. Take
a look at the update method
def update
@task = Task.find(params[:id])
respond_to do |format|
if @task.update_attributes(params[:task])
format.html { redirect_to @task, notice: ... }
format.json { head :no_content }
else
format.html { render action: edit }
format.json { render json: @task.errors, status:
:unprocessable_entity }
end
end
end
Ruby
In not a lot of code we accomplish quite a lot. We start by
finding one of our tasks via the Task class find method and
the id parameter that Rails has extracted from the URL.
We then go into a responds_to block, which is Rails way of
dealing with different output types. Were not dealing with
json here, so focus on the HTML formatters.
First, we try to update_attributes on our task object with
the data passed in from the form. This will either return true
or false, depending on what state our task has been put into.
If we fail to save the task, usually because of a validation error,
the controller renders our edit action. If we are successful, the
browser is redirected to the @task itself. Rails assumes that if
we ask to redirect to an object rather than a URL then there
must be a show action of a controller named after the
objects class.
Notice here what we didnt have to do. The incoming
request was accessed directly: Rails automatically parsed it
into Ruby objects for us to manipulate. We didnt have to
know about the internals of our task object. Our controller
simply relied on @task to manage its own state, and instead
concerned itself with the result. Finally, by following Rails
naming conventions we were able to redirect/render as
required without directly calling URLs or template files.
This might not seem like a lot, but consider that pretty
much every page of every web application on the planet is
a collection of showing, editing, updating, creating or
destroying database records. Rails provides just the right level
of abstraction to make the whole process almost seamless.
You can always go deeper if need be, but an awful lot can be
achieved without the need to do this.
The view
Finally, lets look at our templating language. Open the views
directory, and youll see we have five files: in Rails terms, four
are actions and one is a partial. All are written in Rails
default templating language, ERB.
Essentially, ERB is just Ruby injected into text files.
Although this means you can technically call any Ruby
method in your templates, it would be exceptionally bad form
to do so. Templates should only concern themselves with the
presentation of data. Generally speaking, if you find more
than two consecutive lines of Ruby in a template then
somethings wrong.
Inside our edit template, youll see that it just renders
a different template: the form partial. This makes a lot of
Ruby
Ruby on Rails:
Code testing
Gavin Montague shows how Test Driven Development
can improve your code and catch bugs.
58
gem libnotify
Then install them and start guard:
$ bundle install && rbenv rehash
$ bundle exec guard init
$ bundle exec guard
Now, as you save files in your project,
guard will intelligently run the matching
parts of your suite. It can also hook into
several desktop notification systems to
provide more visual feedback on how
youre progressing.
Ruby
feature of Test Driven Development. Before we write any
production code we first define what the code should do by
way of a test. In this context the test serves two purposes:
it acts as a target for us to work towards and as a line over
which we dont step. We write just enough code to pass our
current test and then re-evaluate where we are before either
starting on a new test or altering our existing code. As you
build up code through multiple cycles, two trends should
naturally emerge.
Your code will become simpler as a result of focusing on
writing just enough to hop to the next green stage. Too many
developers will go off on flights of fancy writing large, tightly
coupled, overly complex methods and classes that become
impossible to debug. A Test Driven system is more likely to
be composed of many tiny interconnected parts that can
all be operated independently and are easy to understand
in isolation.
Additionally, youll spend less time chasing developmental
dead-ends. And because you start by writing a test that
actually exercises the code as its finally intended to be used,
youll become much more aware of dependencies and flaws
in the interface design.
The TDD development cycle can be summed up as red,
green, refactor. We start with a failing test: red. We then write
just enough code to make the test pass: green. Finally, we
refactor our new code for maintainability and performance,
safe in the knowledge that if we break anything our suite will
catch it.
A full discussion of the merits and drawbacks of Test
Driven Development could fill up an entire bookshelf, but if
youre interested in the theory and evidence behind the
technique, I recommend you take a look at this paper
from dare I say it Microsoft, which provides a
comprehensive overview (http://research.microsoft.com/
en-us/groups/ese/nagappan_tdd.pdf). But thats enough
theory, lets look at TDD in practice.
It might not look like much, but if you can see this, congratulations! Youve
test-driven your first feature.
59
Ruby
environments and config/database.yml files. Running our
tests via rake will ensure that our test database is created
and correctly reset for each test.
Our test suite can be run via rake, either in full or by
providing one of the test subsets, such as models:
$ bundle exec rake spec
$ bundle exec rake spec:models
Fixing a bug
If you run the full suite from the last tutorials app youll see
that were starting with half a dozen errors. The output is too
big to print here, but if you read through it youll see that the
error relates to our requirement that a new task cant have
a due_at time in the past. If you try to save a task that has
no due_at date set, an exception is thrown. Lets fix that
by opening spec/models/task_spec.rb and adding a
failing test:
describe #due_at_is_in_the_past do
it doesnt throw an exception if due_at is nil do
lambda {
Task.new(:due_at=>nil).due_at_is_in_the_past
}.should_not raise_error
end
end
Here we use a different expectation to trap any raised
exception and report it back as a test failure. In this test were
doing more than just capturing the bug in code: were also
suggesting the correct outcome.
If a task isnt supplied with a due_at value then it should
simply save nil. Run this test with rake spec:models and
watch it explode. Our tests are failing because our record tries
to compare a Time object with nil: a no-no in Ruby. We now
adjust our Task class:
def due_at_is_in_the_past
errors.add(:due_at, is in the past!) if (due_at && (due_at <
Time.zone.now ))
end
Our test now passes! Theres not much to refactor so well
skip that step and run our full suite. Its good practice to do
this after each successful cycle to make sure our changes
havent broken any other part of the app.
In order to better prioritise our time, items that are due
soon should appear in red on our index. To implement this
well first add the concept of due_soon? to our model. As
before, start by writing a test in task_spec.rb that expresses
the code we want to be able to call:
describe #due_soon? do
it is true if due in less than 24 hours do
task = Task.new(:due_at => Time.zone.now + 23.hour)
task.should be_due_soon
end
60
Helper tests
Its bad practice to put anything other than the most
minimal flow control in templates, so well put our
formatting logic in a helper. Add a failing test to spec/
helpers/tasks_helper.rb:
describe task_title_formatter(task) do
before do
@task = Task.new(:title=>task)
end
it adds a due css class to tasks which are due_soon do
@task.due_at = Time.zone.now
task_title_formatter(@task).should == <span
class=due>task</span>
end
Ruby
it adds no extra classes to tasks which arent due_soon
do
@task.due_at = nil
task_title_formatter(@task).should == <span>task</
span>
end
end
Again, Id recommend you add the tests one at a time and
try to develop incrementally towards a solution, which should
look something like this:
module TasksHelper
def task_title_formatter(task)
if task.due_soon?
<span class=due>#{task.title}</span>.html_safe
else
<span>#{task.title}</span>.html_safe
end
end
end
Finally, you will need to update your index.html.erb view
to call task_title_formatter(title) and add .due to your
application.css.scss file (well look at why this isnt just a
plain CSS file in the next and final tutorial). If you start up the
Rails server your index page should now look something like
the grab on p59.
Controller test
Notice that our new feature didnt actually need to be tested
in either the controller or the view. Rails tends towards a
style of design known as Fat Model, Skinny Controller.
Where we have custom functionality its often pushed down
to the model or, for presentation data, out to a helper. A
well-designed Rails controller will usually contain very little
code because all the web-specific stuff, like session-handling,
URL parsing and header generation, is handled automatically
by Rails. That said, its the controllers responsibility to
manage the users login status, permissions etc and these
should be tested thoroughly.
Once a task has been completed it should no longer
be editable through the web interface. Lets drive out this
feature in two parts: well remove the edit links from our
tasks show page and stop our controller from allowing
updates. Well start with the controllers spec.
Go to the description of our update method around line
105 of tasks_controller_spec.rb and start a new nested
describe block:
describe PUT update do
describe where the task has already been completed do
before do
@task = Task.create! valid_attributes
@task.update_attribute :done, true
end
it does not update the task do
put :update, {:id => @task.to_param, :task => { title =>
t }}, valid_session
Task.any_instance.should_not_receive(:update_
attributes)
end
it redirects the user back to the index do
put :update, {:id => @task.to_param, :task => { title =>
t }}, valid_session
response.should redirect_to(:action=>:index)
end
Our tests here are slightly different from before.
Remember I said that a controllers main job is to
orchestrate other objects. This means that were not so
much interested in the outcome of some actions, but
whether the actions trigger other events. In our first test we
attach an expectation directly onto our Task class using a
mocking library. A mock object can be used in place of a real
one, but may have additional behaviour. If you were testing a
library to transfer money between bank accounts it could get
pretty expensive to run your tests against real APIs. Instead
you would mock out the various responses the bank could
give (ok.xml, fail.xml etc) and run tests against them. Here
we use a mock to make sure that no update_attributes call
is made to any task.
You can alter the update method in tasks_controller.rb
to pass the tests:
respond_to do |format|
if @task.done?
format.html { redirect_to tasks_url, notice: Completed
tasks cant be changed. }
elsif @task.update_attributes(params[:task])
View testing
Finally, we can test our view. Open up show.html.erb_spec.
rb and add a test against a completed task:
it doesnt link to edit on complete tasks do
@task.done = true
render
rendered.should_not match(/Edit/)
end
That test will fail, so make it pass by updating
show.html.erb:
<%= link_to Edit, edit_task_path(@task) unless @task.
done? %>
Congratulations, youve now test-driven your second
feature. Try extending the behaviour to not show edit links on
the index page. Remember to write your tests first.
Weve only grazed the surface of TDD here, but I hope its
given you some idea of how useful it can be in helping your
development workflow. The key to getting the greatest value
from it is to apply it from the very start of your project and to
avoid the temptation to skip steps of the Red-Green-Refactor
loop. Over time, youll get faster at writing tests and faster at
deciding what tests should be written, and thats the path to
TDD happiness.
In the next tutorial well look at how Rails simplifies the
client-side aspects of web development with the assetspipeline and the JavaScript compiler CoffeeScript. Q
61
Ruby
Ruby on Rails:
Site optimisation
Gavin Montague shows how Rails, with help from Ajax,
CoffeeScript and SASS, can help front-end development.
JavaScript
has a mixed
reputation
because of
its occasional
eccentricities.
CoffeeScript
tries to fix them
and you can try
it out for yourself
in the browser.
62
Unobtrusive JavaScript
Rails was one of the first web frameworks to integrate Ajax
into its core. This made for faster, more responsive sites,
but the markup it generated wasnt notably good quality.
That all changed in Rails 3, which generates clean, semantic
markup and integrates with a range of JavaScript frameworks
in an unobtrusive manner. Lets look at how easy it is to add
Ajax interactivity to our to-do application. Before we get
started, youll need a good set of front-end developer tools.
Ajax on Rails
When you delete a task from the list, youll be met with a
JavaScript confirmation before you can proceed. But where
does this come from? Inspect the page source and youll see
that each delete link contains:
<a [...] data-confirm=Are you sure?>delete</a>
Rails uses HTML5 data attributes to describe behaviours
to a JavaScript framework in this case jQuery in an
unobtrusive way. The markup of the page isnt littered with
script tags or inline oclock attributes. This behaviour is
injected in afterwards, as is best practice.
In the <head> of your page youll see our scaffold has
already included jQuery and jquery_ujs libraries, which deal
with setting up and monitoring the behaviour indicated by
these extra attributes. If you havent used jQuery before, Id
recommend taking a look at the official tutorial (http://bit.
ly/13t9K7S). If you have a preferred JavaScript framework,
theres likely to be an analogous adaptor library available for
use with Rails.
To let jQuery know that our delete links should use Ajax we
only need to make two small changes to our code. Update the
delete link to include:
link_to Destroy, task, remote:true, method: :delete, data: {
confirm:
Are you sure? }
In your tasks controller, flip the responds_to block inside
the delete method:
respond_to do |format|
format.json { head :no_content }
format.html { redirect_to tasks_url }
end
Ruby
CoffeeScript & SASS everywhere!
If youre keen on trying out CoffeeScript or
SASS, but not fortunate enough to be into Ruby
on Rails, no problem! Both are standalone tools
that can be used in any project, albeit without
the built-in integration that Rails provides. SASS
is distributed as a Ruby Gem and can usually be
installed system-wide with one line:
sudo gem install sass
Once its installed you simply have to tell
SASS which scss files you want it to watch and
Reload the page and the rendered links will now contain a
data-remote attribute. This tells jQuery to intercept any clicks
on the link and submit the request via an XMLHttpRequest
instead. Open the network console in Firebug then delete a
task. Youll see the request being fired off in the background.
Refresh the page and the task will be gone, but thats not
the most user-friendly behaviour. It would be better if our
user got an instant visual feedback. Create a new file at
app/assets/javascripts/ajax_tasks.js and add:
$(document).on(ajax:success, .index-table a[datamethod=delete],
function() { return $(this).closest(tr).fadeOut(); });
If youve used jQuery before, this should look relatively
familiar. We tell our document to watch for any successful
Ajax pseudo-event that originates from links with the delete
data-method. When we receive this event we fade out the
table row around the
originating link. Save your
changes, refresh the page
and try deleting a task.
Rails requests
Using CoffeeScript
None of these things make JavaScript a bad interpreted
programming language per se, but its a language that a lot of
developers dont look forward to working with. Thats why we
have CoffeeScript, the precompiler for JavaScript.
To quote its website:
Underneath that
awkward Java-esque
patina, JavaScript has
always had a gorgeous
heart. CoffeeScript is an
attempt to expose the
good parts of JavaScript in a simple way. The CoffeeScript
project isnt part of Rails, but out of the box, our to-do app
has been set up to seamlessly work with the compiler.
Lets look at a small piece of CoffeeScript to demonstrate
its key features. Create a new file at app/assets/
congratulations.js.coffee:
@congratulation_bot =
messages: [Well done, Great job, , Top stuff]
name: name
congratulate: (message) ->
alert message+ +@name unless message.length is 0
overcongratulate: ->
for message in @messages
@congratulate message
By amending our Ajax deletion method from earlier, we
can receive the praise of our congratulation bot each time we
delete a task.
window.congratulation_bot.name = Gavin;
$(document).on(ajax:success, .index-table a[datamethod=delete], function() {
window.congratulation_bot.overcongratulate();
return $(this).closest(tr).fadeOut();
});
On the next refresh, Rails will automatically compile the
CoffeeScript and serve the JavaScript output in its place.
This happens seamlessly and youll never have to worry
about making sure an up-to-date version is being served.
CoffeeScript is an attempt
to expose the good parts of
JavaScript in a simple way.
63
Ruby
To see how CoffeeScripts syntax breaks down, go to
http://coffeescript.org and paste our code into the online
compiler. Youll see the output appear on the right-hand side.
Its slightly easier to experiment online with the code than
reloading pages in your app. Lets look at some of the
differences CoffeeScript brings.
CoffeeScript features
If youre
debugging
JavaScript or
examining the
dialogue between
the browser
and the server,
Firefoxs Firebug
extension is a
must-have tool.
SASS
SASS extends CSS with variables and nesting, which well
see below, along with mixins (reusable chunks of CSS),
mathematical operations (for example, set the margin to
line-height/2) and even colour maths (for example, set
the background to halfway between blue and green).
The true value of SASS doesnt become apparent until
youre managing large stylesheets and sadly our little
application doesnt quite meet that criterion. Well just look
at two of the most important features of SASS: nesting and
variables. Append the following to app/assets/stylesheets/
tasks.css.scss and then reload the page to see it in action.
$header_color: #0000FF;
$danger_color: #FF0000;
.fancy_text {
font-weight:bold;
color: $header_color;
}
h1 { @extend .fancy_text; }
#error_explanation {
h2 { background:$danger_color; }
}
table {
th { @extend .fancy_text; }
a[data-method=delete] {
color:$danger_color;
}
}
With SASS we are able to nest CSS selectors and use it to
organise our styles into a more visible hierarchy. We also gain
the ability to extract common colours as meaningfully named
Debugging Javascript
Developing JavaScript and Ajax behaviour is
best done with a good client-side extension to
your browser, such as Firebug for Firefox. Install
Firebug as you would any Firefox extension, and
then press F12 to bring up its extensive set of
tools. The Console and Net tabs are of most
interest to us. Both of these are disabled by
default, but you can click on their little black
triangle icons to activate them.
The Console gives you access to a JavaScript
REPL that runs within the scope of your page.
64
Ruby
variables, rather than having to always refer back to an
external style guide to remind us which shade of red is which.
The SASS website has excellent documentation on all the
languages features. Even if you dont use Rails, Id urge you to
have a read and see how you can incorporate it into your web
framework of choice.
Speed is important. Users have a spectacularly low
attention span and any lag in your site will cost you visitors.
Amazon famously established a relationship between
response time and income where a 100 millisecond reduction
in load time resulted in a 1% bump in revenue. While you
should always try to optimise the server-side component (by
correctly indexing your database, cache templates etc) a
more substantial improvement is optimising how a browser
will render your site. Rails provides a set of built-in features
(collectively called the Asset Pipeline) which attempts to
maximise the delivery speed of your pages.
Speed is important.
Users have a spectacularly
low attention span.
65
s
.
<
.
.
-
.
:
)
);
_
),
t
a
<
I
-
,
$
);
);
Lesser-known Languages
More languages
More languages
T
67
More languages
C and beyond:
Mike Saunders takes you on a whirlwind tour of three programming
languages and toolkits, showing how to make a starfield effect in each.
but its a really good way to discover how these languages
work in action, and how identical goals are achieved using
their varying approaches. Were not going to provide lengthy,
meandering introductions to the languages the best thing
you can do is read the code and our short explanations,
and then start hacking around with it yourself. So, without
further ado
68
More languages
Code a starfield
stars[i].y = rand()%480;
stars[i].speed = 1 + rand()%16;
}
for(i = 0; i < SDL_NUMEVENTS; i++) {
if (i != SDL_QUIT)
SDL_EventState(i, SDL_
IGNORE);
}
while (SDL_PollEvent(NULL) == 0) {
SDL_FillRect(screen, NULL, SDL_
MapRGB(screen->format, 0, 0, 0));
for(i = 0; i < MAX_STARS; i++) {
stars[i].x -= stars[i].speed;
if(stars[i].x <= 0)
stars[i].x = 640;
p = (Uint8 *) screen->pixels +
stars[i].y * screen->pitch + stars[i].x * screen->format>BytesPerPixel;
*p = 255;
}
SDL_UpdateRect(screen, 0, 0, 0, 0);
SDL_Delay(30);
}
return 0;
}
Not bad, eh? In just over 50 lines of code, we have a
snazzy starfield effect, all using plain C and SDL without any
fancy layers on top. To compile the binary from this, enter:
gcc -o starfield starfield.c `sdl-config --cflags` `sdl-config
--libs`
Note the backtick characters here the key to generate
them will probably be at the top-left of your keyboard.
Basically, anything inside backticks is treated as a command,
and the output from them is generated before the complete
command (from gcc onwards) is processed. So if you type
sdl-config --libs on its own, for instance, you can see the
compiler parameters required to build SDL programs. To run
the compiled program, enter ./starfield.
Anyway, lets look at the code. The first two lines tell the
compiler simply that we want to include header files for the
standard library (so that we can generate random numbers
later), and SDL (so that we can use routines from the library).
Then we have a #define line, which tells GCC that all
instances of MAX_STARS from here onwards in the source
code should be replaced with the number 100. This allows us
to experiment with the number of stars by changing just one
line, instead of having to make changes all over the code.
Next up, we define a structure that is, a collection of
variables that can be referenced together under one name.
We call this star_type, and every variable we create with this
type will have X and Y coordinate variables inside it, along with
a speed (theyre all integer numbers). With this line:
star_type stars[MAX_STARS];
we create a new array of star_type structures, called stars.
So, in the coverdisc version of the code, thats an array of
100 stars, which can be referenced from star[0] to star[99].
My main man
Now its time to kick off the code itself, inside the main()
function (the first one that is executed in a C program and
indeed the only one in our case). We declare i as an integer
variable that well use for counting purposes later. Then we
declare p as an unsigned 8-bit integer pointer, which is
a variable that well use to point to the graphics data later
on. And, lastly, we have a screen, a pointer that well use
with SDL.
To make the
screenshot more
exciting, heres
what the C
implementation
with 2000
double-size stars
whizzing around.
69
More languages
x is less than zero), we start it again from the right-hand side,
at 640.
The two lines beginning with p and *p are used to plot the
stars. In the first, we set our p pointer variable to point at the
exact area of the graphics data where the pixel should be
plotted. This data is stored inside the pixels member of the
screen structure, and because its a linear one-dimensional
row of bytes we need to do some maths to match it up with
our 2D image. Once we have p pointing to the right place, we
place the number 255 meaning white inside the byte that
it points to by dereferencing it (using the star).
Finally, we tell SDL that were finished with our drawing
operations so it should render the whole lot to the screen, and
then have a delay of 30 miliseconds so that it doesnt run too
quickly. And were done!
This might seem a bit complicated if youve never done
any C programming before, so lets move on to the higherlevel alternatives. Once you have those sussed out, come
back to this one and it will be clearer.
Even humble
Ncurses, mixed
up with a bit of
Perl, is capable of
parallax-scrolling
starfields.
Compared with what weve just done, life is a lot easier when
youre using Python and its Pygame library. Working with
graphics is a lot simpler, and you dont have to fiddle around
with pointers. Heres the code its called starfield.py, and if
youve got Pygame installed then you can run it with ./
starfield.py. If youre new to Python you should find this code
quite readable, but its important to note the indentation here,
which is vital for program flow. Code belonging to blocks
(such as loops) must always be indented.
#!/usr/bin/env python
import pygame
from random import randrange
MAX_STARS = 100
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
stars = []
for i in range(MAX_STARS):
star = [randrange(0, 639), randrange(0, 479),
randrange(1, 16)]
stars.append(star)
while True:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit(0)
A path to follow
Were bound to start a few flame wars here, but
anyway If you want to spread your wings and
become a great all-round programmer, these
are the languages wed recommend exploring:
Assembly This will get you familiar with the
nuts and bolts of programming, working directly
with hardware and memory. Telling the CPU
directly what to do is way more satisfying than
magically obscuring things behind compilers.
x86 is a bit of a mess, so to start with get a ZX
Spectrum or Commodore 64 emulator, and try
out their assembly languages (Z80 and
6502 respectively).
70
More languages
screen.fill((0,0,0))
for star in stars:
star[0] -= star[2]
if star[0] < 0:
star[0] = 640
screen.set_at((star[0], star[1]), (255, 255,
255))
pygame.display.flip()
Logic-wise, this implementation is very similar to the C
one, so you can compare features in the languages. We start
off by telling Python that we want to use the Pygame module
and the randrange number-generating routine from the
random module, and then set up a variable called MAX_
STARS which has the same purpose as its C equivalent.
Then we get Pygame fired up and create a window, assigning
it to a variable called screen, before setting up a background
timer using Pygames clock facility, to slow things down
a bit later.
Next comes the construction of our star array (or list in
Python parlance), in the form of stars = []. You can see that
Python is a lot more flexible than C, and you dont have to
declare the size of an array at the start. What we do here is
create, step by step, 100 star objects and provide each of
them with three values: the X coordinate, the Y coordinate,
and the speed, just like in the C version. After creating each
star object we drop it into our stars list using append.
Then theres the while True loop, which is the main loop.
First, we have a delay using clock.tick, and then we tell
Pygame to process any keyboard or mouse events coming in
(so it can quit the program if the user closes the window).
Then, we use the fill routine of our screen object to black out
the window, and start cycling through the stars. For each star,
we subtract its third element (speed) from its first (X
position), making the stars move left (elements in an array or
list are counted from zero). Like in the C version, we check to
see if the star has gone off the left-hand side of the screen.
Then we plot a white pixel (255, 255, 255 in RGB format)
at the X and Y positions, and were done with the star
processing loop. Lastly, we call the display flip routine, which
renders all of our previous drawing operations to the screen.
Overall, its shorter, simpler and easier to read than the
C/SDL version, and you can imagine that writing games in
Pygame is a lot of fun.
Get hacking!
Once youve spent a bit of the time with
these programs, why not see if you can
expand them? Some ideas:
Try experimenting with different
window sizes and dimensions.
For the C and Python versions, you
can add a few lines so that the stars
have random colours.
Take input from the keyboard to
affect the direction of the stars and
their speed.
For C and SDL, see the documentation
website at www.libsdl.org/cgi/docwiki.
cgi the quick guide is especially
$star_y[$i] = rand(24);
$star_s[$i] = rand(4) + 1;
}
while (1) {
$screen->clear;
for ($i = 0; $i < $numstars ; $i++) {
$star_x[$i] -= $star_s[$i];
if ($star_x[$i] < 0) {
$star_x[$i] = 80;
}
$screen->addch($star_y[$i], $star_x[$i],
.);
}
$screen->refresh;
usleep 50000;
}
By now, the general structure of this code should make
a lot of sense to you. Theres one major difference here,
though; instead of having an array of stars containing
coordinates and speeds for each one (ie, an array of arrays),
Quick
tip
If youre thinking
about writing
a game, check out
the Allegro library
(http://alleg.
sf.net). Its stable,
mature, crossplatform and many
impressive games
have been written
with it, as you can
see at www.allegro.
cc (browse the
Action category in
Projects on the left,
for instance).
More languages
Scheme: Learn
the basics
To help you get used to learning new languages, Jonathan Roberts explains
how to get started with the simple but popular Scheme.
Installing Scheme
Almost all Linux distributions come with a Scheme
interpreter built in. Its called Guile, but its pretty complex and
we wont be using it. Instead, were going to use a tool called
Dr. Racket, which you can find in some distributions
repositories or by heading to its official website: http://
racket-lang.org/download.
Dr. Racket is actually designed to work with a particular
dialect of Scheme (which is itself a dialect of Lisp, the
language Emacs is written and extended in) called Racket.
Simple expressions
All programming is really about manipulating information,
data. That data can represent anything, from the ingredients
used to mass produce a certain kind of biscuit to the position
of a robots arms and claws on a factory assembly line.
In order to manipulate this data, we must find ways to
represent it that a computer can understand, and we must be
Dr. Racket
Dr. Racket is a programming environment that
makes coding in Scheme much easier than in a
plain text editor. Its main window is split in two.
The top half can be used for entering, saving
and loading entire programs, made up of many
functions and definitions. When you have
completed work on part of a program and you
want to see how it works, press the Run button
in the top-right of the screen and Dr. Racket will
evaluate the program and display any results in
the bottom half of the window.
As well as being used to display results of
72
More languages
Programming paradigms
At the start of this article, we said one of the
things that sets Scheme apart is its a largely
functional language, as opposed to an objectorientated or imperative one. If youre new to
programming, this might not have meant much
to you, but weve got you covered.
These three obscure sounding titles refer to
different programming paradigms that is,
particular ways of thinking about problems and
programming solutions for them, different styles
of programming.
When programming in an imperative style,
the main focus is on recording and adjusting the
state of the program. This is often done through
the use of variables and functions that modify
their value directly. This particular paradigm
Doing calculations
If you combine numbers with their basic operations, you can
use Dr. Rackets interpreter and Scheme as a simple
calculator, although the syntax is a bit different from what you
might be used to from school:
> (+ 5 5)
10
> (- 8 4)
4
> (* 2 3 4)
24
As you can see, the operation comes first and the
numbers its applied to (the operands) come afterwards;
whats more, the whole expression must be wrapped in
brackets. This syntax has two advantages: first, you can easily
apply the operation to as many numbers as you like without
having to repeat the symbol.
The other advantage is that theres never any question
about what order to perform the calculation in, just
remember that you evaluate the inner-most expression first
and work your way out:
> (+ 3 (* 2 4) (+ 4 (- 8 3)))
20
In this example, you calculate (- 8 3) first, then (+ 4 5) and
(* 2 4), before finally doing (+ 3 8 9). To make it easier to keep
track of all the brackets, Dr. Racket will highlight matching
brackets and which part of the expression they apply to as
you close them off.
Our lives are also made easier thanks to a convention
called pretty-printing. Rather than writing the entire
Variables
When were programming, the data were working with rarely
represents just numbers. For instance, a number might be
the quantity of elderberries needed to make 30 litres of
elderberry wine, or it might be the volume of a bottle needed
to store the wine thats being made.
To make our program more manageable as it becomes
more complex, to make it more readable, one of the most
important things a language can do is let us give the numbers
were working with names.
In Scheme, this is done with the define statement:
> (define pi 3.14)
> (define radius 3)
As you can see, it works just like any of the mathematical
operators in our earlier example. The only difference is that
73
More languages
the operands must come in a certain order: first is the name
you want to give to the variable and then comes the value to
be assigned.
After defining a variable, you can then use it just as you
would any primitive value built in to the language:
> pi
3.14
> (* 2 pi radius)
18.84
A good way to think about how Scheme evaluates this
expression is like you were taught to do algebra in school:
through substitution. It will look up the values for pi and
radius and then re-write the expression with these values in
place: (* 2 3.14 3). After getting down to primitives, it will then
carry out the final evaluation and return the result (this is
a simplification, but its a good way to think about things).
Pretty-printing
helps separate
the function
name and formal
parameters from
the body. Once
defined, we can
use functions
just like any
other primitive
operation.
Conditional expressions
One tool that all languages, including Scheme, have is the
ability to take an action only if a certain condition is true. This
is obviously a vital ability, as its something we do all the time
when following a procedure. For example, if the elderberry
wine is clear on day 35, progress to the next step; if its not,
keep stirring.
In Scheme, this ability is implemented through three
separate tools. The first is the existence of relational
operations that allow us to inspect the relationship between
two things:
> (< 10 5)
#f
> (> 10 5)
#t
> (= 10 10)
#t
< is the less than symbol, and checks whether the number
on the left is less than the number on the right; > is the
greater than symbol and checks whether the number on the
left is greater than the number on the right; finally, = tests
whether the two numbers are equal. These operations will
always return #f for false and #t for true.
The second is the existence of the boolean operations that
allow us to combine the results of multiple relational
expressions:
> (and (> 10 5) (= 10 10))
#t
> (or (< 10 5) (= 10 10))
#t
> (not (> 10 5))
#f
As is ever the case, and returns #t if, and only if, all the
expressions within are true, or if any of the expressions are
true, and not inverts the result of the expression, turning true
to false and false to true.
Case analysis
The final tool that Scheme gives us is the ability to perform
case analysis. This allows us to perform different operations
depending on the result of any of the tests described above.
The general form of a case analysis in Scheme is:
(cond (<test> <expression>)
(<test> <expression>)
.
(<test> <expression>))
74
More languages
By putting
the code for
Fizz-Buzz in
the definitions
window, the
top part of Dr.
Racket, we can
use its debugger
to walk through
its execution
step-by-step.
Fizz-Buzz
For instance, imagine we were making a program to play the
game Fizz-Buzz. The rules of the game say that if a number is
a multiple of 3, it should return the word Fizz, if its a multiple
of 5, it should return the word Buzz and if its a multiple of 3
and 5, it should return Fizz-Buzz:
> (define (fizz-buzz x)
(cond ((and (= (remainder x 3) 0))
(= (remainder x 5) 0))
fizz-buzz)
((= (remainder x 3) 0) fizz)
Exercises
Having learned the basics of Scheme,
you ought to solidify your knowledge by
working on some example problems.
Having a good grasp of the material
covered here will make sure youre
ready for the next tutorial.
EXERCISE 1 Without using Dr. Rackets
interactive interpreter, evaluate the
following expression: (+ 2 (* (- 5 3) 4 (/
2 (- 3 1)))). Rewrite the expression in
pretty-printing format.
EXERCISE 2 Write a procedure, called
> (sum-squares 2 4)
EXERCISE 4 A cinema sets prices
based on age. If youre younger than six,
you get in for free; if youre younger
than 12, you get in for 3; if youre older
than 65, you also get in for 3.
Otherwise, you have to pay full adult
price, which is 5. Complete the
following Scheme procedure to
calculate how much someone has to
pay to go to the cinema.
> (define (ticket-price age)
(...
75
More languages
Ready to get your brain in a twist? In the second part of our guide to Scheme,
Jonathan Roberts introduces recursion and some useful data types.
Recur what?
If youre put off by the weird-sounding name recursion in
the introduction to this article, dont panic as theres nothing
much to it really; in fact, once you know what its all about,
you may think its pretty cool that is, if you enjoyed
Inception.
If youve ever watched that movie, or stood in a hall of
mirrors and seen the way the reflections seem to carry on for
ever, thats recursion the thing that youre interested in
repeats itself within itself: dreams within dreams, mirrors
within mirrors.
To understand how this can be put to work in
programming, imagine that you work for a large publishing
company, with 100 advertising sales people, 30 database
marketers, 20 photographers and 100 magazines, each with
six people working on them. Your boss comes along and asks
you to find out what every single person had for breakfast.
Ugh! you think, what a boring task!.
After procrastinating for a while, you decide that the
easiest way to get out of this is to pass the ball along. You call
the head of advertising and say that the boss wants them to
find out what all their staff had for breakfast, and ask them to
let you have the information once theyve found out; you then
do the same to the head of database marketing, the
Factorials
Exactly the same principle can be applied to code. A popular
example of this is calculating factorials that is, calculating
the number of ways a list of items can be arranged. By itself,
this sounds like a daunting problem to solve. Where on earth
do you start, and how can you be sure that youve got all
the arrangements?
Mathematicians figured out how to do this a long time
ago, and the procedure can be described thus:
If the length of the list is one, then the answer is 1 theres
Iterative recursion
Experienced programmers may know that the
factorial problem is easily solved in other
languages through a more common method.
Instead of writing a recursive function, you can
just assign some variables and write a loop, as
this Python example shows:
def factorial(n):
fact = 1
for x in range(1,n + 1):
fact *= x
return fact
76
www.linuxformat.com
More languages
only one way to arrange a list with a single item in it.
For any other length list, the number of arrangements
equals that lists length multiplied by the factorial of all the
length lists smaller than it.
For example, 1 factorial (written 1!) equals 1; 2! = 2 x 1!; 3! =
3 x 2! x 1! etc...
There are two points to note here. First is that this is
a recursive technique the factorial of any number can be
found by finding the factorial of smaller and smaller numbers.
The second is that theres a base case, that is a simple case
at which the recursion stops and a simple, primitive answer is
returned (in this case, the base case is 1! = 1).
This can be translated in to a Scheme procedure quite
literally:
(define (factorial n)
(cond ((= n 1) 1)
(else (* n (factorial (- n 1))))))
The clever thing about this is that the procedure calls
itself! This is what makes it a recursive procedure. To
understand how this works, you can apply the substitution
model that we talked about last time and work through
a small run of factorial by hand:
(factorial 3)
(* 3 (factorial 2))
(* 3 (* 2 (factorial 1)))
(* 3 (* 2 1))
(* 3 2)
6
Each time the procedure is evaluated, the interpreter
substitutes the formal parameters into the appropriate parts
of the procedures body. Instead of reaching primitive
operations that it can evaluate straightaway, however, it finds
it has to evaluate another procedure itself.
Eventually, the parameter given to factorial will be 1,
which is a primitive and can be evaluated, and the procedure
Exercises
As with the previous tutorial, here are
some Scheme-based exercises to keep
you busy.
EXERCISE 1 Write a procedure that
takes a list as input and returns the
reversed version of it: eg, (1 2 3 4)
becomes (4 3 2 1).
EXERCISE 2 Draw a box and pointer
diagram that shows how you can use
pairs and lists to build a tree structure.
EXERCISE 3 The Towers of Hanoi is a
can unravel itself, just like the managers all reporting back to
the person above them.
Without this base case, however, the recursive procedure
would run forever and your interpreter would eventually give
up. Having a base case, then, is a vital element in all recursive
programming and thinking, and its always wise to start
writing recursive procedures with a base case to ensure you
dont miss it.
Pairs
The other way recursion is put to work in programming is in
creating data representations. To see how this works in
Scheme, we must first introduce one of its fundamental data
structures: pairs. These are Schemes data building blocks.
Remember what we said at the start of the last article:
programming is all about data, and specifically manipulating
that data. So far, weve seen how to write procedures that can
Dreams within
dreams, mirrors
within mirrors
77
More languages
manipulate simple numbers, but its rare that real world data
is this simple.
For instance, we might want to write procedures that can
manipulate information about a CD, including title, artist and
year of production; equally, we might want to write
a procedure that controls the pixels on our monitor, including
their colour and position.
In the pixel example, we could obviously treat each piece
of information separately, but its far more natural to
understand the pixel as a single object thats composed of
the other, smaller bits of data.
In Scheme, it is pairs that
let us represent compound
data. For instance, to
represent a pixel on screen at
position (3, 4), we could create
a pair called point:
> (define point (cons 3 4))
The procedure cons, short for construct, takes two
arguments, which it compounds together in to a single object.
The elements of that object, that is, the x and y coordinates,
can be accessed through two more primitive procedures, car
and cdr.
> (car point)
3
> (cdr point)
4
Calculating distance
Once youve created a new compound object with cons, you
can pass it around just as you would any primitive object. To
see how this works, consider using cons, car and cdr to write
a small procedure that calculates the distance a point on a
graph is from the origin, that is the point (0, 0). To do this you
need to:
Add the squares of the x and y coordinates together
Take the square root of the resulting number
As ever, this translates quite literally in to Scheme. First,
create the square procedure:
(define (square x)
(* x x))
Scope
In the iterative version of factorial that we
demonstrated, you may have noticed that we
defined a procedure inside another procedure.
What was going on here? The first thing you need
to know is that a program is constructed of a
number of environments. These environments
provide a mapping between names, including
primitive or user-defined ones, and the values
and procedures they specify. The environment
can then provide a context for procedures to be
evaluated in. For example:
> (define x 10)
> (define y 7)
> (+ x y)
17
The define procedure adds the names x and y to
the environment, associating them with their
respective values. When the addition is
performed, the interpreter looks up the
procedure associated with the symbol +, and
78
Recursive environments
One problem with the environment model is that
there can only be a single occurrence of each
name in an environment, since otherwise the
interpreter wouldnt know which value or
procedure you meant to refer to when you used
it. The trouble is, within a single program you
might want to refer to different kinds of points in
different circumstances, but youd be unable to
re-use the make-point, get-x and get-y
procedures, since theyd already been used.
To get around this, Scheme has many
environments arranged in a hierarchy. At the top
of the hierarchy is the global environment, which
we saw above. Below this, however, each
procedure gets its own unique environment, with
More languages
reach the base values. As you can see, when using car and
cdr as we did before, you get access to the pairs that are held
in those positions. By calling car or cdr again, you then get
access to the primitive data thats held inside those pairs.
Dreams within dreams, mirrors within mirrors, pairs within
pairs this, as you might have guessed, is a recursive data
structure. It is a pairs ability to combine other pairs that
makes it such a powerful building block, allowing us to build
all kinds of fancy data structures that we can use to represent
the real world with. For instance, pairs can be used to
represent sequences:
> (define li (cons 1 (cons 2 (cons 3 ()))))
> li
(1 2 3)
Here, the cdr of each pair is the next item in the list. The
final pairs cdr is a special symbol, (), that is used by Scheme
to represent an empty list. This is, however, such a common
structure in Scheme that there is some syntactic sugar to
make this easier to code:
> (define l (list 1 2 3))
This will create exactly the same list, only called l and
without all the confusing cons. The elements of a list can be
accessed with successive cars and cdrs:
> (car l)
1
> (car (cdr l))
2
> (cdr l)
(2 3)
The third example here is the most important. Notice that
the cdr of the list is the list minus the first element. Were
going to put this to use in a moment.
list
car
cdr
car
cdr
car
cdr
car
cdr
car
cdr
car
cdr
car
cdr
Recursing a list
Lists, being a recursive data structure, are naturally dealt with
by recursive procedures. Imagine writing a procedure to find
the length of a list. That sounds quite complicated, but if you
recognise that the empty list, at the end of the list, has length
0, then youve got a base case that can be used for making
a simple recursive procedure to do the hard work for you.
You
Advertising
Advertising
managers
Database
marketing
Magazines Photography
Magazine
editors
Magazine
staff
By asking a few people, recursion enables you to find out
what many people had for breakfast. The same technique
can be used to solve difficult programming problems.
More languages
Abstract procedures
Before we look at higher-order procedures, lets start by
looking at the simpler idea of abstract procedures. Imagine
that you run an online shopping business and your users can
construct wish lists of items they would like to buy.
In Scheme, a typical wish list might look like this:
(define wish-list-a (list lxf-subscription 1984 car))
Lazy evaluation
One very cool functional programming technique
that we havent had time to cover in detail is the
idea of lazy evaluation. The idea is that, if you
assign the result of an expression to a variable,
the value of that variable doesnt matter until
you make use of it. As such, evaluation of the
expression can be delayed until later in the
application or, depending on the expression,
it can be partially evaluated, doing only those
calculations that are strictly necessary at the
current point of the program.
80
More languages
to write new procedures for each and every symbol you want
to check for. This is bad because repetition leads to mistakes;
its also bad because it makes programming, normally a fun
and challenging hobby, into a boring one! To fix this, you can
create a more abstract, or more general, version of the
procedure. For example, consider what the differences would
have been between car-in-wish-list and laptop-in-wish-list:
the only thing to change between the two would be the
symbol car in the second line, which would become laptop.
Recognising this, you can easily create a more general
version by adding a new parameter to the procedure: the
symbol to search for in the list.
(define (in-wish-list symbol wish-list) ...
A higher-order procedure
This was an easy example, but in many situations featuring
repetition, the same techniques work: look at examples of the
similar procedures, note what changes between them, and
then abstract these out of the body of the procedure and in to
the parameters. In fact, exactly the same technique can be
applied when creating higher-order procedures.
Consider the following procedure, which extracts all the
even numbers from a list and returns them as a new list:
(define (ev? list)
(cond ((null? list) ())
((= (remainder (car list) 2) 0)
(cons (car list) (ev? (cdr list))))
(else (ev? (cdr list)))))
Again, in itself this is quite a clever procedure, but it could
be made more general or abstract. What would happen if you
wanted to create a new procedure that would extract all the
odd numbers from a list, or all the numbers that are divisible
by 3? Youd once again find yourself writing three almost
identical procedures: first checking for the empty list, then
checking to see if the current item in the list matches your
criteria, whether even, odd or divisible by 3, and then moving
on to the next item if it doesnt match.
As before, you can create a more abstract version of this
procedure by looking for the differences in similar functions.
Unlike in our last example, this time the difference actually
comes in the predicate used to check whether the current
item meets our requirements, which seems altogether more
difficult to abstract than a simple data variable.
The thing is, its really no more difficult, since in Scheme
predicates are just procedures, and procedures are first class
objects and can be passed in to other procedures just like any
other. This means that we can abstract the above procedure
in exactly the same way as before, by abstracting the
differences in to parameters.
(define (filter list pred)
(cond ((null? list) ())
((pred (car list))
(cons (car list) (filter (cdr list) pred)))
(else (filter (cdr list) pred))))
As you can see, weve given the procedure a new name,
filter, as it more accurately represents what this new, more
abstract version does. Weve also created a new parameter
called pred, a place holder for the predicate thats used to do
the work of the function - to determine whether or not a given
element meets our criteria. With this in hand, we can easily
find all even numbers, or all odd numbers, or all any category
of number, simply by writing a new predicate to do the work,
without nearly so much duplication:
(define (ev? list)
(define (z x)
Functional programming
Many of the examples weve looked at
over the last few months have involved
mathematical problems. These kind
of problems suit pure functional
programming well, since most
mathematical functions dont involve
side-effects. Whats more, mathematical
examples have allowed us to focus on
patterns and techniques, as opposed
to libraries for interacting with more
complex data types, such as files,
images or web pages, since numbers
are built straight in to most
programming languages.
Because of this focus, you might have
come to the conclusion that while
functional programming is an
interesting novelty, its not much use in
the real world. You would be wrong!
The recursive programming model is
well suited to working with files and
directories, while list or stream
processing can easily be applied to text
file processing or network programming.
For a good introduction to the
car-in-wish-list
cdr wish-list
wish-list is null?
YES
(car wish-list)
equals car?
NO
Return true
Return false
This flow
diagram shows
how the recursive
procedure carin-wish-list goes
about checking
to see whether
a list contains a
given item.
81
More languages
(= (remainder x 2) 0))
(filter list z))
(define (threes list)
(define (z x)
(= (remainder x 3) 0)))
(filter list z))
Filter is one example of a higher-order procedure. Its also
one of three procedures designed to operate on lists, which
when combined together can be made to do an amazing
number of things for very little effort on your part. Lets look
at these other procedures now.
Higher-order procedures
The first of these other procedures were going to look at is
map, another useful higher-order procedure for operating on
lists, which lets us modify all the elements in a list.
(define (map list proc)
(cond ((null? list) ())
(else (cons (proc (car list))
(map (cdr list) proc)))))
With this procedure, you could pass a list of numbers in
and return a new list with the
square of all the original lists
elements contained within,
find the absolute value of all
those items (that is, removing
the sign from all the numbers),
or anything else that you
can imagine.
Weve shown you this example map procedure so you can
get an idea of how it works, but Scheme actually has a built-in
version of it that is more flexible and more powerful, capable
of applying the given procedure to the elements of an
arbitrary number of lists its well worth investigating.
The second of these is a bit more complicated its called
accumulate. It compresses an entire list to a single element.
To see why this is useful, consider the case of summing all the
values in the list. You could do this with an independent
procedure, sum:
(define (sum list)
(cond ((null? list) 0)
(else (+ (car list)
(sum (cdr list))))))
But then the same problem weve seen throughout this
article rears its head again: what happens if you want to find
the product of all the elements in the list, for instance? Once
again, youd have to write a new procedure, product.
While its a touch more tricky, with more variables to keep
track of, the same technique of looking for differences
between similar functions, such as sum and product, makes
creating a higher-order procedure simple.
On this occasion, the differences lie in the base case, and
the procedure thats applied to the current element of the list
and the rest of it. In sum, for instance, the base case would be
0, as above, but in product it would be 1 (think about it, if you
kept the base case as 0, youd get 0 as your answer every
time anything multiplied by 0 is 0!).
Thinking it through, youll find yourself with a procedure
like the one below:
(define (accumulate proc base list)
(cond ((null? list) base)
(else (proc (car list)
(accumulate (cdr list) proc base)))))
After looking at all these procedures, you might be feeling
a bit lost they all look quite similar, and none of them seem
incredibly powerful or useful
alone, even if the idea of
writing one procedure to do
the work of many seems like
a clever idea.
Lets take a look at an
example to see how these
higher order procedures can let us express complex ideas in
clear and simple ways.
Our example task will be to find the sum of all the squares
of numbers which are multiples of three between any two
numbers. If we were to write a procedure to accomplish this
without the help of any higher-order procedures, it might look
something like this, without all the helper procedures
definitions:
(define (sum-cubes a b)
(cond ((> a b) 0)
((= (remainder a 3) 0)
(+ (cube a)
(sum-cubes (+ a 1) b)))
(else (sum-cubes (+ a 1) b))))
After everything else weve seen over the last three
instalments, with a bit of careful thought you can probably get
82
More languages
car
cdr
car
cdr
car
cdr
list
map cube
car
cdr
car
cdr
car
The map
procedure lets
you transform
the elements
of one list in to
another, making
use of a helper
procedure to
do the work of
transformation
to the value
of the current
element. The
final cdr has a
strike through it,
representing (),
the empty list.
cdr
list
1
your head around this, but its not the most readable piece of
code youll ever see. One of the main reasons for this, despite
the use of clear procedure names such as cube, is that the
overall logic is muddled and obscured by details not relevant
to this specific task.
For instance, the first, fourth and fifth lines of this
procedure are all involved in walking the procedure through
the integers between a and b. Wouldnt it be simpler if this
part of the process could be kept to one small area of code,
rather than spread through the rest of it? Whats more, if you
came to this code without any guidance, you might find
yourself tripped up by the remainder statement or by the two
recursive calls its not exactly clear what the purpose of any
of these lines is.
You may have noticed another significant problem with
this version of the procedure: its not very re-usable, so if you
ever wanted to complete a similar task, such as finding the
sum of the cubes of multiples of four, youd have to write an
entirely new procedure, all the while watching for typos and
other mistakes. And if there were any mistakes, because all
the parts of the code are mixed together, it would be very
difficult to debug.
Fortunately, it doesnt have to be this way: with the help
of the higher-order procedures discussed in the rest of this
article, you can come up with a much better solution.
(define (sum-cubes-better a b)
(accumulate +
0
(map cube
(filter multiple-three
(enumerate a b)))))
The only thing that weve not seen so far in this version is
the enumerate procedure at the very end. This simply
27
.
.
.
:
_
,
t
a
PHP
PHP
P
85
PHP
PHP: Write
your first script
Using this open source technology and your Linux platform, Mike Mackay
explores how to dive into the popular world of PHP programming.
Getting started
The PHP website might not be the most eye-catching in the world, but itll be a
site you return to time and time again.
86
Now we get to the fun part working with, and writing, our
first PHP script. Historically, wed write a basic Hello World
PHP
script, but that can be, well, a little boring. Instead well write
some dynamic text output using PHPs date() function.
Before we get into the real nitty gritty of the language, we
must first understand how the interpreter reads in our PHP
code and generates the necessary output.
Easy embedding
One of the advantages of PHP is that you can embed your
code directly into your static HTML pages of which the
entire page is sent directly to the interpreter. Its extremely
important to note that all of your PHP files must end in the
.php extension. Embedding your PHP code in HTML or HTM
files means they wont be run through the interpreter and
wont get executed instead, youll just see the plain text
code in your pages.
For your PHP code to be extracted from the rest of your
content, it must be enclosed within delimiters. PHP will
execute any code found within these delimiters anything
else is simply ignored by the interpreter. The default, and
most common, delimiters that we use are <?php to indicate
the start of our code and ?> to signal the end.
There are a few other options available for delimiters
such as short tags, but some of these can have implications
with XML and XHTML languages. For the purpose of this
tutorial, were going to stick with the recommended default.
With this in mind, open up your favourite text editor and write
the following:
<?php echo Welcome to the world of PHP; ?>
Save this file as welcome.php in your web servers root
folder that is, the folder that your web server reads when
you request the site in your browser. Once the file has been
saved, open your web browser and point it to the file on your
local web server, for me this is http://127.0.0.1/welcome.
php this URL may differ based on your Linux setup/
configuration. When run, you should simply see Welcome to
the world of PHP displayed in your browser.
If, instead, you see the raw PHP code, this means that
you havent set up your web server to interpret your PHP files
correctly. Go back, or find the relevant installation guide, and
make sure youve followed all the steps outlined. If you
successfully see the text without any PHP code, then were
ready to move on.
Flexible scripts
In our first example, we simply instructed PHP to output a
specific string of text by using the echo function. The value
of our string can come from many places a database,
the output of a function, a file on the server or even from
user interaction on our site. By hard-coding this value were
pretty much stuck on what the string value can be. Instead
well now assign the value to a variable, so open up a new file
in your text editor, enter the code below and save it as
welcome-var.php:
<?php
$display_text = Welcome to world of PHP;
echo $display_text;
?>
When you run this script you shouldnt see any difference
in output from the first file, but first youll notice a new line
starting with $display_text. This is a variable. Variables in
PHP can hold a single piece of data at any one time. This
data can change and can be of any type at any point.
Quick
tip
Use a text editor
that has syntax
highlighting for
PHP itll help you
quickly identify
your code and
specific parts, or
functions, inside
it. There are free
programs available
too so have a look
around and go with
the one you prefer
the look of.
Believe it or not we used to write all our PHP code in Notepad, then we realised
how much nicer life is in colour. Text editors are heaven to coding eyes.
87
PHP
To concatenate means
to combine two or more
things together.
Quick
tip
Where possible,
make use of
indenting as it will
make things flow
better and help you
read the code on
the page. Some text
editors auto-indent
for you but most
developers use
either a single tab
or two-four spaces.
88
Essential resources
There are plenty of books
dedicated to learning PHP
and its often hard to tell
which one(s) to buy to steer
you in the right direction.
While I cant help you chose
the book that suits you the
most, I can point you in the
general direction of great
companion websites:
http://php.net The
ultimate resource for
anything PHP related.
http://php.net/manual/
en/intro-whatcando.php
A taste of things that can be
done with PHP.
http://phpsec.org/ A great
resource for any security
related with PHP.
PHP
PHP 6 whats it all about?
So what can we expect in version 6?
One of the core updates will be better
support for Unicode strings, allowing
for a much broader set of available
characters to cover greater international
support. For the more advanced
developers, its bringing in better
support for Namespaces. With the
massive take up of Web 2.0 functionality,
version 6 is also giving default support
<html>
...
<body>
<div id=welcome-text>
<?php
$display_text = Welcome to the world of PHP. It is ;
echo $display_text . date(l, jS F) . , and the time is .
date(H:iA);
?>
</div>
...
</body>
</html>
In this case, Ive pasted
some trimmed and
rudimentary HTML code
and you can clearly see
where Ive embedded the PHP code to output my dynamic
text on the page. The PHP code block can sit anywhere on the
page and any amount of times inside a page dont be
concerned if you have five, 10 or sometimes more code
blocks within your HTML.
Something thats really handy is that internally, PHP will
communicate between each code block on your page. For
example, if you set the value of a variable in the first code
block at the top of your page, it will be available to the last
code block at the bottom of your page. This can work
wonders when youre altering the display of the content
based on the value of a variable elsewhere in the page a
really common use of this is a Login/Logout system where a
user is presented with the Login or Logout options based on
their logged in state.
Multiple updates
Dont forget when doing this, that you must save your files
with the .php extension otherwise your PHP code will fail to be
executed and youll be left with plain text code on your page.
If you find that youre adding the same block of code to
multiple pages and you need to change it, going through each
page and updating your code can be a treacherous job.
Thankfully, PHP has got you covered. To help with this
process, we can use the include() function. This allows us to
write our PHP to a file (just as we have in our welcome-var.
php file). Instead of embedding the full code in our HTML
each time, we can instead do:
<?php include(welcome-var.php); ?>
When the page is run, PHP will pick up the include()
request and will read in and execute the code on that page,
simply embedding the output it works as if the code was
directly on the page.
Facebook loves
PHP so much, it
even wrote its
own Facebook
Optimised
version called
HipHop.
89
PHP
Build an
online calendar
Continuing from his introductory tutorial, Mike Mackay explores arrays and
functions to build a basic events calendar for our website.
Introducing arrays
The best way to think of an array is a special variable that
holds other variables. An array allows you to hold as many
Associative arrays
Despite the many books that have been written on the language, the PHP
website is still most up-to-date and comprehensive reference manual available.
90
PHP
call the array and key you want:
echo $data[0];
This echos out the word Red to the screen. To echo out
the word Orange, you would simply change the key from 0 to
1. On our person array its just as simple to echo the name
to the screen, all I need to do is:
echo $person[name];
would we display each item when we dont know how long the
array is? Thankfully for us, theres a simple control function
called foreach() that allows us to do this.
So you might be asking why we wouldnt know the length
of an array? Well, we know what driver data is contained in
each item (name, nationality and championships), but a
database query (or similar function) might return one driver,
or it might return five drivers. We could get the the total
number of items in the array by using a PHP function, but
using foreach() is simpler and lets us write shorter code. The
foreach() control gives us an easy way to iterate over an
array. Using drivers.php that weve just created, copy the
code just below the PHP block that contains the drivers array:
<?php
foreach($drivers as $driver) {
echo Name: . $driver[name]. <br />;
echo Nationality: . $driver[nationality]. <br />;
echo World Championships: . $driver[championships].
<br /><br />;
}
?>
When you view the file in your browser, you should see a
basic list of drivers on your screen:
Name: Jenson Button
Nationality: British
World Championships: 1
Name: Lewis Hamilton
Nationality: British
World Championships: 1
Quick
tip
Use a text editor
that has syntax
highlighting for
PHP itll help you
quickly identify
your code and
specific parts, or
functions, inside
it. There are free
programs available
too, so have a look
around and go with
the one you prefer
the look of.
Using a code editor that has built-in syntax checker (such as Eclipse, the
winner of our IDEs Roundup in LXF152) can save a lot of time and frustration!
91
PHP
The 2012 F1
calendar well be
recreating with
our code.
Quick
tip
Where possible,
make use of
indenting, as it will
make things flow
better and will help
you read the code
on the page. Some
text editors autoindent for you, but
most developers
use either a single
tab or 24 spaces.
.
92
function shout($text) {
}
If we want the newly-modified data back, we can use
return to send it back:
function shout($text) {
return $text;
}
To call a function, all you need to do is write the function
name followed by parenthesis either with or without any
parameters (based on the functions requirements):
shout();
We have our basic function, but all it does is send back
exactly what we sent to it pretty pointless Im sure youll
agree. Lets make our function do something a bit more
interesting. Create a new PHP file, copy the following code in
to it and save it as function.php:
<?php
echo shout(Hello World);
function shout($text) {
return strtoupper($text);
}
?>
If you run that in your browser, you should see HELLO
WORLD being displayed. Whats happening is were sending
a string straight to the function, where we echo the returned
value out.
The built-in PHP function strtoupper() has a simple
purpose take the string input and convert it to uppercase.
We could modify the function to perform the echo inside
instead of using return, but our original method gives us
greater flexibility for multi-purpose use (we may not always
want to echo a value out immediately).
We could write any kind of code inside our function, and
were not limited to doing string transformations.
PHP
From the previous tutorial
In case you missed it, here are some of
the basics from what we covered in the
last tutorial:
In the standard distribution of PHP,
there are more than 1,000 functions
available to use. These range from
simple things, such as date and time
functions, through to more advanced
concepts, such as LDAP and MySQL
database functionality.
Write the following just below the end of the array and
before the functions closing curly brace:
$date = date(m/d/Y);
if(array_key_exists($date, $race_dates)) {
echo Todays race is the . $race_dates[$date][title] . in
. $race_dates[$date][location] . .;
}
else {
echo There is no race today.;
}
We use the value of $date inside the array_key_exists()
function this function accepts
two parameters: the first is the
key youre looking for (in our
case its the date) and the
second is the array you wish to
check against (our $race_dates
array).
The array_key_exists() function returns a boolean of
TRUE if the key exists, or FALSE if it doesnt.
If the race is found, were going to echo out the race
details. We can retrieve this data because we know the key
exists, therefore we use the $date variable as a shortcut to
retrieve the information. Essentially, its the same as writing:
echo $race_dates[13/5/2012]
title];
All thats left for us to do is to call the script. We do this in
exactly the same way as our earlier function script and put
the function name (with parenthesis) at the top of our script:
is_race_day();
We can then run this script in our browsers by going to
calendar.php, or we can use includes() to embed it on an
existing website. If a race exists on the day the script is run,
well be presented with the details. To test this, simply
hardcode a date in the $date variable:
$date = 13/5/2012;
PHP
Extend
your calendar
Following his second tutorial, Mike Mackay explains how to add
the functionality to dynamically select races and view details.
that we can work safely when dealing with user input. Start
by creating a blank PHP file (mines called races.php), and
at the top make an empty PHP code block with an empty
variable, $race_data, in it (well come to using this variable a
little later on):
<?php
$race_data = FALSE;
?>
Before we begin building our full script, we need a few
things in place. First, create an array of race dates inside the
code block after the $race_data line youve just created
(refer back to the previous article if youve forgotten all about
arrays). You dont have to use F1 races youre free to use
any kind of dates you want. For F1 race dates, please refer to
www.formula1.com/races/calendar.html.
My array looks like this (for display purposes, Ive
truncated most of my array data):
$races = array(
Australia => array(title => Australian Grand Prix,
location => Melbourne, date => 18/3/2012),
Malaysia => array(title => Malaysia Grand Prix, location
=> Kuala Lumpur, date => 25/3/2012),
China => array(title => Chinese Grand Prix, location =>
Shanghai, date => 15/4/2012),
Bahrain => array(title => Bahrain Grand Prix, location
=> Sakhir, date => 22/4/2012),
Spain => array(title => Spanish Grand Prix, location =>
Catalunya, date => 13/5/2012),
...
You may notice that the array differs from last time Im now
using a location instead of a date as the array key. Were going
to allow the user to specify the location of the race, so that
they can view more details for this we need to change the
array key.
94
The next thing we need is to add the HTML code and form
that lets the user choose the race. Were going to assume
that you have some HTML knowledge, as covering HTML is
outside the scope for this article. Create a basic page
structure (head, body etc) directly below (and outside) the
PHP code block.
The form is basic and consists of one drop-down menu
and a Submit button. To populate the locations in to the dropdown menu dynamically, were going to use PHPs foreach()
function. We covered this function previously, but to
PHP
summarise it allows us to simply loop, or iterate, through
an array and access each array items data.
In your HTML body area, add the following form:
<form method=post>
<fieldset>
<label for=location>Choose a race:</label>
<select name=location>
<?php foreach($races as $location => $race): ?>
<option value=<?php echo $location; ?>><?php echo
$location; ?></option>
<?php endforeach; ?>
</select>
<input type=submit name=submit value=View />
</fieldset>
</form>
View the script in your browser, and you should be
presented with all races in the drop-down. As you can see,
a minimal amount of code gives us a dynamically generated
drop-down of the items inside our array. Ive called my dropdown location, but you can choose whatever name you want.
After the drop-down, Ive added a Submit button, so that
once the user has chosen a location, they can then submit
the form.
You may notice that Ive set the form method to post. You
can use get if you wish, but to keep the URL tidy, I like to use
post for my forms. If you want know the difference between
POST and GET, refer to the information box titled GET vs
POST in short on p96.
return;
}
Before we do anything, were declaring our function,
check_for_race(). This function houses the crux of our
validation and assignment code.
The first line (starting with global) is new to us. Because
we defined the races array outside of the function, we have
to tell PHP that we want our function to be able to access it.
By writing global, preceded by any existing array and/or
variables name(s), were giving our function scope to access
the data. Scope can be difficult to understand if youre new
to it, check out the information box at the top of p97 for a
more detailed explanation.
The next line is where PHPs internal validation and
filtering routine comes in. PHP (as of 5.2+) comes with
built-in functions for checking specific input. You can verify
that input is in a certain format, within a range, or conforms
to a specific pattern. The filter_input() function allows you to
validate integers, floats, email addresses, URLs or supply your
own regular expression pattern to test against. You can also
perform a few sanitising functions on data, too this is the
part were most interested in for this tutorial.
Youll notice that weve supplied four arguments to
the filter_input() function. The first one tells PHP that we
want to deal with data thats coming from the POST input.
Following on, we then specify the field name that we want
to sanitise, location. Dont forget, if you changed the field
name earlier to something other than location, youll need
to amend this line accordingly.
Quick
tip
Always comment
your code
throughout.
While at the time
everything makes
sense to you, will
it do so if you have
to return to your
code a few months
later? Commenting
will help you make
sense of those
complicated
functions that
youve written.
The official PHP website is the best place online to learn about the other
filtering and validation options available natively to PHP.
95
PHP
Quick
tip
Use a text editor
that has syntax
highlighting for
PHP itll help you
quickly identify
your code and
specific parts, or
functions, inside
it. There are free
programs available
too, so have a look
around and go with
the one you prefer
the look of.
Because our array keys are defined by the place names, its
easy to check if the chosen race exists.
The key will exist in our array if we have the data we
verify this by using the shortcut to an array item with the
square brackets and using the isset() function to return
a TRUE or FALSE value (boolean). If the function returns
TRUE, we have array data, and so we assign the value of that
array item to the $race_data variable. If the function returns
FALSE, it means the value the user chose doesnt exist
so we supply a string message back.
You might be wondering how a user could choose a value
thats not in the array when were using the array itself to
populate the drop-down menu in the HTML? Well, nine times
out of ten this wont be an issue, but this is in place as a
preventative measure.
Sometimes, URLs get
broken, or people tamper with
the HTML to see what they
can do to sites by only
showing the array data if its
there or an error message
if it isnt, were preventing
our script from breaking
and showing a PHP error message; or disclosing private
information about our script or server. You may not think
this is important right now, but if youre using a database
or have potentially private data in the script or on the server,
the less you can reveal about your hardware, or scripts
content, the better.
For example, in one case, I saw a website break on a
database connection, and the error message displayed
to me contained the database username and password.
Anyone with a mind to do so could use this information
to get further into the database (or server) to get their
hands on information they wouldnt otherwise be able to
access.You should always be proactive in your security
measures, and not have the bad luck of needing to be
reactive instead.
After weve assigned a value to the $races_data variable,
be it the race data array or an error message, we finish
off the function with return. This isnt completely necessary,
but its always good to return from a function when youre
done using it.
The SecurePHP
Wiki (www.
securephpwiki.
com) is a good
site to visit for
general security
practices and
tips related
to PHP.
96
GET and POST both form part of the RFC guidelines on the
HTTP Protocol that defines the method for transferring
data between the client (your internet browser, for
example) and a web server. When you type a URL in to your
browser, it sends a GET request (in plain text) to the server
for the content you want to view. GET requests can be fairly
large and can provide copyable deep links into websites,
or search pages. Data (typically in key/value pairs) is sent
as a part of the URL, and interpreted by the server.
POST requests are similar to GET in that they, too, can be
used to retrieve content. However, any data thats sent
along with the request (again, in key/value pairs) is done
so transparently to the user and the URL it cant easily
be manipulated, viewed, or copied via the URL.
POST is perfect for sending data to a site without
obfuscating the URL.
PHP
Show the user their data
Now that we have data, no matter what its contents are, we
need a way of showing it to the user. We do this by embedding
some additional PHP code in to our HTML page, similar to
how we embedded our drop-down menu code. Below the
form, in the body area, add in the following:
<?php if(is_array($race_data)): ?>
<h3><?php echo $race_data[title]; ?> - <?php echo $race_
data[date]; ?></h3>
<h4><?php echo $race_data[location]; ?></h4>
<?php elseif(is_string($race_data)): ?>
<h3><?php echo $race_data; ?></h3>
<?php endif; ?>
This may look a little confusing at first, but if you break it
down line by line you should be able to work out whats
happening. Essentially, were checking our $race_data
variable to see what sort of content it holds if it contains an
array, we know we have race data, if it contains just a string
we have an error message.
PHP provides us with some great, simple tools to check
the data type of a variable. The first line uses the function
is_array(), with $races_data supplied to it as an argument.
This function will return a TRUE or FALSE value back.
By checking the response, we know whether to show all the
data fields that we know are incased inside it, or to show a
standard string line.
In our previous tutorial, we
used only if()else(); this is the
first time weve come across
elseif(). The elseif() control
simply allows us to execute a
certain block of code if
additional criteria is met this
criteria will differ from the one contained in the initial if()
block. In readable terms, were saying: if its an array, show
this data, otherwise if its just a string, show this data instead.
Were not using else() here, because the $races_data holds
an initial value of FALSE, set at the start of our script. If we
used else(), then wed see an error message even if the form
hadnt been submitted not really ideal.
Theres a plethora
of ways to get data
in to your website.
Wrapping it all up
With the output code in place, you should now have
everything together to run an interactive F1 calendar. When
the user first visits the page, theyre presented with a drop-
PHP
Get started
with MySQL
Mike Mackay puts the M in LAMP, and shows how easily we
can use databases by writing our own live visitor counter.
Getting started
98
PHP
<?php
?>
In our planning steps, I mentioned that wed count only
visitors inside a five-minute window. You might prefer a larger,
or even smaller, timeframe, so what well do is allow you to
customise this amount. Were going to use the current date
and time value in EPOCH in order to allow us some pretty
fine-grained control.
What exactly is EPOCH, I hear you cry? EPOCH, or Unix
Time, or even POSIX Time, is simply the number of seconds
that have elapsed since midnight (UTC) of 1 January, 1970.
For example, the EPOCH value for midnight of 1 January, 2012
is 1,325,419,200. So thats 1 billion, 325 million, 419 thousand
and 200 seconds since midnight of 1 January, 1970. Capiche?
Time limit
Quick
tip
Always comment
your code
throughout.
While, at the time,
everything makes
sense to you will
it do so if you have
to return to your
code a few months
later? Commenting
will help you make
sense of those
complicated
functions that
youve written.
We have many different data and structure options in MySQL. Take a browse
through the options available and see what else is there.
99
PHP
functions. Its similar to its predecessor MYSQL, but offers
both procedural and object-orientated methods of working.
For the purpose of simplicity, well work exclusively with the
procedural method in this tutorial.
Well be using the mysqli_connect() function initially, and
then well take a look at mysqli_query() and the trick to
echoing out the live visitor count easily, the mysqli_num_
rows() function.
Quick
tip
Use a text editor
that has syntax
highlighting for
PHP itll help you
quickly identify
your code and
specific parts, or
functions, inside
it. There are free
programs available,
too, so have a look
around and go with
the one you prefer
the look of.
MySQL.
com is a great
resource for
the latest news
in the world of
MySQL. Its also
home to official
documentation.
100
PHP
table, put simply, is to identify uniquely a single row, or piece
of data, within a table.
The next part of the query specifies which table we want
our data added to (visitors) and then what our data is. The
SQL approach is very similar to the key => value relationship
that we use in arrays. Here, were saying simply (column1,
column2) VALUES (value1, value2), but when it comes to
the actual data insert, MyQSL translates that to column1 =>
value1, column2 => value2.
Our query inserts only one item of data, the users IP
address, but to make sure we do this securely, were going to
enclose that piece of data inside a special function.
In the previous tutorial, we touched on some important
security basics, such as validating and sanitising user input
data. The same rules apply to working with databases, too.
Not only should we protect the data that goes in, but we
also need to make sure that the user isnt inserting malicious
code that can mess around with our database, such as
deleting every single record a practice more commonly
known as SQL Injection.
To counteract this, well use MySQLs built-in sanitising
function mysqli_real_escape_string(). As with our other
functions, the first parameter is a database handle. This
function takes the second input parameter and escapes any
special characters inside it, making it safe for use in SQL
queries. This can be a real life-saver, so Id recommend using
it all the time, even for user input that you believe is safe.
While we can guarantee with 99.9% certainty that
$_SERVER[REMOTE_ADDR] contains the users IP
address, safeguarding our input in this way prevents anyone
from messing around on the server and inserting
inappropriate or malicious content.
The $_SERVER global array contains information such as
headers, paths and script locations that are created by the
web server itself. Check out the PHP website if you want
further information.
PHP
Do more
with MySQL
Mike Mackay explains how to add to your sites visitor counter database
to show not only the all-important numbers but also visitor data.
Installing MySQL
Just as before, we have now added MySQL to our arsenal.
If you followed along with the last tutorial, you can skip this
installation step. If youre new to MySQL then youll need to
download and install the MySQL Server software so that PHP
can make use of it.
You can download and install MySQL for Linux from
dev.mysql.com/downloads/mysq you should follow the
links for the appropriate version of Linux you are using. If in
doubt, the MySQL online documentation has installation
guidelines, and can be found at dev.mysql.com/doc.
Were going to make the assumption here that you
already have your Linux platform configured and serving
PHP pages through your web browser, as outlined in the
first tutorial. If not, please refer to the previous tutorials in
this chapter, or the section titled Installing PHP on your
Linux platform at the top of p103.
Youll also now need to make sure you have compiled PHP
with the --with-mysql[=DIR] configuration option. See the
official PHP documentation at http://php.net/manual/en/
mysql.installation.php
For managing your database(s), I would recommend
downloading and installing phpMyAdmin. phpMyAdmin
provides a web-based interface to a MySQL server and allows
102
Diving back in
So, our task now is to be able to display the visitor data along
with our counter. Lets get started with our changes and
review what we need to update.
Open up whosonline.php in your favourite text editor and
spend a few minutes familiarising yourself with the script in
your mind. Check out the variables we set at the top of the
script, such as the database connection details and our
visitor timeout window. If you need to alter these for your
local setup then feel free to do so. The $dbHost value should
point to your MySQL server; in most cases this will be
127.0.0.1 or localhost. $dbUsername and $dbPassword will
again be different for your setup, and you should replace
these values with the correct ones for your server (see the
info box titled MySQL users in a nutshell). $dbDatabase
should remain the same unless you called your database
something other than whosonline.
We still need to continue using the mysqli_connect()
function as well as the mysqli_query() function, as these
form the basics of our SQL query and are required. Were
calling the mysqli_connect() function using our database
connection credentials, and assigning a database connection
handle to the variable $mysqli:
$mysqli = mysqli_connect($dbHost, $dbUsername,
$dbPassword, $dbDatabase);
if(mysqli_connect_errno($mysqli)) echo Failed to connect to
MySQL: . mysqli_connect_error();
Beneath our initial connection function, we want to keep
the exact same query to insert the visitors IP address into
our table so a record of their visit is tracked:
mysqli_query($mysqli, REPLACE INTO visitors (ip_
address) VALUES ( . mysqli_real_escape_string($mysqli, $
PHP
Installing PHP on your Linux platform
Most of the latest distributions of Linux come
with PHP. Although you can run PHP scripts
from the command line, well be using the web
browser (and therefore the web server) for
this tutorial.
You can follow this tutorial by uploading your
PHP files to a web server on the internet (if you
have one). For me, though, Im using a default
SERVER[REMOTE_ADDR]) . ));
Keep in the next part that retrieves all the applicable visitor
data. However, we will need to make a small change here:
$visitors = mysqli_query($mysqli, SELECT ip_address
FROM visitors WHERE UNIX_TIMESTAMP(NOW())UNIX_TIMESTAMP(visited) <= . mysqli_real_escape_
string($mysqli, $time_limit) . );
Quick
tip
Always comment
your code
throughout.
While, at the time,
everything makes
sense to you will
it do so if you have
to return to your
code a few months
later? Commenting
will help you make
sense of those
complicated
functions that
youve written.
Using phpMyAdmin, we can quickly view our visitor data without having to
check it through the website.
103
PHP
TRUE and the code inside the curly braces is then executed.
The initial line within the if() braces assigns the integer value
(the number of rows returned) from the database query to
our $visitor_data[total] array item. The next line,
containing the while() statement, is the most complicated
part of our update. The while() loop contains the crux of our
local data storage/assignment and makes use of the
massively helpful PHP function, mysqli_fetch_array().
Using loops
The official
online PHP
reference
has details of
every single
MySQLi function
available an
absolute must!
104
PHP
something a little bit fancier, then you can format this data
with the date() function in PHP.
This function has a multitude of formatting options, all of
which can be found on the official reference at http://php.
net/manual/en/function.date.php
This display method assumes that you are showing the
data on the same PHP file, or page, as the main MySQL
functionality. If this is not the case, you can simply include()
the main whosonline.php (substitute accordingly if youve
changed the filename) file on any page you wish to show the
data on (note that this must be a file ending in .php for it to
be parsed correctly). Youd then just embed the HTML table
code where necessary. When you execute the code and view
the output on your page, youll have noticed that Im far from
a designer and my table is, well, extremely bland and boring.
Why not make good use of some CSS and style the table to
fit around your website theme or design? You are free to
customise it as much as you need or want to.
Quick
tip
Use a text editor
that has syntax
highlighting for
PHP itll help you
quickly identify
your code and
specific parts, or
functions, inside
it. There are free
programs available
too, so have a look
around and go with
the one you prefer
the look of.
Where to next?
Now that youve learnt the basics of retrieving data from
a MySQL query and looping through the records, why not
adapt this function to output some other types of data? You
could perhaps store the F1 2012 season calendar in a
MySQL database.
By using the DATE() function (in MySQL, not PHP)
in a WHERE clause, you could display only the races that
are left in the season. You might even decide to take things
a step further and introduce teams and driver data to the
mix, as well. You could achieve this by creating further
tables in a database and writing queries to retrieve specific
information from them.
By following the guide in the previous tutorial in this
chapter, which covered creating MySQL tables with
phpMyAdmin, you should find it relatively straightforward
to set up any required tables and fields. You can also use
phpMyAdmin to enter manually the data yourself through
the Insert tab found on any table properties page. The most
important aspect of learning not just PHP but any new
language is to be creative and to start challenging yourself
more and more.
Push and drive your PHP skills forward and be creative.
Make use of all the available online resources, from tutorial
sites to forums. There are literally thousands, if not millions,
of people out there who are willing to help others out when
they get stuck. Its always a good idea to get involved in the
community, so dive in and have fun. Q
105
a
1
c
a
e
m
o
a
e
++
r
Modern Perl
Modern Perl
W
107
Modern Perl
Modern Perl:
Track your reading
Modern Perl makes it simple to write a database program say, for example,
to keep tabs on your books without using SQL. Dave Cross explains how.
108
Modern Perl
you want to create. Then there is a Perl DBI connection string,
which includes information about the type of database we are
talking about (mysql) and the actual database that were
interested in (books). The last two arguments are the
username (books) and the password (README).
When you run that command youll find a new file in your
current directory called Book.pm and a new directory called
Book. Within the Book directory youll find another directory
called Result and within that there are two files called Author.
pm and Book.pm. If you look at the contents of these last two
files, youll see code that closely matches the definitions of
the two tables in your database.
Quick
tip
The language is
called Perl. The
program that
compiles Perl
programs is called
perl. Typing either
of these as PERL is
wrong.
109
Modern Perl
Quick
tip
Perl comes
with a lot of
documentation
which you can
read using the
perldoc program.
Alternatively, its all
online at http://
perldoc.perl.org.
110
Modern Perl
Taking it further
They are very similar, so Ill just show the start one here:
sub start {
my $schema = get_schema();
my $books_rs = $schema->resultset(Book);
my $isbn = shift || die No ISBN to start\n;
my ($book) = $books_rs->search({ isbn => $isbn });
unless ($book) {
die ISBN $isbn not found in db\n;
}
$book->started(DateTime->now);
$book->update;
say Started to read , $book->title;
}
A lot of this looks very standard by now. We check weve
been given an ISBN number and then we get a schema
object and a book resultset object. We use the search
method to get the book object from the database (and die if
it cant be found). Then we use the started method to update
that column and call the update method to save the changes
back to the database.
When we set up our database classes using
dbicdump we asked for an extra component called
InflateColumn::DateTime to be included. This is where we
see the advantage of that. It identifies any date and time
columns in the database and converts those values into
Perl DateTime objects in our program. So we can create
a Perl DateTime object using the classs now method
and DBIX::Class will automatically convert that into the
appropriate string to be stored in the database. The end
sub-command looks very similar to this, with only the
column name changed from started to ended.
Quick
tip
Theres a useful
program called
perltidy, which
will tidy up Perl
code. Its almost
certainly available
prepackaged for
your distribution.
Modern Perl
112
Modern Perl
file we need to look at. Its really up to you how much you
change this file. We removed most of the text, left a few of the
<div> elements and changed the headers. My file ended up
looking like this:
<div id=page>
<div id=sidebar>
</div>
<div id=content>
<div id=header>
<h1>BookWeb</h1>
<h2>Heres your reading list</h2>
</div>
</div>
</div>
template index, {
reading => \@reading,
read => \@read,
to_read => \@to_read,
};
Weve added another parameter to the template call. This
parameter is a hash, which contains details of the data we
need while processing the template. The keys of the hash are
names that we can use within the template (reading, read
and to_read) and the values are references to arrays of
books. Actually, they are references to arrays of books as you
cant store an array in a Perl hash, but you can store a
reference to an array. See the perldoc perlreftut manual page
for more explanation of this.
Quick
tip
The Perl motto is
Theres more than
one way to do it.
So dont be too
surprised if you find
Perl code that is
written differently
to my examples.
113
Modern Perl
While youre editing config.yml you should also change
the template engine. Dancer comes with support for two
templating engines. The default engine is a simple one that
isnt quite powerful enough for our needs, so we need to
change to use the Template Toolkit. Do that by changing the
template section in config.yml to look like this:
# template: simple
Quick
tip
If you want advice
on improving your
Perl code, try the
perlcritic program
that is probably
available
pre-packaged for
your distribution.
template: template_toolkit
engines:
template_toolkit:
encoding: utf8
start_tag: <%
end_tag: %>
Notice that weve changed the template toolkits start and
end tags from [% and %] to <% and %>. Thats
because our existing templates already contain these tags.
114
<div id=content>
<div id=header>
<h1>BookWeb</h1>
<h2>Heres your reading list</h2>
</div>
<h3>Reading</h3>
<% IF reading.size %>
<ul>
<% FOREACH book IN reading %>
<div class=book><p><img src=<% book.image_url %> />
<a href=http://amazon.co.uk/dp/<% book.isbn %>><%
book.title %></a>
<br />By <% book.author.name %></p>
<p><% IF book.started %>Began reading: <% book.started.
strftime(%d %b %Y) %>.<% END %>
<% IF book.ended %>Finished reading: <% book.ended.
strftime(%d %b %Y) %>.<% END %></p>
</div>
<% END %>
</ul>
<% ELSE %>
<p>No books found.</p>
<% END %>
</div>
</div>
The interesting bits are in the <% ... %> tags. Youll see an
if/else statement and a
foreach loop. These are
both standard
programming constructs
that act as youd expect.
The hash of variables
that we passed into the
template call had a key called reading, and that becomes the
name that we use to refer to that data inside the template. As
the variable is an array, we can call a size method to see how
many elements it contains. If the array is empty, the if
condition is false (Perl treats zero as a false value) so we
execute the else code which displays no books found. If there
are books in the list then we iterate over the list, putting each
book in turn into a temporary variable called book.
Each of these book values is a DBIx::Class book object
like the ones we used in the previous article. And that means
they all have methods for each of the columns in the
database table. We can use those to print the title and the
authors name. We also use the ISBN to construct a link back
to the books page on Amazon. Notice that the started
column returns a Perl DateTime object so that we can use
that classs strftime method to get a nicely formatted date.
In order to make the page look a little more attractive, we
can add the following tweaks to the stylesheet in
public/css/style.css:
img {
float: left;
margin: 0 5px 5px 0;
clear: both;
}
Heres what the index.tt file looks like once weve added
code to handle the reading array.
<div id=page>
<div id=sidebar>
</div>
Modern Perl
Routes in Dancer
Weve said that Dancer is built around
the concept of routes, but what exactly
is a route and how does it use them?
A route is a combination of three
things: an HTTP request method; a
request path; and some code. When
Dancer matches an incoming request
with the method and the path, it runs
the associated code. In our example,
this was the definition of the only route.
get / => sub { ... };
In this example, get is the HTTP
method, / is the path and the code is
.book, h3 {
clear: both;
}
All we need to do now is to write the code to retrieve and
display the other two lists the list of books weve read and
the list of books we havent started. But thats going to get a
little repetitive, so before we do that lets make a few changes
to the index.tt to make our life as easy as possible.
The Template Toolkit has the concept of macros. These
are a bit like functions in the templating world. They make it
easy to bundle up and re-use repetitive code. We can define
a showbook macro that defines how we display a book and
then call it whenever we want to show the details of a book.
Heres the macro:
<% MACRO showbook(book) BLOCK %>
<div class=book><p><img src=<% book.image_url %> />
<a href=http://amazon.co.uk/dp/<% book.isbn %>><%
book.title %></a>
<br />By <% book.author.name %></p>
<p><% IF book.started %>Began reading: <% book.started.
strftime(%d %b %Y) %>.<% END %>
<% IF book.ended %>Finished reading: <% book.ended.
strftime(%d %b %Y) %>.<% END %></p>
<% END %>
Arrays of light
Each section is exactly the same; they just work on different
arrays. All we need to do now is to fill in the values of the read
and to_read arrays that are passed to the template.
We wrote code that did this in the previous article and we
just need to replicate that in the route in BookWeb.pm. When
weve finished, the complete route definition will look
something like this:
get / => sub {
my $books_rs = schema->resultset(Book);
my @reading = $books_rs->search({
started => { !=, undef },
ended => undef,
});
Re-usable bundles
my @read = $books_rs->search({
ended => { !=, undef },
});
my @to_read = $books_rs->search({
started => undef,
});
template index, {
reading => \@reading,
read => \@read,
to_read => \@to_read,
};
};
Next time
Modern Perl
The books
application, with
links allowing you
to maintain your
reading list.
if ($book) {
$book->update({started => DateTime->now});
}
return redirect /;
};
Like all Dancer routes, this definition consists of an HTTP
action (in this case get), a path and some code to execute
when the first two items are matched. The path here is more
complex than the path that we saw last time, as it contains a
parameter. The URL that we want to use to start reading a
book looks like http://example.com/start/1930110006.
This will flag that you have started reading the book with
ISBN 1930110006.
Obviously, that ISBN value will change for different books,
so we need a way to capture that parameter and use it in our
code. In a Dancer route, you can match parameters with the
:name syntax that you see in our definition. You can have
more than one parameter defined in the route as long as they
are named and separated by slashes. You access these
parameters using Dancers param function.
The rest of the code will look familiar to anyone who read
the first article in this chapter (beginning on p108) where we
wrote the command-line version of this program. We get a
resultset for our book table, search it for a book with the given
ISBN and then update the started column in that object to be
equal to the current date and time.
You might also remember that the DBIx::Class tool that we
are using for database access automatically converts
between Perl DateTime objects and date/time columns in
your database.
Notice that if we dont find a book with the given ISBN,
then we do nothing. It might be worth displaying an error
message at that point. Or, perhaps, redirecting to the add
action (which we havent written yet).
Once we have updated the book record, we just use
Dancers redirect function to redirect the browser back to the
main page of the application. The user will then see that the
chosen book has moved from the To Read list to the
Reading list.
The code for the end route is almost identical. Only the
path and the database column will differ. The path will be
/end/:isbn, and well need to update the ended column in
the database.
116
Modern Perl
object in a couple of places, so well write a get_amazon()
subroutine that creates the object for us.
sub get_amazon {
return Net::Amazon->new(
token => $ENV{AMAZON_KEY},
secret_key => $ENV{AMAZON_SECRET},
associate_tag => $ENV{AMAZON_ASSTAG},
locale => uk,
) or die Cannot connect to Amazon\n;
}
Theres nothing complicated here. Its just calling the
constructor on the Net::Amazon class and returning the
object that is created. Annoyingly, Amazon has changed the
way that this works since I wrote the first article in this series.
See the Amazon API Changes boxout for more details.
We can now define our add route. The path will be a similar
format to the start and end routes. The code looks like this:
get /add/:isbn => sub {
my $author_rs = schema->resultset(Author);
my $amz = get_amazon();
# Search for the book at Amazon
my $resp = $amz->search(asin => param(isbn));
unless ($resp->is_success) {
die Error: , $resp->message;
}
my $book = $resp->properties;
my $title = $book->ProductName;
my $author_name = ($book->authors)[0];
my $imgurl = $book->ImageUrlMedium;
# Find or create the author
my $author = $author_rs->find_or_create({
name => $author_name,
});
# Add the book to the author
$author->add_to_books({
isbn => param(isbn),
title => $title,
image_url => $imgurl,
});
return redirect /;
};
In this function, we need to talk to both the database and
Amazon, so the first thing
we do is create an author
resultset and a
Net::Amazon object. We
then search Amazon for the
ISBN that weve been given.
If we find it, we first create
an author record (or find
the existing one if we already know about this author) and
then insert details of the book. Once again, when weve
finished, we just need to redirect to the front page and the
user will see their new book in the To Read list.
Quick
tip
The best book
about Perl is
Programming Perl.
The fourth edition
has just been
published.
Adding links
Thats all very well, but currently the only way to access our
new routes is by typing addresses, including the ISBNs, into
the location bar in your browser. Thats hardly user-friendly.
Lets fix that by adding links to the list of books. In the file
views/index.tt, we have a macro called showbook which is
responsible for displaying an individual book in the main list.
We can edit that and have the links appear for every book.
Once the links have been added, the macro looks like this:
<% MACRO showbook(book) BLOCK %>
Amazon exploration
The best place for a search box is in a sidebar that appears on
every page. Our sidebar is defined in views/layouts/main.tt.
Edit the sidebar div so it looks like this:
<div id=sidebar>
<p><form method=POST action=/search><p>Search
Amazon:
<input name=search values=<% search %> /> <input
type=submit value=Search /></form></p>
</div>
That will put a search box on every page in our application.
But now we need to write code to carry out the search and
display the results. Notice in the form definition weve said
that the form sends a POST request to /search. That gives us
a couple of clues as to how our route definition should look:
117
Modern Perl
Quick
tip
There are a huge
number of blogs
dedicated to Perl
programming.
Many of the best
ones are collected
at http://mgnm.
at/ironman.
118
Adding security
Presumably, youd like to display your reading list to anyone
whos interested, but youd prefer it if only you can update it.
For that we need to introduce some security. Were going use
some really basic authentication, but I hope it will be obvious
how to extend it for use in the real world.
Were going to add the concept of a logged in user.
And were going to store whether the current user is logged in
or logged out using a session cookie. Support for sessions
comes as a part of the standard Dancer distribution, but in
order to store your session in a cookie, you will need to install
the extra Dancer::Session::Cookie module from CPAN.
Having installed the module, you need to configure it by
adding the following two lines to your config.yml file:
session: cookie
session_cookie_key: somerandomnonsense
The value of the cookie key can be any random string
the more random the better. Mine isnt a great example.
In order to add session support, we need to add use
Dancer::Session to the list of modules near the top of
BookWeb.pm. Now we need to think about how our security
will work. Im going to define a list of paths that are public.
Anyone can see those pages, but anyone trying to access
pages outside of this list will be prompted to log in if they
havent already.
Dancer has the concept of a before hook which is fired
before any route is run. Thats a perfect place to check
whether the user is allowed to do whatever they are trying
to do:
my %public_path = map { $_ => 1 } (/, /login, /search);
hook before => sub {
(
,
n
;
n
;
Python
Python
P
121
Python
Python: Different
types of data
Functions tell programs how to work, but its data that they operate
on. Nick Veitch goes through the basics of data in Python.
What is data?
While were
looking only at
basic data types,
in real programs
getting the
wrong type can
cause problems,
in which case
youll see
a TypeError.
122
Python
element of a list or the last character in a string, you can use
the same notation with a -1 as the index. -2 will reference the
second-to-last character, -3 the third, and so on. Note that
when working backwards, the indexes dont start at 0.
Methods
Lists and strings also have a range of other special
operations, each unique to that particular type. These are
known as methods. Theyre similar to functions such as
type() in that they perform a procedure. What makes them
different is that theyre associated with a particular piece of
data, and hence have a different syntax for execution.
For example, among the list types methods are append
and insert.
>>> list.append(chicken)
>>> list
[banana, cake, tiffin, chicken]
>>> list.insert(1, pasta)
>>> list
[banana, pasta, cake, tiffin, chicken]
As you can see, a method is invoked by placing a period
between the piece of data that youre applying the method to
and the name of the method. Then you pass any arguments
between round brackets, just as you would with a normal
function. It works the same with strings and any other data
object, too:
>>> word = HELLO
>>> word.lower()
hello
There are lots of different methods that can be applied to
lists and strings, and to tuples and dictionaries (which were
about to look at). To see the order of the arguments and the
full range of methods available, youll need to consult the
Python documentation.
Variables
In the previous examples, we used the idea of variables to
make it easier to work with our data. Variables are a way to
name different values different pieces of data. They make it
easy to manage all the bits of data youre working with, and
greatly reduce the complexity of development (when you use
sensible names).
As we saw above, in Python you create a new variable with
an assignment statement. First comes the name of the
variable, then a single equals sign, followed by the piece of
data that you want to assign to that variable.
From that point on, whenever you use the name assigned
to the variable, you are referring to the data that you assigned
to it. In the examples, we saw this in action when we
referenced the second character in a string or the third
element in a list by appending index notation to the variable
name. You can also see this in action if you apply the type()
function to a variable name:
>>> type(word)
<type str>
>>> type(list)
<type list>
The Python
interpreter is a
great place to
experiment with
Python code and
see how different
data types work
together.
Looping sequences
One common operation that you may want to perform on any
of the sequence types is looping over their contents to apply
an operation to every element contained within. Consider this
small Python program:
list = [banana, tiffin, burrito]
for item in list:
print item
First, we created the list as we would normally, then we
used the for in construct to perform the print function
on each item in the list. The second word in that construct
doesnt have to be item, thats just a variable name that
gets assigned temporarily to each element contained within
the sequence specified at the end. We could just as well
have written for letter in word and it would have worked
just as well.
Thats all we have time to cover in this article, but with the
basic data types covered, well be ready to look at how you
can put this knowledge to use when modelling real-world
problems in later articles.
In the meantime, read the Python documentation to
become familiar with some of the other methods that it
provides for the data types weve looked at before. Youll find
lots of useful tools, such as sort and reverse! Q
123
Python
Python: Code a
system monitor
Tidying up some code with Clutter, Nick Veitch takes you far from the
command line into a new realm of technicolour graphical possibilities.
124
Python
...
if event.keyval == clutter.keysyms.q:
...
clutter.main_quit()
...
elif event.keyval == clutter.keysyms.r:
...
self.set_color(red)
...
>>> stage.connect(key-press-event, parseKeyPress)
>>> clutter.main()
When run in the interactive Python shell, the quit function will
not quit Python itself, or even destroy the application; it will
just return control to the Python shell. In the case of a running
script though, calling the clutter.main_quit() method will
effectively end the application, or at least the Clutter part of it.
The traditional
first app, though
rather daringly
we have left out
the comma.
125
Python
Quick
tip
Keeping track of
versions can be
a nightmare, but
most modules
store their
version number in
<modulename>.__
version__ . Not only
is this useful for
you to check, but
your applications
can check for a
compatible version
before they try and
do anything tricky.
126
line=line.strip()
if (line[:4] == eth0):
line=line[5:].split()
bin=int(line[0])
bout=int(line[8])
return (bin, bout)
while True :
z= getspeed()
timedelta=time.time()-lasttime
lasttime=time.time()
sin=(float(z[0]-lastin))/(1024*timedelta)
sout=(float(z[1]-lastout))/(1024*timedelta)
print sin, sout
lastin=z[0]
lastout=z[1]
time.sleep(5)
This incorporates a timing function to more accurately
calculate the speeds, but bear in mind that were only talking
about a couple of milliseconds, so it doesnt make a lot of
difference. It is useful however, if we ever want to alter the
timing period elsewhere in the software.
Now what we have to do is to incorporate this functionality
into our Clutter application. We could just stick the loop at the
end of our program and fail to ever call the main Clutter loop.
We can still update the actor objects whenever we like, but
this would be a Bad Thing. The nicer way to do it is to give
liberty, autonomy and freedom back to the actors, but make
use of an animation timeline to control their text.
Timelines are covered in slightly more detail in the box
over the page, but to give you a brief summary, a timeline is
just a timer that counts to some value and then emits the
programmatic equivalent of a beep a signal. The signal can
be caught and fed to a callback, and as well as itself, you can
supply other parameters to the call. For our purposes, we can
make the timer call a function that will test the network speed
and update our two actors.
The timeline is an object unto itself, but when we execute
the connection between the timeline and the callback
function, we can pass along our text actor objects too, so the
callback function will be able to change them directly. Note
that if youre going for more complicated behaviours, this
doesnt preclude you from having other timers too you
could set one up to change the colour of the objects every
Python
outtext=clutter.Text()
outtext.set_font_name(Sans 30)
outtext.set_color(red)
stage.add(outtext)
stage.show_all()
t=clutter.Timeline()
t.set_duration(5000)
t.set_loop(True)
t.connect(completed, updatespeed, intext, outtext)
t.start()
clutter.main()
Here weve brought together all the elements we have
explored in this tutorial. We have created a stage, populated it
with actors, and then used the timeline objects in Clutter to
make them update themselves at our whim. But so far we
have only scratched the surface of Clutters graphical
capabilities. We havent even learned about behaviours or
animations yet, never mind the alpha channel effects. Please
trust us that we will be including these in our next project. Q
The main
clutter website
at www.clutterproject.org
doesnt have
much help for
Python users, but
there is lots of
background info
and plenty of C
documentation.
127
Python
Python: Clutter
animations
The code master Nick Veitch gets his head in a spin with the help
of a news feed and some clever clutter animations.
Getting fed
The very first thing we should look at is how to get the data
from our feed. We have come across the most excellent
Feedparser library for Python before, and we will be using it
128
Python
Cluttering up
With that little excursion into the realm of feeds now out
of the way, we can get down to the serious business of
getting all cluttery. Now, in the last tutorial we introduced the
basic Clutter elements of the stage, actors and timelines. If
you havent read it yet, its a good idea to go back now and
take a look because well be building on the things we went
through there.
Anyhow, for now, here is the Previously on Lost short
recap. A Clutter window is called a stage. This is where the
action happens, and by default in PyClutter is implemented in
a standard GTK window. The elements that appear on (in) the
stage are called actors, and can be anything from text to
simple shapes or pixmap textures.
The last one is what interests us this time. We will be using
text actors again, but Clutter allows us to import images
directly from files to use. Those of you with memories longer
than a Channel 4 ad break will no doubt remember that we
have such a file lurking around. Just to prove that it works, we
should set up a stage and all the usual Clutter, erm, clutter,
and then import our new pixmap.
>>> import clutter
>>> black=clutter.color(0,0,0,255)
>>> white=clutter.color(255,255,255,255)
>>> stage= clutter.Stage()
129
Python
>>> stage.set_size(400,60)
>>> stage.set_color(white)
>>> stage.show_all()
>>> ident =clutter.texture_new_from_file(img)
>>> stage.add(ident)
This code just sets up a simple stage, sets the background
colour to white and then adds the pixmap image. You can see
from the code that unlike many graphic toolkits, we can add
the actor to the stage after the stage is already on display. You
should see something like, if not exactly the same as, the
image on the first page of this tutorial.
Foresight is a wonderful thing, which is why the pixmap fits
so nicely on the stage. As we didnt set a position for it, it just
comes in at (0,0), which is the top-left of the stage.
Getting animated
Quick
tip
Want to find a
complete list
of the Clutter
built-in animation
codes? Try the C
documentation,
which is more up
to date: http://
clutter-project.
org/docs/
clutter/stable/
clutter-ImplicitAnimations.
html#Clutter
AnimationMode.
Doc Holiday
Clutter is really great. The only thing
that sucks at the moment is the
documentation for the Python module.
Although the classes and methods are
largely identical to the C
implementation of Clutter (naturally),
there are a few subtle differences, and
sometimes the semantics of doing
130
Python
In this code we have created a new ident image and two text
objects one to represent the title of the news item (head1),
and one for the summary (body1). We have set the text for
them from the first entry we found in the feed and set them
up in a good position next to the ident image. Here we have
seemed to set absolute positions for the head1 and the
body1 text objects, but these positions will actually remain
relative to the group. When we initiate the group, it becomes
the parent object to the actors, rather than the stage as
before. So the positions of the objects are relative to the
group position (which starts off by default at 0,0).
You should try to remember this. For example:
>>> head1.get_position()
(130,5)
>>> group1.set_position(10,10)
>>> head1.get_position()
(130,5)
>>> group1.set_position(0,0)
You saw all the objects move along the screen when we
changed the group position, but dont fall into the trap of
thinking that the child objects think they have moved. They
havent they are still in the same place, but their world has
moved The great thing about this is, of course, that we can
also animate a group we need only supply one
transformation, because all the properties of the individual
actors are relative to the group.
>>> group.animate(clutter.LINEAR,2000,x,200,y,30)
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at
0xa4bb2c8)>
>>> group.animate(clutter.LINEAR,2000,x,0,y,0)
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at
0xa4bb4a8)>
This time all the elements moved smoothly as though they
were one, which (in terms of the group) they are. We can still
adjust them individually change the text or move them, but
any transformations are relative to the group, not the stage.
So, now we have a group, we can add another, then
implement two animation modes to make the transition
between the different items from the news feed:
>>> group2= clutter.Group()
>>> ident2=clutter.texture_new_from_file(img)
>>> head2=clutter.Text()
>>> head2.set_position(130, 5)
>>> head2.set_color(blue)
>>> head2.set_text(f.entries[1].title)
>>> body2=clutter.Text()
>>> body2.set_max_length(75)
>>> body2.set_position(130, 22)
>>> body2.set_size(250, 100)
>>> body2.set_line_wrap(True)
>>> body2.set_text(f.entries[1].summary)
>>> group2.add(ident2, head2, body2)
>>> group2.hide()
>>> stage.add(group2)
>>> group1.animate(clutter.EASE_OUT_EXPO,4000,x,800,
y,0,rotation-angle-y,180)
<clutter.Animation object at 0x92ca61c (ClutterAnimation at
0x935a2a0)>
>>> group2.animate(clutter.EASE_OUT_
EXPO,1,x,400,y,100,rotation-angle-y,720)
<clutter.Animation object at 0x92ca66c (ClutterAnimation at
0x935a118)>
>>> group2.show()
>>> group2.animate(clutter.EASE_OUT_
EXPO,3000,x,0,y,0,rotation-angle-y,0)
<clutter.Animation object at 0x92ca16c (ClutterAnimation at
0x8f46028)>
Here we have set up a new group and hidden it before
adding to the stage. Then, for conveniences sake, we
animate it out of the way and reveal it. As it is out of the
stage area, it cant be seen and doesnt interfere with the
first group. When we reveal it, it is still offstage, but the new
animation then puts it back in the proper position, and it
seems to glide through the ether to come to a perfect stop.
Hurrah! We are now expert animators to rival the likes of
Disney. Maybe.
That reveal in
all its animated
glory. Well,
obviously you
cant see it here,
but you can
if you run the
listing or type
it in.
Moving on
Its looking good so far, but our feed reader could still do
with some extensions. There is very little in the way of error
checking for instance (what if the feed is empty or the web
connection is lost?) and it only handles a single feed rather
than merging together several feeds.
Next time well be looking at more advanced methods
of animation, and stringing animations together. We
will also extend the basic building blocks of clutter by
showing how to incorporate elements of the Cairo
graphics library. Q
131
Python
Python:
Stream video
132
Python
Awww. There
are some quite
interesting cams
if you search for
them. Or just use
your own files.
Getting freaky
All weve done is create a Clutter version of a stream viewer
like Kaffeine, but with fewer options. However, our video
texture is a Clutter actor, so we can make it do things.
>>> vid.set_size(100,90)
>>> vid.set_size(100,290)
>>> vid.set_size(320,50)
Even when we change the size, or move the actor around
>>> vid.move_by(10,10)
>>> vid.move_by(10,10)
You can do pretty much all the standard actor methods on
the video. Previously we took a detailed look at the animation
133
Python
methods, and these will work on a video texture too. As a brief
recap, the method takes a value for the animation effect (a
Clutter constant that enumerates various styles), the duration
in milliseconds and a list of property /value pairs. The actor is
then transformed to these absolute values. For example:
>>> vid.animate(clutter.LINEAR, 1000, width,640, height,
480, x,0,y,0)
<clutter.Animation object at 0x96f3884 (ClutterAnimation at
0xb4799a00)>
>>> vid.animate(clutter.LINEAR, 1000, width,320, height,
240, x,160,y,120)
<clutter.Animation object at 0x96f4284 (ClutterAnimation at
0xb4799c00)>
If you want more of an idea what can be done with the
animation transforms, check out the previous tutorial. We can
now have some picture-in-picture fun with our little camera
experiment, but theres one more new element we need to
Quick
tip
Confusingly, the
VideoTexture actor
has a get_uri()
method that
returns nothing.
Thats because it is
just a texture the
playbin object is
the one that has
the URI data in
it, and if you ever
forget what feed
its linked to, you
can use playbin.
get_property(uri).
134
Python
self.stage.add(self.video1)
self.pipeline1.set_state(gst.STATE_PLAYING)
# second one
self.video2 = cluttergst.VideoTexture()
self.playbin2 = self.video2.get_playbin()
self.playbin2.set_property(uri, self.channel2)
self.pipeline2 = gst.Pipeline(pipe2)
self.pipeline2.add(self.playbin2)
self.video2.set_position(0,0)
self.video2.animate(clutter.LINEAR,
1000, rotation_angle_y, 0, rotation_angle_z, 0, width, 80,
height, 60 )
self.stage.add(self.video2)
self.video2.set_depth(0)
self.pipeline2.set_state(gst.STATE_PLAYING)
#display the stage and run
self.stage.show_all()
clutter.main()
def parseKeyPress(self, stage, event):
print parsekey got , self, event
#do stuff when the user presses a key
if event.keyval == clutter.keysyms.q:
#if the user pressed q quit the app
clutter.main_quit()
elif event.keyval == clutter.keysyms.t:
#if the user pressed t, toggle video
#which is in front?
if self.video1.get_depth() == -2 :
#video2 is on top
self.video2.animate(clutter.LINEAR,
300, rotation_angle_y, 360, rotation_angle_z, 360, width,
640, height, 480 )
self.video1.animate(clutter.LINEAR,
1000, rotation_angle_y, 0, rotation_angle_z, 0, width, 80,
height, 60 )
self.video2.set_depth(-2)
self.video1.set_depth(0)
else :
# video 1 is on top
self.video1.animate(clutter.LINEAR
300, rotation_angle_y, 360, rotation_angle_z, 360, width,
640, height, 480 )
self.video2.animate(clutter.LINEAR,
1000, rotation_angle_y, 0, rotation_angle_z, 0, width, 80,
height, 60 )
self.video1.set_depth(-2)
self.video2.set_depth(0)
if __name__ == __main__:
videobrowser()
The init code sets up two video streams, placing one as an
actor above the larger one, and the other in the corner. After
a few seconds the feeds should spring to life and youll see
double-decker buses and whatnot. The cunning part of the
code is the callback signal that traps keypresses. While the
window on screen has focus, any keypress will cause an
event. The stage.connect() method connects this signal to
the keypress method we defined in the main application
class. If the toggle key has been pressed, the two video feeds
are animated into the opposite position. We have to
remember to set the depths again, so the new, small image
goes on top (the depth setting is how we determine which
The GStreamer
site can help you
if you want to do
clever things with
your pipes and
feeds.
Going further
Its possible to add further streams to this and cascade them
down one side. Instead of toggling between two positions, the
streams could all rotate around one spot. You might also want
to play more with the animations: the values given in the
animate method are absolute, rather than relative, which is
why the angular rotations are multiples of 360 degrees so
the video texture ends up the right way round.
In the last two parts of this series, we looked at the
clutter.Text() actors. It would be pretty simple to add a text
actor here at the front to let you know which stream youre
looking at, and you can change the value of the text when the
screens are being switched without worrying about animating
that too. For streams with audio, I guess you would want to
mute the stream that is minimised. Check the boxout (A Word
in your Shell-like, p134) for details about doing that.
For more tricks with GStreamer objects, you should also
check out the GStreamer documentation, which is a lot more
effusive than the PyClutter docs at the moment. Q
Flash! Ahh-ahh!
There was a time when any sort of live
video feed was just that a nice
streaming socket you could clamp on to
and suck the goodness out of. These
days it seems that everyone would
rather you use their silly, customised
and Flash-based embedded players
instead. Im not sure whether they do
this to get better metrics on users or to
135
Python
Python: Code
a Gimp plugin
Jonni Bidwell uses Python to add some extra features to the favourite open
source image-manipulation app, without even a word about Gimp masks.
Get started
On Linux, most packages will ensure that all the required
Python gubbins get installed alongside Gimp; your Windows
and Mac friends will have these included as standard since
version 2.8. You can check everything is ready by starting up
Gimp and checking for the Python-Fu entry in the Filters
menu. If its not there, youll need to check your installation. If
it is there, then go ahead and click on it. If all goes to plan this
should open up an expectant-looking console window, with a
prompt (>>>) hungry for your input. Everything that Gimp
can do is registered in something called the Procedure
You can customise lines and splodges to your hearts content, though frankly
doing this is unlikely to produce anything particularly useful.
136
Python
pdb.gimp_context_set_foreground('#00a000')
pdb.gimp_context_set_brush_size(128)
pdb.gimp_paintbrush_default(layer,2,[160,100])
If you have the brush called Cell 01 available, then the
above code will draw a green splodge in the middle of your
canvas. If you dont, then youll get an error message. You can
get a list of all the brushes available to you by calling pdb.
gimp_brushes_get_list(). The paintbrush tool is more
suited to these fancy brushes than the hard-edged pencil,
and if you look in the procedure browser at the function
gimp_paintbrush, you will see that you can configure
gradients and fades too. For simplicity, we have just used the
defaults/current settings here.
For the rest of this tutorial we will describe a slightly more
advanced plugin for creating bokeh effects in your own
pictures. Bokeh derives from a Japanese word meaning
blur or haze, and in photography refers to the out-of-focus
effects caused by light sources outside of the depth of field. It
often results in uniformly coloured, blurred, disc-shaped
artefacts in the highlights of the image, which are reminiscent
of lens flare (think Star Trek: Into Darkness). The effect you
get in each case is a characteristic of the lens and the
Applying our
bokeh plugin
has created
a pleasing
bokeh effect in
the highlights.
137
Python
Quick
tip
For many, many
more home-brewed
plugins, check out
the Gimp Plugin
Registry at http://
registry.gimp.org
plugin will not burden them with further layers. This means
that one can apply the function many times with different
parameters and still have all the flare-effect discs on the same
layer. It is recommended to turn the blur parameter to zero
after the first iteration, since otherwise the user would just be
blurring the already blurred layer.
After initialising a few de rigueur variables, we set about
making our two new layers. For our blur layer, we copy our
original image and add a transparency channel. The bokeh
layer is created much as in the previous example.
blur_layer = pdb.gimp_layer_copy(timg.layers[0],1)
pdb.gimp_image_insert_layer(timg, blur_layer, None, 0)
bokeh_layer = pdb.gimp_layer_new(timg, width, height,
RGBA_IMAGE, "bokeh", 100, NORMAL_MODE)
pdb.gimp_image_insert_layer(timg, bokeh_layer, None, 0)
Our scripts next task of note is to extract a list of points
from the users chosen path. This is slightly non-trivial since a
general path could be quite a complicated object, with curves
and changes of direction and allsorts. Details are in the box
below, but dont worry all you need to understand is that
the main for loop will proceed along the path in the order
drawn, extracting the coordinates of each component point
Bring a bucket
To draw our appropriately-coloured disc on the bokeh layer,
we start somewhat counter-intuitively by drawing a black
disc. Rather than use the paintbrush tool, which would rely on
all possible users having consistent brush sets, we will make
our circle by bucket filling a circular selection. The selection is
achieved like so:
pdb.gimp_image_select_ellipse(timg, CHANNEL_OP_
REPLACE, x - radius, y - radius, diameter, diameter)
There are a few constants that refer to various Gimp-specific
modes and other arcana. They are easily identified by their
shouty case. Here the second argument stands for the
138
Python
number 2, but also to the fact that the current selection
should be replaced by the specified elliptical one.
The dimensions are specified by giving the top left corner
of the box that encloses the ellipse and the said boxs width.
We feather this selection by two pixels, just to take the edge
off, and then set the foreground colour to black. Then we
bucket fill this new selection in Behind mode so as not to
interfere with any other discs on the layer:
pdb.gimp_selection_feather(timg, 2)
pdb.gimp_context_set_foreground('#000000')
pdb.gimp_edit_bucket_fill_full(bokeh_layer, 0,BEHIND_
MODE,100,0,False,True,0,0,0)
And now the reason for using black: we are going to draw
the discs in additive colour mode. This means that regions of
overlapping discs will get brighter, in a manner which vaguely
resembles what goes on in photography. The trouble is,
additive colour doesnt really do anything on transparency,
so we black it up first, and then all the black is undone by our
new additive disc.
pdb.gimp_context_set_foreground(color)
pdb.gimp_edit_bucket_fill_full(bokeh_layer, 0,ADDITION_
MODE,100,0,False,True,0,0,0)
Once weve drawn all our discs in this way, we do a
Gaussian blur if requested on our copied layer. We said
that part of the image should stay in focus; you may want to
work on this layer later so that it is less opaque at regions of
interest. We deselect everything before we do the fill, since
otherwise we would just blur our most-recently drawn disc.
if blur > 0:
pdb.plug_in_gauss_iir2(timg, blur_layer, blur, blur)
Softly, softly
Finally we apply our hue and lightness adjustments, and set
the bokeh layer to Soft-Light mode, so that lower layers are
illuminated beneath the discs. And just in case any black
survived the bucket fill, we use the Color-To-Alpha plugin to
squash it out.
pdb.gimp_hue_saturation(bokeh_layer, 0, 0, lightness,
saturation)
pdb.gimp_layer_set_mode(bokeh_layer, SOFTLIGHT_
MODE)
pdb.plug_in_colortoalpha(timg, bokeh_layer, '#000000')
And that just about summarises the guts of our script. You
will see from the code on the disc that there is a little bit of
housekeeping to take care of, namely grouping the whole
series of operations into a single undoable one, and restoring
After we apply
the filter, things
get a bit blurry.
Changing the
opacity of the
layer will bring
back some detail.
params, results,
function) # myplugin
main()
The proc_name parameter specifies what
your plugin will be called in the PDB; python_fu
is actually automatically prepended so that all
Python plugins have their own branch in the
taxonomy. The menupath parameter specifies
what kind of plugin youre registering, and where
your plugin will appear in the Gimp menu: in our
case <Image>/Filters/Artistic/LineSplodge...
would suffice. imagetypes specifies what kind
of images the plugin works on, such as RGB*,
GRAY*, or simply if it doesnt operate on any
image, such as in our example. The list params
139
Python
Python: Gimp
snowflakes
Winter is here, so set your White Walker traps and snares, hunker down
and admire some fractal snowflakes with Jonni Bidwell.
there will be an entry called FractalFlake, so go ahead and
click it, if you haven't already, you impatient devil. You will be
greeted with a new dialogue don't worry about the Image
and Drawable options, these are irrelevant for plug-ins that
output new. In fact, don't worry about any of the options for
your first snowflake, just click go ahead and OK and watch the
script work away.
When youre done admiring your handiwork, have a fiddle
with the parameters: Size corresponds to the pixel size of the
square canvas produced by the plugin (of which the
snowflake occupies about 60%), Minimum Length is the line
length of each straight line in your fractal. This corresponds to
the base case for the recursion (see below), if you have a
larger image, and a smaller minimum length, then the image
will take longer to produce. The random wobble will randomly
deviate the vertices of the snowflake by up to this number of
pixels, making for a more organic look, or a bit of mess if you
set it too high.
Quick
tip
See more on
the Thue-Morse
connections on
a blog piece by
Zachary Abel
http://bit.ly/
ThueMorse
140
Python
t.forward(size)
else:
for angle in [60, -120, 60, 0]:
von_koch(t, order - 1, size / 3)
t.left(angle)
von_koch(turtle,5,400)
This enables us to see a connection with the
binary sequence obtained by starting with a 0
141
Python
px = x1 + dx / 3.
py = y1 + dy / 3.
mpx = x1 + dx / 2.
mpy = y1 + dy / 2.
h = length / 3 * math.sqrt(3)/2
qx = px + dx / 3.
qy = py + dy / 3.
rx = mpx + h * (y1 - y2) / length
ry = mpy + h * (x2 - x1) / length
Next, we consider if we are adding a random wobble. If so
we make a list of 10 random numbers in the required range,
if not we make a list of 10 zeros. We then do the recursive call
with the new points perturbed, if requested.
if rnd > 0:
r = [random.randrange(0,rnd) for j in range(10)]
else:
r = [0 for j in range(10)]
drawStep(x1 + 0,y1 + 0,px + r[2],py + r[3])
drawStep(px + r[2],py + r[3],rx + r[4],ry +r [5])
drawStep(rx + r[4],ry + r[5],qx + r[6],qy + r[7])
drawStep(qx + r[6],qy + r[7],x2 + 0,y2 + 0)
Notice that we don't let the random wobble affect the end
points (x1,y1) and (x2,y2). You are welcome to do this, using
x1 + r[0], y1 + r[1] and x[2] + r[8] and y[2] + r[9], but the
resulting curve will not be closed, which looks a bit odd.
The von Koch curve of order 0 is just a humble straight line. There
really isnt that much more one can possibly say about it aside from it
being rather peaceful and reminds us of Flatliners
142
If we divide this line into thirds and form an equilateral triangle with
the middle third we get the order 1 curve. This curve is made up of
four order 0 curves at one third scale.
Linux is free
Linux is fast
Linux is secure
Linux is reliable
Got a bang up to date wonder PC? Great! Itll run Linux. Got a
PC from 10 years ago with an old CPU and limited RAM?
Thatll run Linux too. Got a PC from 20 years ago? Yes, even that
will run Linux just fine. Whether you have 64MB of RAM or 4GB,
Linux is ready for you.
Linux is growing
10
Linux is everywhere
Python
Make
a Twitter client
Jonni Bidwell shows you how to do Twitter like a boss. A command-line
boss that accepts arguments and catches errors.
Twitter enables
developers access to its
comprehensive REST API.
Our search
for Windows XP
found a lot of
worried people
survival kit
indeed. Notice
how nicely
the unicode
characters
are printed.
Adding argparse
By importing the argparse module and creating an
ArgumentParser object and calling parse_args() your
program will get a -h or --help option for free. You can
see this in action by creating a file argtest.py with the
following contents:
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
Then run python argtest.py -h to see your free usage
message. As it stands this is not particularly useful, but once
we start adding arguments this will change. Arguments can
be positional (mandatory) or optional and we can add a
mandatory argument to argtest.py by inserting the following
just above the last line:
parser.add_argument("grr_arg", help="Repeat what you just
told me")
144
Python
Taking a REST
REST is short for Representational State
Transfer and refers to a set of principles for
gathering and sharing data rather than any
concrete protocol. Twitter implements two
major APIs, a RESTful one, which we will use,
and a streaming one, which we wont.
The streaming API provides low-latency
access to real-time data, which you can do all
sorts of fancy stuff with, but the RESTful API
provides a simple query and response
mechanism which suits our purposes just fine.
Using arguments
More complicated arguments can easily be dealt with; for
example we could sum an arbitrarily long list of integers by
modifying the add_argument call like this:
parser.add_argument('integers', metavar='N', type=int,
nargs='+', help='some integers')
By default, arguments are assumed to be strings, so we
use the type= option to stipulate that integers are provided.
The metavar directive refers to how our argument is referred
to in the usage message, and nargs=+ refers to the fact
that many integers may be provided. We could make a
regular-ordinary program for summing two integers with
nargs=2, but where would be the fun in that? We have to put
the arguments provided into the list args.integers, so we can
process it like so:
print "The answer is {}.".format(sum(args.integers))
Our Twitter project works exclusively with optional
arguments. These creatures are preceded with dashes, often
having a long form, eg --verbosity, and a short form, say -v.
Our Twitter program has 5 options in total (not counting the
complementary --help option): --search, --trending-topics,
--user-tweets, --trending-tweets, and --woeid.
As it stands --woeid only affects the --trending-topics and
--trending-tweets options. While the argparse module could
easily handle grouping these arguments so that an error is
issued if you try and use --woeid with another option, its
much easier to not bother and silently ignore the users
superfluous input: Havent we all seen enough errors?
For example, the search argument which takes an
additional string argument (the thing youre searching for) is
described as follows:
parser.add_argument("-s", "--search",
type=str,
dest="search_term",
nargs=1,
help="Display tweets containing a particular string.")
Our usage
instructions for
all the optional
arguments you
can use.
145
Python
OpenHatch community
OpenHatch.org is a Boston-based
not-for-profit with the admirable and
noble goal of lowering the barriers into
open source development.
Its website provides a system for
matching volunteer contributors to
various community and education
projects and it runs numerous free
workshops imparting the skills
required to become a bona fide open
source contributor. Since 2011 it has
with it, which results in an error. You can test for this in the
Python interpreter as follows, where woeid is the WOEID of
your desired location:
import twitter_functions
test = twitter_functions.api.GetTrendsWoeid(woeid)
If you dont get an error ending with Sorry, this page does
not exist, then all is well. We use Pythons error catching to
fallback to the global trends function GetTrendsCurrent()
when this happens:
try:
trending_topics = api.GetTrendsWoeid(woeid)
except twitter.TwitterError:
trending_topics = api.GetTrendsCurrent()
Its prudent (but not necessarily essential, the catchall
clause except: is entirely valid) to specify the exception that
you want to catch if you arent specific, however, confusion
and hairpulling may arise.
The common base
exceptions include IOError,
for when file operations go
wrong, and ImportError
which is thrown up when you
try and import something
that isnt there:
try:
import sys, absent_module
except ImportError:
print "the module is not there"
sys.exit()
Modules will also provide their own exceptions, for example if
Unicode fixer
The function trendingTweets() is a little more complicated:
we need to first get a list of trending topics, and then for each
of these grab some tweets.
But theres a sting in the tail
sometimes the topics
returned will have funky
unicode characters in them,
and these need to be
sanitised before we can feed
them to our search function. Specifically, we need to use the
quote function of urllib2 to do proper escaping, otherwise it
will try and fail to ASCII-ize them.
trending_topics = api.GetTrendsCurrent()
for topic in trending_topics:
print "**",topic.name
esc_topic_name = twitter.urllib2.quote(topic.name.
encode('utf8'))
tweets = api.GetSearch(esc_topic_name)
for tweet in tweets[:5]:
print '@' + tweet.user.screen_name + ': ',
util.safe_print(tweet.GetText())
print '\n'
Weve been a bit naughty in assuming that there will be at
least five tweets, the syntax for limiting the number of tweets
GetSearch returns seems to be in a state of flux, but since
these are trending its reasonable that there will be plenty.
And that completes our first foray into pythonic twittering.
We have developed the beginnings of a command-line Twitter
client, we have parsed options, caught exceptions and
sanitised strings. If your appetite is sufficiently whetted then
why not go further? You could add a --friends option to just
display tweets from your friends, a --post option to post stuff,
a --follow option, and really anything else you want. Q
146
Python
Minecraft:
Start hacking
Use Python on your Pi to merrily meddle with Minecraft, says Jonni Bidwell.
148
Python
t = mc.getBlock(x, y 1, z)
mc.postToChat(str(x) + + str(y) + + str(z) + +
str(t))
Now fire up Minecraft, enter a world, then open up a
terminal and run your program:
$ python gps.py
The result should be that your co-ordinates and the
BlockType of what youre stood on are displayed as you move
about. Once youve memorized all the BlockTypes (joke),
Ctrl+C the Python program to quit.
We have covered some of the passive options of the API,
but these are only any fun when used in conjunction with the
more constructive (or destructive) options. Before we sign off,
well cover a couple of these. As before start Minecraft and a
Python session, import the Minecraft and block modules, and
set up the mc object:
posVec = mc.player.getTilePos()
x = posVec.x
y = posVec.y
z = posVec.z
for j in range(5):
for k in range(x - 5, x + 5)
mc.setBlock(k, j, z + 1, 246)
Behold! A 10x5 wall of glowing obsidian has been erected
adjacent to your current location. We can also destroy blocks
by turning them into air. So we can make a tiny tunnel in our
obsidian wall like so:
mc.setBlock(x, y, z + 1, 0)
Assuming of course that you didnt move since inputting the
previous code.
In the rest of this chapter, well see how to build and
destroy some serious structures, dabble with physics, rewrite
some of the laws thereof, and go a bit crazy within the
confines of our 256x256x256 world. Until then, try playing
with the mc.player.setPos() function. Teleporting is fun! Q
All manner
of improbable
structures can
be yours.
Quick
tip
Check out Martin
OHanlons
website www.
stuffaboutcode.
com, which
includes some
great examples of
just what the API is
capable of.
149
Python
Minecraft: Image
wall importing
Have you ever wanted to reduce your pictures to 16 colour blocks? You
havent? Tough Jonni Bidwell is going to tell you how regardless.
echnology has spoiled us with 32-bit colour, multimegapixel imagery. Remember all those blocky
sprites from days of yore, when one had to invoke
something called ones imagination in order to visualise what
those giant pixels represented? In this tutorial we hark back
to those halcyon days from the comfort of Minecraft-world, as
we show you how to import and display graphics using blocks
of coloured wool. Also Python. And the Raspberry Pi.
Standard setup
If youve used Minecraft: Pi Edition before
youll be familiar with the drill, but if not this is
how to install Minecraft and copy the API for
use in your code.
Were going to assume youre using Raspbian,
and that everything is up to date. You can
download Minecraft from http://pi.minecraft.
net, then open a terminal and unzip the file as
follows (assuming you downloaded it to your
home directory):
$ tar -xvzf ~/minecraft-pi-0.1.1.tar.gz
150
Python
With just
16 colours,
Steve can
draw anything
he wants
(inaccurately).
pixel to one block our image must be at most 256 pixels in its
largest dimension. However, you might not want your image
taking up all that space, and blocks cannot be stacked more
than 64 high, so the provided code resizes your image to 64
pixels in the largest dimension, maintaining the original
aspect ratio. You can modify the maxsize variable to change
this behaviour, but the resultant image will be missing its top
if it is too tall.
The PIL module handles the quantization and resizing
with one-line simplicity, but we must first define the palette
and compute the new image size. The palette is given as a list
of RGB values, which we then pad out with zeroes so that it is
of the required 8-bit order. For convenience, we will list our
colours in order of the blockData parameter.
mcPalette = [
221,221,221, # White
219,125,62, # Orange
179,80,188, # Magenta
107,138,201, # Light Blue
177,166,39, # Yellow
65,174,56, # Lime Green
208,132,153, # Pink
64,64,64, # Dark Grey
154,161,161, # Light Grey
46,110,137, # Cyan
126,61,181, # Purple
46,56,141, # Blue
79,50,31, # Brown
53,70,27, # Green
150,52,48, # Red
25,22,22, # Black
]
mcPalette.extend((0,0,0) * 256 - len(mcPalette) / 3)
Unfortunately the / 3 is missing from the code on the
disc, though it is a mistake without any real consequence
151
Python
Unlike in Doom,
this strawberry/
cacodemon
doesnt spit
fireballs at you.
This is good.
152
pixel = mcImage.getpixel((j,k))
if pixel < 16:
mc.setBlock(j + x + 5, rheight - k + y, z, 35, pixel)
To do all the magic, start Minecraft and move Steve to
a position that befits your intended image. Then open a
terminal and run:
$ cd ~/mcimg
$ python mcimg.py
So that covers the code on the disc, but you can have a
lot of fun by expanding on this idea. A good start is probably
to put the contents of mcimg.py into a function. You might
want to give this function some arguments too. Something
like the following could be useful as it enables you to specify
the image file and the desired co-ordinates:
def drawImage(imgfile, x=None, y=None, z=None):
if x == None:
playerPos = mc.player.getPos()
x = playerPos.x
y = playerPos.y
z = playerPos.z
If no co-ordinates are specified, then the players position
is used. If you have a slight tendency towards destruction,
then you can use live TNT for the red pixels in your image.
Just replace the mc.setBlock line inside the drawing loop
with the following block:
if pixel == 14:
mc.setBlock(j + x + 5, rheight - k + y, z, 46, 1)
else:
mc.setBlock(j + x + 5, rheight - k + y, z,
mcPaletteBlocks[pixel])
If you dont like the resulting image, then its good news,
everyone it is highly unstable and a few careful clicks on the
TNT blocks will either make some holes in it or reduce it to
dust. It depends how red your original image was.
While Minecraft proper has a whole bunch of colourful
blocks, including five different types of wooden planks and
Python
Thats right,
Steve, go for
the ankles.
Lets see how
fast he runs
without those!
Block ID
Red
Green
Blue
Gold
41
241
234
81
Lapis Lazuli
22
36
61
126
Sandstone
24
209
201
152
Ice
79
118
165
244
Diamond
57
116
217
212
116,217,212
]
mcPaletteLength = len(mcPalette / 3)
then we can structure our lookup table as follows:
mcLookup = []
for j in range(16):
mcLookup.append((35,j))
mcLookup += [(41,0),(22,0),(24,0),(79,0),(57,0)]
Thus the list mcLookup comprises the blockType and
blockData for each colour in our palette. And we now have
a phenomenal 31.25% more colours [gamut out of here - Ed]
with which to play. To use this in the drawing loop, use the
following code inside the for loops:
pixel = mcImage.getpixel((j,k))
if pixel < mcPaletteLength:
bType = mcLookup[pixel][0]
bData = mcLookup[pixel][1]
mc.setBlock(j + x + 5, rheight - k + y, z, bType, bData)
In this manner you could add any blocks you like to your
palette, but be careful with the lava and water ones: their
pleasing orange and blue hues belie an inconvenient
tendency to turn into lava/waterfalls. Incidentally, lava and
water will combine to create obsidian. Cold, hard obsidian. Q
More dimensions
One of the earliest documentations of displaying
custom images in Minecraft:Pi Edition is Dav
Stotts excellent tutorial on displaying Ordnance
Survey maps, http://bit.ly/1lP20E5. Twodimensional images are all very well, but Steve
has a whole other axis to play with. To this end
the aforementioned Ordnance Survey team has
provided, for the full version of Minecraft, a world
comprising most of Great Britain, with each
block representing 50m. Its Danish counterpart
has also done similar, though parts of MinecraftDenmark were sabotaged by miscreants.
Another fine example is Martin OHanlons
excellent 3d modelling project. This can import
.obj files (text files with vertex, face and texture
data) and display them in Minecraft: Pi Edition.
Read all about it at http://bit.ly/1sutoOS .
Of course, we also have a temporal
dimension, so you could expand this tutorial in
that direction, giving Steve some animated gifs
153
Python
Minecraft: Make
a trebuchet
Build your labour of love and then blow it sky high with pyrotechnic
Jonni Bidwell, a stash of TNT and an age-old siege machine.
154
Python
It doesnt look
like much, but
just you wait...
Quick
tip
The trebuchet
code was inspired
by the amazing
Martin OHanlon
and his Pi-based
projects on www.
stuffaboutcode.
com.
155
Python
Swiss cheese, baby! (Your house may need some repairs after this tutorial.)
Quick
tip
You can do all the
coding here in the
interpreter, but
copying errors are
frustrating. Thus
it might be easier
to put it all in a
file called house.
py which you
can execute with
python house.py
while Minecraft is
running.
156
Python
if dminor[j] >= 0:
minor[j] += s[j + 1]
dminor[j] -= aX
dminor[j] += aminor[j]
X += s[0]
return vertices
To conclude in style, we will test this function by making a
mysterious and precarious beam of wood next to where we
are standing as a fitting testament to your wonderous labours
this day, padawan.
v1 = mc.player.getTilePos() + minecraft.Vec3(1,1,0)
v1 = minecraft.Vec3(1,1,0) + pos
v2 = v1 + minecraft.Vec3(5,5,5)
bline = getLine([v1.x,v1.y,v1.z],[v2.x,v2.y,v2.z])
for j in bline:
mc.setBlock(j[0],j[1],j[2],5)
Over the page, well look at creating a fully functioning
Minecraft cannon. Boom! Q
Now we
can escape
the gridlock
and build at
whatever angles
our heart desires.
157
Python
Minecraft:
Build a cannon
Learn some object-oriented Python and, just as importantly, blow more stuff
up as Jonni Bidwell continues his adventures with Minecraft: Pi Edition.
distinct species. Thus you have likely already done some work
with objects, possibly without knowing it. A particular object
has some methods associated with it: for example we can
add, subtract, divide and multiply integers; compare and
concatenate strings; slice, curtail and append to lists, and so
on. These methods all come for free when we instantiate an
object. So when we do proper object oriented programming
we make a blueprint detailing our own custom methods for
our own bespoke objects. This blueprint is called a class, and
in Python the methods therein are defined as functions. If you
look at Martins code, the first class we come across (line 33)
is called MinecraftDrawing. Its fairly lengthy, comprising six
methods, dealing with all the drawing primitives one could
hope for the drawing of points, lines, spheres and faces.
I
Quick
tip
Try experimenting
with the velocity
and blast radius
arguments to the
cannon.fire()
method on line
376 of minecraftcannon.py
158
Python
a line. The reason for having two functions here is that we can
change the angles of azimuth and elevation for the cannon,
necessitating its redrawing. This is achieved from the
interpreter using the commands rotate and tilt respectively,
which in turn call the setDirection() and setAngle() methods
of the cannon class. The coordinates of the end of the cannon
are calculated by considering the point on an appropriately
sized sphere centred at the fuse end of the cannon. Details
are in the Spherical Trigonometry box overleaf paint over it
if trigonometry triggers youth-related trauma.
When the cannon is tilted or rotated the clearGun()
method is called, which draws over the gun with air blocks.
Then we calculate the new end-point of the cannon as
described in the box, and use mcDrawing.drawLine to draw
the appropriate line. The latter function calls getLine() (the
longest function in the module), which is an implementation
of the Bresenham line algorithm which we talked about last
issue. (If you missed it, dont worry, but if you worry get
yourself a back issue as instructed below.)
So now we come to the best bit: firing the cannon. This
instantiates a MinecraftBullet object with velocity 1 (it moves
Steve wishes
his balls were
just a bit more
destructive.
My first objects
As a gentle introduction to object oriented
programming, lets consider a simple music
library application. Here our objects will be the
library itself and our favourite tracks (whether
thats some rousing Brahms or the latest Israeli
psytrance anthems) and we will have a method
for adding tracks.
class library:
def __init__(self):
self.lib = []
def add(self,trackobj):
self.lib += [trackobj]
class track:
def __init__(self,artist,title):
self.artist = artist
self.title = title
Here our library uses just the standard list
methods, and we bring it into fruition and
populate as follows:
>>> mylib = library()
>>> mylib.add(track("Tom Lehrer", "Poisoning
pigeons in the park"))
>>> mylib.add(track("Bill Bailey", "Beautiful
ladies in danger"))
Its admittedly not much, but you get the idea.
159
Python
You have to
fire this one
manually, but
Steve dont
care, he crazy.
Spherical trigonometry
Given an azimuthal (horizontal) angle phi
and an angle of elevation (vertical) theta,
the point on a sphere centred on the
origin and having radius l is calculated by
trigonometry as shewn in the diagram.
Here the blue dot represents a point
on the sphere and the black dot its
projection onto the x-z plane. Because
of how the angles are defined, the y
co-ordinate ends up with a slightly
tidier expression than the others,
which we can see in the function
findPointOnSphere() (line 24):
def findPointOnSphere(cx, cy, cz, radius,
phi, theta):
x = cx + radius * math.cos(math.
radians(theta)) * math.cos(math.
radians(phi))
z = cz + radius * math.cos(math.
radians(theta)) * math.sin(math.
radians(phi))
y = cy + radius * math.sin(math.
radians(theta))
The trig functions require angles to be
converted to radians (we assume that
the rotate and tilt commands take their
angles in degrees). You may recall that
there are exactly pi radians in 180
degrees, and they enjoy the property of
being an entirely dimensionless measure.
160
effort redrawing it. We test that this is not the case with
if matchVec3(newDrawPos, self.drawPos) == False:
and then proceed with the redrawing. This involves a simple
check that the new position is empty space:
if self.mc.getBlock(newDrawPos.x, newDrawPos.y,
newDrawPos.z) == block.AIR:
If this is the case then we disappear the old obsidian block,
update the draw position and redraw:
self.clear()
self.drawPos = minecraft.Vec3(newDrawPos.x,
newDrawPos.y, newDrawPos.z)
self.draw()
If not then we make our crater and change movedBullet
to False so that we exit the update loop:
self.mcDrawing.drawSphere(newDrawPos, self.
blastRadius, block.AIR)
movedBullet = False
Better explosions
Remember in our previous tutorial how we blew all that stuff
up using chain reactions from TNT? Now were going to
upgrade our cannon fire using similar principles. Since the
only way to detonate TNT is by hitting it or by detonating
another block of TNT in its vicinity, we will have to manually
trigger the explosion, which means that the fire command
will work differently this time around. Specifically, it will now
add a block of TNT to the end of the barrel, leaving poor Steve
to light the blue touch paper and run. We need to monitor this
block and act swiftly when it is struck a matter of delicate
timing, to be sure. Just before it explodes, we need another
TNT block to appear just in front of it, and so on creating
the illusion of a moving/exploding/super nashwan fireball of
destruction. The code for this part of the exercise is called
tntcannon.py.
Once Steve hits the TNT and it starts flashing, the
getBlock() method actually detects it as air, which is a
convenient trigger for us to prepare to place the next block
in the chain. This waiting with bated breath is achieved using
the following loop, in which pass is the standard Python do
nothing command:
while self.mc.getBlock(xt, yt, zt) == 46:
pass
Python
Yeah, it dont
always work
right. If you can
fix it Ill let you
paint my fence.
def update(self):
self.yVelocity += self.gravity
oldPos = minecraft.Vec3(int(round(self.
Posx)),int(round(self.Posy)),int(round(self.Posz)))
self.Posx += self.xVelocity
self.Posy += self.yVelocity
self.Posz += self.zVelocity
newPos = minecraft.Vec3(int(round(self.
Posx)),int(round(self.Posy)),int(round(self.Posz)))
if newPos != oldPos:
if self.mc.getBlock(newPos.x,newPos.y,newPos.z) !=
block.AIR.id:
return True
if abs(newPos.x) > 128 or newPos.y > 128 or
abs(newPos.z) > 128 or newPos.y < -10:
# off the edge of the world or deep underground
return True
self.mc.setBlocks(newPos.x,newPos.y,newPos.z,new
Pos.x,oldPos.y,newPos.z,46,1)
height = newPos.y
return False
Since TNT is quite destructive it is possible that our bullet
could rip through quite a bit of scenery before finally coming
to rest, but we limit the damage by having the update()
method return True when the bullets altitude drops below
10. We also do this if it leaves the confines of Minecraft world
that is, if any of its coordinates gets larger in magnitude
than 128. Once the fireworks finally die down, we redraw our
cannon since it will have suffered some damage at launchtime, and then return the command prompt so that Steve can
have another pop at some innocent blocks.
Unfortunately, thanks to the unfathomable forces at
work when so many TNT blocks are exploding at once, there
is an element of randomness in all of this. As a result, blocks
can fly off in all directions or detonate at the wrong time
culminating in the unfortunate side-effect of stopping the
reaction and leaving half a parabolas worth of TNT in the
sky. The sleep time values on lines 276 and 280 were chosen
in a fairly ad hoc manner, so possibly by tweaking these you
might be able to improve the situation. On the other hand,
no weapon is perfect, and this unreliability is merely a
reflection of this ineluctable truth. Have fun experimenting
and happy coding! Q
161
TRY IT
FOR FREE
TODAY!