Wednesday 12 December 2012

Programming In Python 1: The Cookbook Project

Finally it's finished! Well as finished as it'll ever be. Almost a year a go I started learning Python, the programming language on and off. Which really isn't an ideal way to learn.

As with anything we want to learn in life I needed source materials, examples and exercises to complete. I found these in the Ubuntu orientated on-line magazine Full Circle. One of the early tutorials dives straight into writing a simple program that creates, loads and updates a database of cooking recipes.

The data in the database in this exercise isn't particularly important. It's the lessons learnt in writing the program that creates and manipulates the database. And fortunately Python is a very rich language with a lot of additional modules that can be called upon to enhance the power of software written in Python.

Personally I don't think what I've produced is particularly impressive. But it's where I'm at. For example I really should be making better use of Python's class objects. I guess I still have some habits from learning COMAL and Turbo Pascal bouncing around in my brain getting in the way. I guess I can only get better right?

With some basic concepts learnt, the next challenge is to create a program that will win me the lottery. On the off chance anybody is interested I've included my source code below.

#!/usr/bin/env python
#------------------------------------------------------------------------------
#
#    Program Title    :    Cookbook Database
#    Local Filename    :    sql-0004-cookbook_database.py
#    Author            :    Kevin Lynch
#    Created            :    04.06.2012
#
#------------------------------------------------------------------------------
#
#    Brief:    Write a program capable of storing recipes for later retrival.
#
#                * The program should be menu driven.
#                * Include a search function allowing users to search by;
#                    + Recipe title,
#                    + Author,
#                    + Ingredients.
#
#                * The program should also be capable of creating new cookbooks.
#                * Adding new recipe entries to each relevant book on demand.
#                * Removing unwanted entries from a book on demand.
#                * Program output and interaction prompts must be presentable.
#
#------------------------------------------------------------------------------
#
#    Additional Credits:
#
#        This project is based on the Python tutorials published by
#        Full Circle Magazine. So far as this author can tell the original
#        "cookbook" tutorial was written by Greg Walters of
#        "RainyDay Solutions, LLC" and "www.thedesignatedgeek.com".
#
#        Terminal dimensions code courtisy of Grant Edwards.
#        http://bytes.com/topic/python/answers/607757-getting-terminal-display-size
#
#------------------------------------------------------------------------------
#--|    Import Modules    |----------------------------------------------------

import os
import apsw # SQLite wrapper.
import string
#import webbrowser
import termios, fcntl, struct, sys

#------------------------------------------------------------------------------
#--|    Class/Object Definition Section    |-----------------------------------
class aScreen():
    def __init__(self,aTitle,aBorder,aJustify,aMessage,aContent,aOptionlist):
#        Get Dimentions
        s = struct.pack("HHHH", 0, 0, 0, 0)
        fd_stdout = sys.stdout.fileno()
        x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
#        print '(rows, cols, x pixels, y pixels) =',
#        print struct.unpack("HHHH", x)
#        return struct.unpack("HHHH", x)
        s = struct.unpack("HHHH", x)

        self.iDefault = 'Press "Q" to quit <::> '

        self.H = (s[0] - 6)
        self.W = s[1]
        self.J = aJustify # Tells the aScreen object which type of justification to use.
        self.T = aTitle
        self.B = (aBorder * self.W)
        self.P = 1
        self.M = aMessage # Should be a short single line instruction.
        self.C = aContent # Should be a list. Each list entry will correspond to a line in the terminal.
        self.O = aOptionlist # List of valid responses for this screen.

    def checkLine(line):
        pass

    def formatScreen(self):    # Formats the screen output. Works for showScreen() and should not be called directly.
        # Initialise screen segmentation.
        seg1 = [self.T,self.B,' ']
        seg2 = []
        seg3 = [' ',self.B]
        lines = []

        # Copy charcaters one at a time from self.C to creat a string of a maximum length of self.W - self.P
        if len(self.C) < self.H:
            j = len(self.C)
        else:
            j = self.H

        for i in range(0,j):
            line = self.C[i]
            if len(line) < self.W:
                seg2.append(' %s' % line)
            else:
                j = (self.W - self.P)
                while len(line) >= self.W:
                    if line[j] == ' ':
                        seg2.append(' %s' % line[0:j])   
                        line = line[(j + 1):len(line)]
                        i += 1
                    else:
                        j -= 1
                seg2.append(' %s' % line)
                i += 1

        # Add filler lines.
        if len(seg2) < self.H:
            for i in range(len(seg2),self.H):
                seg2.append(' ')

        # Add everything to one big list.
        for i in range(0,len(seg1)):
            lines.append(seg1[i])
        for i in range(0,len(seg2)):
            lines.append(seg2[i])
        for i in range(0,len(seg3)):
            lines.append(seg3[i])
        return lines

    def errorScreen(self):
        pass

    def showScreen(self): # Displays the current screen.
        loop = True
        lines = self.formatScreen()
        while loop == True:
            # Print the title, main body and borders of the screen.
            for i in range(0,len(lines)):
                print lines[i]
            # Prompt the user for input from the keyboard and verify the response.
            kbd = raw_input(self.M)
            for i in range(0,len(self.O)):
                if kbd == self.O[i]:
                    loop = False
                elif self.O[0] == 'pass':
                    loop = False
                else:
                    self.errorScreen() # When an invalid option is made the user is told.
        return kbd

class newRecord():
    def __init__(self): # Initialise the new recipe class.
        # Variables for Recipes table.
        self.name = ''
        self.servings = 0
        self.source = ''
        # Variables for Instructions table.
        self.instructions = ''
        # Variables for Ingredients table.
        self.ingredients = []
        # General variables needed for this record.
        self.recID = 0

class newDB():
    def __init__(self,dbname): # Initialise the Cookbook class.
        global connection
        global cursor
        self.totalcount = 0
        connection = apsw.Connection(dbname)
#        connection = apsw.Connection("cookbook1.db3")
        cursor = connection.cursor()

    def addRec(self,rec):
        sql = 'INSERT INTO Recipes (name,servings,source) VALUES ("%s",%s,"%s")' % (rec.name,str(rec.servings),rec.source)
        cursor.execute(sql)
        sql = "SELECT last_insert_rowid()"
        cursor.execute(sql)
        for x in cursor.execute(sql):
            rec.recID = x[0]
        sql = 'INSERT INTO Instructions (recipeID,instructions) VALUES (%s,"%s")' % (rec.recID,rec.instructions)
        cursor.execute(sql)
        for x in range(0,(len(rec.ingredients) - 1)):
            sql = 'INSERT INTO Ingredients (recipeID,ingredients) VALUES (%s,"%s")' % (rec.recID,rec.ingredients[x])
            cursor.execute(sql)

    def deleteRec(self,rid):
        sql = "DELETE FROM Recipes WHERE pkID = %s" % rid
        cursor.execute(sql)
        sql = "DELETE FROM Instructions WHERE recipeID = %s" % rid
        cursor.execute(sql)
        sql = "DELETE FROM Ingredients WHERE recipeID = %s" % rid
        cursor.execute(sql)
       
    def listAll(self): # Create a list of all recipes.
        res = ['%s %s %s %s' % ('Item'.rjust(6),'Name'.ljust(30),'Serves'.ljust(7),'Source'.ljust(30))]
        for x in cursor.execute('SELECT * FROM Recipes'):
            res.append('%s %s %s %s' % (str(x[0]).rjust(6),x[1].ljust(30),x[2].ljust(7),x[3].ljust(30)))
        return res

    def listOne(self,rec):
        sql = 'SELECT * FROM Recipes WHERE pkID = %d' % rec.recID
        for x in cursor.execute(sql):
            rec.recID = x[0]
            rec.name = x[1]
            rec.servings = x[2]
            rec.source = x[3]
        sql = 'SELECT * FROM Ingredients WHERE RecipeID = %s' % rec.recID
        for x in cursor.execute(sql):
            rec.ingredients.append(x[1])
        sql = 'SELECT * FROM Instructions WHERE RecipeID = %s' % rec.recID
        for x in cursor.execute(sql):
            rec.instructions = x[1]
        return rec

    def searchDB(self,sql,option):
        try:
            if option != '3':
                # Do search for options 1 and 2
                res = ['%s %s %s %s' % ('Item'.ljust(5),'Name'.ljust(30),'Serves'.ljust(6),'Source'.ljust(30))]
                for x in cursor.execute(sql):
                    res.append('%s %s %s %s' % (str(x[0]).rjust(5),x[1].ljust(30),x[3].ljust(20),x[2].ljust(30)))                   
            else:
                # Do search for option 3
                res = ['%s %s %s %s %s' % ('Item'.rjust(5),'Name'.ljust(30),'Serves'.ljust(6),'Source'.ljust(25),'Ingredient'.ljust(50))]
                for x in cursor.execute(sql):
                    res.append('%s %s %s %s %s' % (str(x[0]).rjust(5),x[1].ljust(30),x[2].ljust(6),x[3].ljust(25),x[4].ljust(50)))
        except:
            # Catch exception.
            res ['I have encountered a problem performing your search request!']

        return res
#------------------------------------------------------------------------------
#--|    Function Definition Section    |---------------------------------------
def LAR(db,title,message): # List all recipes in the database.
    c = db.listAll()
    lars = aScreen(title,'*','',message,c,['pass'])
    kbd = lars.showScreen()
    return kbd

def SAR(db,kbd): #Show a single recipe.
    # Show list of recipes to select from.
    if kbd == 'pass':
        kbd = LAR(db,'The Cookbook Project > Select A Recipe','Press "Q" to quit or make a selection <: br="br">    # Retrieve the choosen recipe from the database.
    elif kbd.isdigit() == True:
        rec = newRecord()
        rec.recID = int(kbd)
        rec = db.listOne(rec)
        # Display the results.
        c = [rec.name,' ','Preperation Steps:',rec.instructions,' ','Ingredients:']
        for x in range(0,len(rec.ingredients)):
            c.append(rec.ingredients[x])
        c.append(' ')
        c.append('Serves: %s' % str(rec.servings))
        c.append(' ')
        c.append('Written by %s' % rec.source)
        sars = aScreen('The Cookbook Project','*','','Press any key to continue <: br="br" c="c" pass="pass">        kbd = sars.showScreen()
    elif kbd == 'delete':
        kbd = LAR(db,'The Cookbook Project > Select A Recipe For DELETION!','Press "Q" to quit or make a selection <: br="br">        return kbd

def SRD(db): # Search for a recipe.
    # Determine search criteria.
    SRDS = aScreen('Search Database','*','pass','Press "Q" to quit or make a selection :> ',['Search by ...','1 - Recipe Name','2 - Author','3 - Ingredients'],['1','2','3','Q'])
    SRDSa = aScreen('Search Database By Recipe Name','*','pass',':> ',['What is the name of the recipie you would like to search for?'],['pass'])   
    SRDSb = aScreen('Search Database By Author','*','pass',':> ',['Who would you like to search for?'],['pass'])   
    SRDSc = aScreen('Search Database By Ingredients','*','pass',':> ',['Which ingredients would you like to search for?'],['pass'])   
    kbd = SRDS.showScreen()
    rec = newRecord()
    if kbd != 'Q':
        if kbd != '3':
            if kbd == '1':
                kbd = SRDSa.showScreen()
                sql = "SELECT pkID,name,source,servings FROM Recipes WHERE name LIKE '%%%s%%'" % kbd
                res = db.searchDB(sql,'pass')
            else:
                kbd = SRDSa.showScreen()
                sql = "SELECT pkID,name,source,servings FROM Recipes WHERE source LIKE '%%%s%%'" % kbd
                res = db.searchDB(sql,'pass')
        else:
            kbd = SRDSa.showScreen()
            sql = "SELECT r.pkID,r.name,r.servings,r.source,i.ingredients FROM Recipes r LEFT JOIN Ingredients i ON (r.pkID == i.recipeID) WHERE i.ingredients LIKE '%%%s%%' GROUP BY r.pkID" % kbd
            res = db.searchDB(sql,'3')

        SRDS = aScreen('Search Results','*','pass','Press "Q" to quit or make a selection :> ',res,['pass'])
        kbd = SRDS.showScreen()
        SAR(db,kbd)

def ARD(db): #Add a recipe to the database.
    nr = newRecord()
    screen = aScreen('Add A New Recipe','*','pass','Please enter the title of your recipe or press "Q" to quit :> ',[''],['pass'])
    nr.name = screen.showScreen()
    screen = aScreen('Add A New Recipe','*','pass','Please enter how many your recipe serves or press "Q" to quit :> ',[nr.name],['pass'])
    nr.servings = screen.showScreen()
    screen = aScreen('Add A New Recipe','*','pass','Please enter the name of the author of your recipe or press "Q" to quit :> ',[nr.name,'Serves %s' % nr.servings],['pass'])
    nr.source = screen.showScreen()
    screen = aScreen('Add A New Recipe','*','pass','Please enter the ingredients for your recipe or press "N" to move on :> ',[nr.name,'Serves %s' % nr.servings,'Written by %s' % nr.source],['pass'])
    kbd = screen.showScreen()
    loop = True
    while loop == True:
        if kbd == 'N':
            loop = False
        else:
            nr.ingredients.append(kbd)
            screen = aScreen('Add A New Recipe','*','pass','Please enter the ingredients for your recipe or press "N" to move on :> ',[nr.name,'Serves %s' % nr.servings,'Written by %s' % nr.source,' ','Ingredients'] + nr.ingredients,['pass'])
            kbd = screen.showScreen()
    screen = aScreen('Add A New Recipe','*','pass','Please provide instructions for prepairing your recipe or press "Q" to quit :> ',[nr.name,'Serves %s' % nr.servings,'Written by %s' % nr.source,' ','Ingredients'] + nr.ingredients,['pass'])
    nr.instructions = screen.showScreen()
    screen = aScreen('Add A New Recipe','*','pass','Do you wish to save this recipe? Press "Y" for YES and "N" for NO :> ',[nr.name,'Serves %s' % nr.servings,'Written by %s' % nr.source,' ','Ingredients'] + nr.ingredients + [' ','Preperation Instructions',nr.instructions],['pass'])
    kbd = screen.showScreen()
    # Now write all data to the database
    if kbd == 'Y':
        db.addRec(nr)

def DRD(db):
    screen = aScreen('Add A New Recipe','*','pass','Do you wish to continue? Press "Y" for YES and "N" for NO :> ',['WARNING!!! This section is for deleting recipes from your data base. This operation cannot be undone!'],['pass'])
    kbd = screen.showScreen()
    if kbd == 'Y':
        kbd = SAR(db,'delete')
        if kbd.isdigit() == True:
            db.deleteRec(kbd)

def mainLoop(dbname):
    db = newDB(dbname)
    t = 'The Cook Book Project > Main Menu'
    m = 'Press "Q" to quit or make a selection <: br="br">    o = ['Q','A','B','C','D','E']
    c = ['A - List All Recipes','B - Select A Recipe','C - Search Recipe Database',' ','D - "ADD" A New Recipe','E - "DELETE" A Recipe']
    mLscreen = aScreen(t,'*','pass',m,c,o)
    loop = True
    while loop == True: # Show the main menu until a valid option is selected.
        kbd = mLscreen.showScreen()
        if kbd == 'Q':
            loop = False
        elif kbd == 'A':
            # List all the recipes in the database.
            kbd = LAR(db,'The Cookbook Project > List All Records','Press any key to continue <: br="br">        elif kbd == 'B':
            # Select a recpie.
            SAR(db,'pass')
        elif kbd == 'C':
            # Search the data base.
            SRD(db)
        elif kbd == 'D':
            # Add a recipe.
            ARD(db)
        elif kbd == 'E':
            # Delete a recipe.
            DRD(db)
#------------------------------------------------------------------------------
#--|    Main Program    |------------------------------------------------------
# Title screen. Database file name will be asked for here.
intro = ['Welcome To The Cookbook Project','please enter the file name of your cookbook.']
title = aScreen('The Cookbook Project','*','pass','Press "Q" to quit or enter a file name <: br="br" intro="intro" pass="pass">dbname = title.showScreen()
# Main program loop.
if dbname != 'Q':
    if dbname == '':
        dbname ='cookbook1.db3' # This is a stub.
        mainLoop(dbname)
    else:
        mainLoop(dbname)

# Credits screen.
intro = ['The Cookbook Project Software.','Written by Kevin Lynch',' ','Original tutorial published by Full Circle Magazine.','Original code written by Greg Walters','Rainyday Solutions, LLC,','www.thedesignatedgeek.com',' ','Terminal dimentions detection by Grant Edwards']
title = aScreen(' ','*','centered','Press any key to quit <: br="br" intro="intro" pass="pass">dbname = title.showScreen()

Saturday 13 October 2012

Ubuntu Tip: Using Quickly To Build Applications - Basic Commands

What is Quickly?
Basically it's a command line based tool for building applications. So go ahead and open a terminal. Now it will be useful if you're familiar with the Python programming language. If you're not then there are tones of on-line resources to help you learn.

How to create a new application:
Type: quickly create ubuntu-application mybrowser

Quickly will create a directory with the same name as your application containing all it's files.

How to edit your application:
In the applications directory type: quickly edit

How to edit the GUI:
In the applications directory type: quickly design

How to run your application:
In the applications directory type: quickly run

How to package your application:
In the applications directory type: quickly package


You might also want to watch this video. It will take you through the development of a very basic web browser application.

Sunday 5 February 2012

Linux Is Not A Viable OS

Pageviews by OS 7 Jan 2012 - 5 Feb 2012
I just came across two comments in an idea on Dell's IdeaStorm claiming Linux is not a viable OS or some nonsense. Frankly I was gob-smacked to learn there are still some people promoting this FUD. So much so in fact I was moved to write quite a lengthy rant and then repost it here. Frankly if this stuff is still doing the rounds and people are actually believing it then we're not doing enough to get the message out that Linux currently dominates the OS market in it's many forms. It has a presence in virtually every device market pigeon hole. And it's the top contender in most. If you are a Linux user and Dell customer using Linux on Dell hardware then get your arse on IdeaStorm and tell Dell you want Linux!

That was the short version. Below is the long version I posted on IdeaStorm.

Linux is not a serious contender in the OS market? This is the sort of FUD and ignorance that keeps consumers away from Linux. Linux is very much a contender in the OS market. Which is why Microsoft lists desktop Linux as a threat to it's desktop Windows in it's tax returns. When netbooks first appeared Linux was such a massive threat to Microsoft it had to literally give Windows XP away for free and extend it's shelf life because Vista wouldn't run on a netbook.

If you have a WiFi router, DVR, DVD player, smart TV, satellite set top box, cable set top box or any number of other devices in your home then the chances are it's running either a Linux or BSD based OS.

Android/Linux smart phone deployments dwarf all others in the smart phone market. Even Apple's iPhone is dwarfed by Android when all Android distributors are counted as one. And Windows Phone 7? Hardly a blip on the radar.

It's true all manner of malware exists for Linux. However Linux has a different approach to dealing with malware. Yes the people sticking their heads in the sand are not helpful. Linux does have anti-malware software built right into the kernel. It's call Apparmor. It works a bit more like a white list of software rather than the black list Windows anti-virus vendors try to use. This is used in combination with community vigilance. One of the great advantages of Free and Open Source Software is the community gets to see the source code. It can be inspected so the community can determine what it does. So additional anti-virus software on Linux is generally speaking not needed.

With the black list approach you'll always be step behind. And that's just not good enough.

So far as the average office worker or home user is concerned GNU/Linux has all the bases covered when it comes to application software. There are more web browsers, e-mail clients and office productivity sweets than you can swing a cat at. And many of these applications are also available for Windows as well. So migration needn't be a harsh experience.

It's true more specialist bespoke software is not available off the shelf. However that's true of all OS platforms. The clue is in the "bespoke software" part. The Microsoft way is to tell people to use their OS and applications stack no matter what. And indeed Microsoft channel partners follow this mantra. Religiously sometimes. However it is very bad practice to shoehorn every business into the same mould. This is in fact the primary reason why malware is such a massive problem for Windows.

When building bespoke systems it is better to assess the clients actual needs and serve those needs first. When that approach is adopted. FOSS tends to win. Hence the reason why many of the worlds stock exchanges have switched to GNU/Linux. Hence the reason why US drones now run on Linux instead of Windows which could not be properly secured against malware. Hence the reason why many EU government agencies are switching to GNU/Linux and why GNU/Linux is so popular in South America. Hence the reason why something on the order of the top 40 of the worlds most powerful super computers are running GNU/Linux and why most of the top 500 are running GNU/Linux.

And then there is the tablet market. Which very closely resembles the smart phone market. ARM based devices running Android/Linux.

At this stage in the game anybody claiming Linux is not a viable contender is just delusional. The last market Linux has left to conquer is the desktop. Which is ironically becoming less relevant every year if the pundits are right. The only market where Windows has a strangle hold is the desktop/laptop market. Linux is slowly gaining ground. Microsoft is slowly losing.
Pageviews by Browser 7 Jan 2010 - 5 Feb 2012

Interested In Linux? Here are a few resources.

Incidentally if you're using Google or Facebook. You're a Linux user already. Something to think about people.