Initial commit.
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 10 Aug 2008 06:26:12 +0000 (02:26 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 10 Aug 2008 06:26:12 +0000 (02:26 -0400)
48 files changed:
.gitignore [new file with mode: 0644]
Configuration/CmdLineOptions.py [new file with mode: 0644]
Configuration/ConfigFile.py [new file with mode: 0644]
Configuration/Defaults.py [new file with mode: 0644]
Configuration/__init__.py [new file with mode: 0644]
Mime/SupportedType.py [new file with mode: 0644]
Mime/XmlDataParser.py [new file with mode: 0644]
Mime/__init__.py [new file with mode: 0644]
Pictar.py [new file with mode: 0644]
TemplateParser.py [new file with mode: 0644]
default_page_template.html [new file with mode: 0644]
default_stylesheet.css [new file with mode: 0644]
mimetypes.xml [new file with mode: 0644]
pictar [new file with mode: 0755]
templates/img.html [new file with mode: 0644]
templates/object.html [new file with mode: 0644]
test_cases/.gif.fail [new file with mode: 0644]
test_cases/.jpeg.fail [new file with mode: 0644]
test_cases/.jpg.fail [new file with mode: 0644]
test_cases/.pass.gif [new file with mode: 0644]
test_cases/.pass.jpeg [new file with mode: 0644]
test_cases/.pass.jpg [new file with mode: 0644]
test_cases/.pass.png [new file with mode: 0644]
test_cases/.pass.svg [new file with mode: 0644]
test_cases/.pass.svgz [new file with mode: 0644]
test_cases/.png.fail [new file with mode: 0644]
test_cases/.svg.fail [new file with mode: 0644]
test_cases/.svgz.fail [new file with mode: 0644]
test_cases/fail.gif.s [new file with mode: 0644]
test_cases/fail.jpeg.s [new file with mode: 0644]
test_cases/fail.jpg.s [new file with mode: 0644]
test_cases/fail.png.s [new file with mode: 0644]
test_cases/fail.svg.s [new file with mode: 0644]
test_cases/fail.svgz.s [new file with mode: 0644]
test_cases/failjpeg [new file with mode: 0644]
test_cases/failjpg [new file with mode: 0644]
test_cases/failpng [new file with mode: 0644]
test_cases/failsvg [new file with mode: 0644]
test_cases/failsvgz [new file with mode: 0644]
test_cases/pass.GIF [new file with mode: 0644]
test_cases/pass.JPG [new file with mode: 0644]
test_cases/pass.gif [new file with mode: 0644]
test_cases/pass.jpEg [new file with mode: 0644]
test_cases/pass.jpeg [new file with mode: 0644]
test_cases/pass.jpg [new file with mode: 0644]
test_cases/pass.png [new file with mode: 0644]
test_cases/pass.svG [new file with mode: 0644]
test_cases/pass.svg [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..7e99e36
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
\ No newline at end of file
diff --git a/Configuration/CmdLineOptions.py b/Configuration/CmdLineOptions.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Configuration/ConfigFile.py b/Configuration/ConfigFile.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Configuration/Defaults.py b/Configuration/Defaults.py
new file mode 100644 (file)
index 0000000..8e6bfc9
--- /dev/null
@@ -0,0 +1,9 @@
+import os, sys
+
+MIMETYPES_FILE_NAME = os.path.realpath(sys.path[0]) + '/mimetypes.xml'
+PAGE_TEMPLATE_FILE_NAME = os.path.realpath(sys.path[0]) + '/default_page_template.html'
+STYLESHEET_FILE_NAME = os.path.realpath(sys.path[0]) + '/default_stylesheet.css'
+BROWSER_COMMAND = '/usr/bin/firefox'
+
+ITEM_WIDTH = 800
+ITEM_HEIGHT = 600
diff --git a/Configuration/__init__.py b/Configuration/__init__.py
new file mode 100644 (file)
index 0000000..62912b9
--- /dev/null
@@ -0,0 +1,94 @@
+import os
+import sys
+import time
+import md5
+import tempfile
+import Defaults
+
+
+def GetPictarDir():
+    rel_script_path = sys.argv[0]
+    abs_script_path = os.path.abspath(rel_script_path)
+    abs_script_dir = os.path.dirname(abs_script_path)
+    
+    return abs_script_dir
+    
+
+def GetBrowserCommand():
+    # Get the command used to execute the user's browser.
+    return Defaults.BROWSER_COMMAND
+
+    
+def GetAbsTargetDir():
+    rel_target_dir = os.getcwd()
+    argc = len(sys.argv)
+
+    if (argc > 1):
+        # If no directory was passed to the program,
+        # use the current working directory as the target.
+        # Otherwise, use the passed directory.
+        rel_target_dir = sys.argv[1]
+
+    # Convert a relative path into an absolute one.
+    # It will be important to have an absolute path
+    # later when we are constructing URLs.
+    __abs_target_dir = os.path.abspath(rel_target_dir)
+
+    return __abs_target_dir
+
+
+
+
+def GetPageTemplatePath():
+    script_dir = GetPictarDir()
+    page_template_path = os.path.join(script_dir, Defaults.PAGE_TEMPLATE_FILE_NAME)
+    
+    return page_template_path
+
+
+
+def GetRecursive():
+    __recursive = False
+    return __recursive
+
+
+
+def GetOutfilePath():
+    unique_string = str(time.time())
+    md5hash = md5.new(unique_string)
+    outfile_name = md5hash.hexdigest()
+    outfile_path = os.path.join(tempfile.gettempdir(), outfile_name)
+        
+    return outfile_path
+
+
+
+
+
+
+def GetItemTemplatePath():
+    script_dir = GetPictarDir()
+    item_template_path = os.path.join(script_dir, Defaults.ITEM_TEMPLATE_FILE_NAME)
+        
+    return item_template_path
+
+
+
+def GetStyleSheetPath():
+    script_dir = GetPictarDir()
+    stylesheet_path = os.path.join(script_dir, Defaults.STYLESHEET_FILE_NAME)
+        
+    return stylesheet_path
+        
+
+
+def GetVerbose():
+    return True
+
+    
+def GetMimetypesFilePath():
+    script_dir = GetPictarDir()
+    mimetypes_file_path = os.path.join(script_dir, Defaults.MIMETYPES_FILE_NAME)
+
+    return mimetypes_file_path
+    
diff --git a/Mime/SupportedType.py b/Mime/SupportedType.py
new file mode 100644 (file)
index 0000000..9ab2f3e
--- /dev/null
@@ -0,0 +1,76 @@
+
+"""
+Wrapper class for the 'filetype' elements in the config file. Nothing
+tricky here, except that the accessor for the template should
+intelligently determine which of the two fields (template_file,
+template_data) is used as the template source.
+"""
+
+class SupportedType:
+
+    def __init__(self,
+                 name = "",
+                 description = "",
+                 extensions = [],
+                 template_file = "",
+                 template_data = ""):
+        
+        self.__name = name
+        self.__description = description
+        self.__extensions = extensions
+        self.__template_file = template_file
+        self.__template_data = template_data
+
+        return
+
+
+
+    def GetName(self):
+        return self.__name
+
+
+    def GetDescription(self):
+        return self.__description
+
+    
+    def GetExtensions(self):
+        return self.__extensions
+
+    
+    def GetTemplate(self):
+        if (self.__template_data == None or self.__template_data == ""):
+            # The data is not given explicitly, so rely on the
+            # template file to provide it.
+            template_file = open(self.__template_file, "r")
+            template = template_file.read()
+            template_file.close()
+            return template
+        else:
+            return self.__template_data
+        
+
+
+    def SetName(self, name):
+        self.__name = name
+        return
+
+
+    def SetDescription(self, description):
+        self.__description = description
+        return
+
+
+    def SetExtensions(self, extensions):
+        self.__extensions = extensions
+        return
+
+    
+    def SetTemplateData(self, template_data):
+        self.__template_data = template_data
+        return
+
+    
+    def SetTemplateFile(self, template_file):
+        self.__template_file = template_file
+        return
+    
diff --git a/Mime/XmlDataParser.py b/Mime/XmlDataParser.py
new file mode 100644 (file)
index 0000000..4cd3bec
--- /dev/null
@@ -0,0 +1,119 @@
+import os
+from SupportedType import SupportedType
+from xml.dom import Node
+from xml.dom.ext.reader.Sax import FromXmlFile
+
+"""
+This class parses all of the information in the config file, and makes
+it available through various methods.
+"""
+
+class XmlDataParser:
+    
+    def __init__(self, xml_file_path):
+        self.__supported_types = []
+
+        xml_doc = FromXmlFile(xml_file_path)
+
+        supported_type_elements = xml_doc.getElementsByTagName('supported_type')
+        
+        for current_node in supported_type_elements:
+            name = self.GetChildrenText(current_node, 'name')[0]
+            descriptions = self.GetChildrenText(current_node, 'description')
+            extensions = self.GetChildrenText(current_node, 'extension')
+            template_files = self.GetChildrenText(current_node, 'template_file')        
+            template_data = self.GetChildrenText(current_node, 'template_data')
+            
+            current_type = SupportedType()
+            current_type.SetName(name)
+            current_type.SetExtensions(extensions)
+
+            if (len(descriptions) > 0):
+                current_type.SetDescription(descriptions[0])
+
+                
+            if (len(template_files) > 0):
+                # Allow for relative paths in the template field
+                root_dir = os.path.dirname(xml_file_path)
+                template_file_path = os.path.join(root_dir, template_files[0])
+                current_type.SetTemplateFile(template_file_path)
+
+                       
+            if (len(template_data) > 0):
+                current_type.SetTemplateData(template_data[0])
+
+            self.__supported_types.append(current_type)
+
+        return
+
+
+
+    def GetAllExtensions(self):
+        all_extensions = []
+        
+        for current_type in self.__supported_types:
+            current_type_extensions = current_type.GetExtensions()
+            for extension in current_type_extensions:
+                all_extensions.append(extension)
+
+        return all_extensions
+
+    
+        
+    def GetTemplateByExtension(self, extension):
+        # Get the (x)html template which corresponds to a mimetype
+        # having the supplied extension.
+        for current_type in self.__supported_types:
+            current_type_extensions = current_type.GetExtensions()
+
+            for current_extension in current_type_extensions:
+                if (current_extension.lower() == extension.lower()):
+                    return current_type.GetTemplate()
+            
+        return None
+
+
+
+    def GetNameByExtension(self, extension):
+        # Get the name of the mimetype that a particular extension
+        # belongs to.
+        for current_type in self.__supported_types:
+            current_type_extensions = current_type.GetExtensions()
+
+            for current_extension in current_type_extensions:
+                if (current_extension.lower() == extension.lower()):
+                    return current_type.GetName()
+            
+        return None
+
+    
+            
+    def GetChildNodesByTagName(self, node, tag_name):
+        found_children = []
+        
+        for child in node.childNodes:
+            if (child.nodeName == tag_name):
+                found_children.append(child)
+
+        return found_children
+
+
+
+    
+    def GetChildrenText(self, node, child_tag_name):
+        children_text = []
+        
+        children = self.GetChildNodesByTagName(node, child_tag_name)
+
+        for child_node in children:
+            for sub_child in child_node.childNodes:
+                if (sub_child.nodeType == Node.TEXT_NODE):
+                    children_text.append(sub_child.nodeValue)
+                    break
+
+                
+        return children_text
+
+
+    
+
diff --git a/Mime/__init__.py b/Mime/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Pictar.py b/Pictar.py
new file mode 100644 (file)
index 0000000..67a056f
--- /dev/null
+++ b/Pictar.py
@@ -0,0 +1,103 @@
+import re
+import os
+import sys
+import urllib
+
+import Configuration
+
+from string import Template
+from TemplateParser import *
+from Mime.XmlDataParser import XmlDataParser
+
+
+class Pictar:
+
+    def __init__(self):
+        self.template_parser = TemplateParser()
+        self.__data_parser = XmlDataParser(Configuration.GetMimetypesFilePath())
+        
+
+        # Compile the regular expressions only once
+        self.item_regexes = []
+
+        extensions = self.__data_parser.GetAllExtensions()
+        
+        for extension in extensions:
+            pattern = '\\' + extension + '$'
+            self.item_regexes.append(re.compile(pattern, re.IGNORECASE))
+
+        return
+
+
+
+    def GetItemPaths(self):
+        # Create a list of the item paths based on which
+        # of the files in the target directory match at least
+        # one of the item regular expressions.
+        target_dir = Configuration.GetAbsTargetDir()
+        
+        item_paths = []
+
+        dir_contents = os.listdir(target_dir)
+
+        for file_name in dir_contents:
+            for regex_obj in self.item_regexes:
+                if (regex_obj.search(file_name) != None):
+                    absolute_item_path = os.path.join(target_dir, file_name)
+                    encoded_path = urllib.quote(absolute_item_path)
+                    item_paths.append(encoded_path)
+
+        return item_paths
+
+
+
+    def GetItemData(self, item_paths):
+        # Build the pictar markup
+        item_data = ""
+        current_item_number = 0
+        
+        for item_path in item_paths:
+            current_item_number += 1
+            item_extension = os.path.splitext(item_path)[1]
+            item_template = Template(self.__data_parser.GetTemplateByExtension(item_extension))
+            item_mimetype = self.__data_parser.GetNameByExtension(item_extension)
+            
+            current_item_data = item_template.substitute(header=item_path,
+                                                         image_path=item_path,
+                                                         item_number=current_item_number,
+                                                         height=Configuration.Defaults.ITEM_HEIGHT,
+                                                         width=Configuration.Defaults.ITEM_WIDTH,
+                                                         mimetype=item_mimetype);
+            
+            item_data += current_item_data
+
+        return item_data
+        
+
+
+    def GetPageData(self, item_data):
+        # Fill in the template with our markup
+        stylesheet_file_name = Configuration.GetStyleSheetPath()
+        page_template = self.template_parser.GetPageTemplate()
+        page_data = page_template.substitute(stylesheet=stylesheet_file_name,
+                                             pictar_markup=item_data)
+
+        return page_data
+
+
+    
+    def LaunchBrowser(self, url='about:blank'):
+        launch_result = os.spawnl(os.P_WAIT, Configuration.GetBrowserCommand(), Configuration.GetBrowserCommand(), url)
+        return launch_result
+
+
+
+    def WritePage(self, page_data):
+        # Write the output file
+        outfile_path = Configuration.GetOutfilePath()
+        
+        outfile = open(outfile_path, "w")
+        outfile.write(page_data)
+        outfile.close()
+        
+        return outfile_path
diff --git a/TemplateParser.py b/TemplateParser.py
new file mode 100644 (file)
index 0000000..fcf044c
--- /dev/null
@@ -0,0 +1,16 @@
+import Configuration
+from string import Template
+
+
+class TemplateParser:
+
+    def GetPageTemplate(self):
+        # Read the page template file into a string
+        page_template_path = Configuration.GetPageTemplatePath()
+        page_template_file = open(page_template_path, "r")
+        page_template = Template(page_template_file.read())
+        page_template_file.close()
+
+        return page_template
+
+    
diff --git a/default_page_template.html b/default_page_template.html
new file mode 100644 (file)
index 0000000..a4386a4
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  
+  <title>PICTAR</title>
+  
+  <link rel="stylesheet" type="text/css" href="${stylesheet}" />
+
+</head>
+
+<body>
+
+  ${pictar_markup}
+  
+</body>
+
+</html>
diff --git a/default_stylesheet.css b/default_stylesheet.css
new file mode 100644 (file)
index 0000000..93e3667
--- /dev/null
@@ -0,0 +1,42 @@
+/************/
+/* ELEMENTS */
+/************/
+
+body {
+  margin: 0em;
+  padding: 0em;
+  background-color: #ebebae;
+}
+
+
+img {
+  border: 0em;
+  border-top: 1px #000000 dashed;
+  padding: 20px;
+}
+
+
+/***********/
+/* CLASSES */
+/***********/
+
+.image_container {
+  display: table;
+  background-color: #f0ead8;
+  border: 1px #000000 dashed;
+  margin-left: 5px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+
+.image_header {
+  background-color: #bf3e18;
+  color: #ffffff;
+  font-weight: bold;
+  text-align: center;
+  padding: .2em;
+  font-family: fixed, monospace;
+}
+
+
diff --git a/mimetypes.xml b/mimetypes.xml
new file mode 100644 (file)
index 0000000..4fa1099
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+
+<!-- PICTARRRRRRRR!!! -->
+
+<mimetypes>
+
+  <supported_type>
+    <name>image/gif</name>
+    <description>Graphics Interchange Format (GIF) Image</description>
+    <extension>.gif</extension>
+    <template_file>templates/img.html</template_file>
+  </supported_type>
+
+  <supported_type>
+    <name>image/jpeg</name>
+    <description>Joint Photographic Experts Group (JPEG) Image</description>
+    <extension>.jpg</extension>
+    <extension>.jpe</extension>
+    <extension>.jpeg</extension>
+    <template_file>templates/img.html</template_file>
+  </supported_type>
+
+  <supported_type>
+    <name>image/png</name>
+    <description>Portable Network Graphic</description>
+    <extension>.png</extension>
+    <template_file>templates/img.html</template_file>
+  </supported_type>
+
+  <supported_type>
+    <name>image/svg+xml</name>
+    <description>Scalable Vector Graphic</description>
+    <extension>.svg</extension>
+    <template_file>templates/object.html</template_file>
+  </supported_type>
+
+</mimetypes>
\ No newline at end of file
diff --git a/pictar b/pictar
new file mode 100755 (executable)
index 0000000..e4a3e8e
--- /dev/null
+++ b/pictar
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+from Pictar import *
+
+the_pictar = Pictar()
+
+
+"""
+if (os.path.isdir(target_dir) == False):
+   print "Error: The given directory, \"" + target_dir + "\", is invalid.\n"
+   sys.exit(FAILURE)
+"""
+
+
+
+image_paths = the_pictar.GetItemPaths()
+
+image_data = the_pictar.GetItemData(image_paths)
+
+page_data = the_pictar.GetPageData(image_data)
+
+outfile = the_pictar.WritePage(page_data)
+
+the_pictar.LaunchBrowser(outfile)
+
diff --git a/templates/img.html b/templates/img.html
new file mode 100644 (file)
index 0000000..0a0d315
--- /dev/null
@@ -0,0 +1,8 @@
+<div class="image_container">
+  <div class="image_header">
+    ${header}
+  </div>
+
+  <img src="${image_path}"
+       alt="Pictar Image ${item_number}" />
+</div>
diff --git a/templates/object.html b/templates/object.html
new file mode 100644 (file)
index 0000000..c69a543
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="image_container">
+  <div class="image_header">
+    ${header}
+  </div>
+
+  <object data="${image_path}"
+         height="${height}"
+         width="${width}"
+         type="${mimetype}" />
+  
+</div>
\ No newline at end of file
diff --git a/test_cases/.gif.fail b/test_cases/.gif.fail
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.jpeg.fail b/test_cases/.jpeg.fail
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.jpg.fail b/test_cases/.jpg.fail
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.pass.gif b/test_cases/.pass.gif
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.pass.jpeg b/test_cases/.pass.jpeg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.pass.jpg b/test_cases/.pass.jpg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.pass.png b/test_cases/.pass.png
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.pass.svg b/test_cases/.pass.svg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.pass.svgz b/test_cases/.pass.svgz
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.png.fail b/test_cases/.png.fail
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.svg.fail b/test_cases/.svg.fail
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/.svgz.fail b/test_cases/.svgz.fail
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/fail.gif.s b/test_cases/fail.gif.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/fail.jpeg.s b/test_cases/fail.jpeg.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/fail.jpg.s b/test_cases/fail.jpg.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/fail.png.s b/test_cases/fail.png.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/fail.svg.s b/test_cases/fail.svg.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/fail.svgz.s b/test_cases/fail.svgz.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/failjpeg b/test_cases/failjpeg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/failjpg b/test_cases/failjpg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/failpng b/test_cases/failpng
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/failsvg b/test_cases/failsvg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/failsvgz b/test_cases/failsvgz
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.GIF b/test_cases/pass.GIF
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.JPG b/test_cases/pass.JPG
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.gif b/test_cases/pass.gif
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.jpEg b/test_cases/pass.jpEg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.jpeg b/test_cases/pass.jpeg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.jpg b/test_cases/pass.jpg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.png b/test_cases/pass.png
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.svG b/test_cases/pass.svG
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test_cases/pass.svg b/test_cases/pass.svg
new file mode 100644 (file)
index 0000000..e69de29