From bf9d2e42f3734fc34593111a2ee40d8193978b89 Mon Sep 17 00:00:00 2001 From: Tom Bell Date: Fri, 5 Mar 2010 17:08:37 +0000 Subject: [PATCH 01/41] Changes to make Hyde run on Windows systems. --- hydeengine/siteinfo.py | 6 ++++-- hydeengine/url.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/hydeengine/siteinfo.py b/hydeengine/siteinfo.py index 24018dc2..e510b451 100644 --- a/hydeengine/siteinfo.py +++ b/hydeengine/siteinfo.py @@ -368,7 +368,8 @@ def target_folder(self): @property def temp_folder(self): temp_folder = self.site.temp_folder - return temp_folder.child_folder_with_fragment(self.url) + # return temp_folder.child_folder_with_fragment(self.url) + return temp_folder.child_folder_with_fragment(self.fragment) @property def fragment(self): @@ -432,7 +433,8 @@ def target_folder(self): @property def temp_folder(self): temp_folder = self.site.temp_folder - return temp_folder.child_folder_with_fragment(self.url) + # return temp_folder.child_folder_with_fragment(self.url) + return temp_folder.child_folder_with_fragment(self.fragment) class SiteInfo(SiteNode): def __init__(self, settings, site_path): diff --git a/hydeengine/url.py b/hydeengine/url.py index 0cfcacd3..3b66dbea 100644 --- a/hydeengine/url.py +++ b/hydeengine/url.py @@ -2,6 +2,7 @@ Utility functions for dealing with urls. """ +import sys def join(parent, child): """ @@ -19,6 +20,8 @@ def fixslash(url, relative=True): otherwise ensures it is not. """ + if sys.platform == 'win32': + url = url.replace('\\', '/') url = url.strip("/") if relative: url = "/" + url @@ -31,7 +34,6 @@ def clean_url(url): Removes .html from the url if it exists. """ - parts = url.rsplit(".", 1) if parts[1] == "html": return parts[0] From 3ca5b140495ec6912af63b497cc8bc8414a508aa Mon Sep 17 00:00:00 2001 From: Tom Bell Date: Thu, 11 Mar 2010 15:52:25 +0000 Subject: [PATCH 02/41] Modified to maintain existing cross-platform compatability, while also allowing it to work on Windows --- hydeengine/siteinfo.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hydeengine/siteinfo.py b/hydeengine/siteinfo.py index e510b451..ce79fd92 100644 --- a/hydeengine/siteinfo.py +++ b/hydeengine/siteinfo.py @@ -368,8 +368,10 @@ def target_folder(self): @property def temp_folder(self): temp_folder = self.site.temp_folder - # return temp_folder.child_folder_with_fragment(self.url) - return temp_folder.child_folder_with_fragment(self.fragment) + if sys.platform == 'win32': + return temp_folder.child_folder_with_fragment(self.fragment) + else: + return temp_folder.child_folder_with_fragment(self.url) @property def fragment(self): @@ -433,8 +435,10 @@ def target_folder(self): @property def temp_folder(self): temp_folder = self.site.temp_folder - # return temp_folder.child_folder_with_fragment(self.url) - return temp_folder.child_folder_with_fragment(self.fragment) + if sys.platform == 'win32': + return temp_folder.child_folder_with_fragment(self.fragment) + else: + return temp_folder.child_folder_with_fragment(self.url) class SiteInfo(SiteNode): def __init__(self, settings, site_path): @@ -547,7 +551,6 @@ def refresh(self, queue=None): site = self # Have to poll for changes since there is no reliable way # to get notification in a platform independent manner - # class Visitor(object): def visit_folder(self, folder): return folder.allow(**site.settings.FILTER) From 823fa0712266037f7757dacf4a0447701c84bacc Mon Sep 17 00:00:00 2001 From: Tom Bell Date: Thu, 11 Mar 2010 23:27:47 +0000 Subject: [PATCH 03/41] Updated with more Windows support --- hydeengine/processor.py | 10 ++++++++-- sites.yaml | 25 +++++++++++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/hydeengine/processor.py b/hydeengine/processor.py index d599feed..5b20f97c 100644 --- a/hydeengine/processor.py +++ b/hydeengine/processor.py @@ -133,9 +133,15 @@ def __around_process__(self, node, processors): if not child.type in ("content", "media"): continue fragment = child.temp_folder.get_fragment(node.site.temp_folder) - fragment = fragment.rstrip("/") + if sys.platform == 'win32': + fragment = fragment.rstrip("\\") + else: + fragment = fragment.rstrip("/") if not fragment: - fragment = "/" + if sys.platform == 'win32': + fragment = "\\" + else: + fragment = "/" if fragment in processors: processor_config = processors[fragment] for processor_name, params in processor_config.iteritems(): diff --git a/sites.yaml b/sites.yaml index d78a0e0b..1aa07a61 100644 --- a/sites.yaml +++ b/sites.yaml @@ -1,14 +1,15 @@ clydetest: - path: ~/clydetest - repo: - type: repos.git.Git - url: git@github.com:lakshmivyas/clydetest.git - draft_branch: drafts - production_branch: master + path: ~/clydetest + repo: + type: repos.git.Git + url: git@github.com:lakshmivyas/clydetest.git + draft_branch: drafts + production_branch: master + site2: - path: ~/mysite - repo: - type: repos.git.Git - url: git@github.com:lakshmivyas/clydetest.git - draft_branch: drafts - production_branch: master + path: ~/mysite + repo: + type: repos.git.Git + url: git@github.com:lakshmivyas/clydetest.git + draft_branch: drafts + production_branch: master From f1b94c55f5fb3b842a3339371181e14a7c304988 Mon Sep 17 00:00:00 2001 From: Tom Bell Date: Fri, 12 Mar 2010 01:29:20 +0000 Subject: [PATCH 04/41] Updated with more Windows support --- hydeengine/file_system.py | 2 +- hydeengine/siteinfo.py | 164 +++++++++++++++++++------------------- 2 files changed, 81 insertions(+), 85 deletions(-) diff --git a/hydeengine/file_system.py b/hydeengine/file_system.py index 4bd66d94..1b0defde 100644 --- a/hydeengine/file_system.py +++ b/hydeengine/file_system.py @@ -12,7 +12,7 @@ import fnmatch from datetime import datetime from distutils import dir_util, file_util -from .path_util import PathUtil +from path_util import PathUtil class FileSystemEntity(object): diff --git a/hydeengine/siteinfo.py b/hydeengine/siteinfo.py index ce79fd92..59b56a0b 100644 --- a/hydeengine/siteinfo.py +++ b/hydeengine/siteinfo.py @@ -22,40 +22,40 @@ def __init__(self, a_file, node): self.temp_file = File( self.node.temp_folder.child(self.file.name)) self.last_known_modification_time = a_file.last_modified - + @property def is_layout(self): return (self.node.type == "layout" or self.file.name.startswith("_")) - + @property def has_changes(self): return (not self.last_known_modification_time == self.file.last_modified) - + @property def url(self): if self.node.url is None: return None return url.join(self.node.url, self.file.name) - + @property def last_modified(self): return self.file.last_modified - + @property def name(self): return self.file.name - + @property def full_url(self): if not self.node.full_url: return None return url.join(self.node.full_url, self.file.name) - + def __repr__(self): return str(self.file) - + class Page(SiteResource): def __init__(self, a_file, node): if not node: @@ -74,7 +74,7 @@ def __init__(self, a_file, node): self.created = datetime.combine(self.created, time()) if type(self.updated) == date: self.updated = datetime.combine(self.updated, time()) - + @property def page_name(self): return self.file.name_without_extension @@ -118,14 +118,14 @@ def process(self): self.node.folder.name.lower() or self.file.name_without_extension.lower() in self.node.site.settings.LISTING_PAGE_NAMES): - + self.listing = True - + self.display_in_list = (not self.listing and not self.exclude and not self.file.name.startswith("_") and self.file.kind == "html") - + def _make_clean_url(self, page_url): if self.node.listing_page == self: page_url = self.node.url @@ -134,8 +134,8 @@ def _make_clean_url(self, page_url): if self.node.site.settings.APPEND_SLASH or not page_url: page_url += "/" return page_url - - + + @property def url(self): page_url = super(Page, self).url @@ -144,7 +144,7 @@ def url(self): if self.node.site.settings.GENERATE_CLEAN_URLS: page_url = self._make_clean_url(page_url) return page_url - + @property def full_url(self): page_url = super(Page, self).full_url @@ -153,7 +153,7 @@ def full_url(self): if self.node.site.settings.GENERATE_CLEAN_URLS: page_url = self._make_clean_url(page_url) return page_url - + class SiteNode(object): def __init__(self, folder, parent=None): super(SiteNode, self).__init__() @@ -167,7 +167,7 @@ def __init__(self, folder, parent=None): def __repr__(self): return str(self.folder) - + @property def simple_dict(self): ress = [] @@ -187,23 +187,23 @@ def simple_dict(self): path=self.folder.get_fragment(self.site.folder.path), resources=ress, nodes=nodes) - + @property def isroot(self): return not self.parent - + @property def name(self): return self.folder.name - + @property def author(self): return self.site.settings.SITE_AUTHOR - + @property def has_listing(self): return not self.listing_page is None - + def walk(self): yield self for child in self.children: @@ -215,7 +215,7 @@ def walk_reverse(self): for child in reversed(self.children): for node in child.walk_reverse(): yield node - + def walk_resources(self): for node in self.walk(): for resource in node.resources: @@ -238,7 +238,7 @@ def add_child(self, folder): self.children.append(node) self.site.child_added(node) return node - + def add_resource(self, a_file): resource = self._add_resource(a_file) self.site.resource_added(resource) @@ -252,21 +252,21 @@ def _add_resource(self, a_file): resource = SiteResource(a_file, self) self.resources.append(resource) return resource - + def find_node(self, folder): try: return self.site.nodemap[folder.path] except KeyError: return None - + find_child = find_node - + def find_resource(self, a_file): try: return self.site.resourcemap[a_file.path] except KeyError: return None - + @property def source_folder(self): return self.folder @@ -274,7 +274,7 @@ def source_folder(self): @property def target_folder(self): return None - + @property def temp_folder(self): return None @@ -282,19 +282,19 @@ def temp_folder(self): @property def url(self): return None - + @property def full_url(self): if self.url is None: return None return url.join(self.site.settings.SITE_WWW_URL, self.url) - + @property def type(self): return None - + class ContentNode(SiteNode): - + def __init__(self, folder, parent=None): super(ContentNode, self).__init__(folder, parent) self.listing_page = None @@ -309,18 +309,18 @@ def module(self): not module.parent == self.site.content_node): module = module.parent return module - + @property def name(self): if self == self.site.content_node: return self.site.name else: return super(ContentNode, self).name - + @property def pages(self): return self.resources - + @property def ancestors(self): node = self @@ -330,13 +330,13 @@ def ancestors(self): node = node.parent ancestors.reverse() return ancestors - - + + @staticmethod def is_content(site, folder): return (site.content_folder.same_as(folder) or site.content_folder.is_ancestor_of(folder)) - + def _add_resource(self, a_file): page = Page(a_file, self) if page.listing and not self.listing_page: @@ -344,7 +344,7 @@ def _add_resource(self, a_file): self.resources.append(page) page.node.sort() return page - + def sort(self): self.resources.sort(key=operator.attrgetter("created"), reverse=True) prev = None @@ -359,19 +359,16 @@ def sort(self): prev = page for node in self.children: node.sort() - + @property def target_folder(self): deploy_folder = self.site.target_folder - return deploy_folder.child_folder_with_fragment(self.url) + return deploy_folder.child_folder_with_fragment(self.fragment) @property def temp_folder(self): temp_folder = self.site.temp_folder - if sys.platform == 'win32': - return temp_folder.child_folder_with_fragment(self.fragment) - else: - return temp_folder.child_folder_with_fragment(self.url) + return temp_folder.child_folder_with_fragment(self.fragment) @property def fragment(self): @@ -382,30 +379,30 @@ def url(self): return url.join(self.site.settings.SITE_ROOT, url.fixslash( self.folder.get_fragment(self.site.content_folder))) - + @property def type(self): return "content" - + @property def listing_url(self): return self.listing_page.url - + class LayoutNode(SiteNode): - + @staticmethod def is_layout(site, folder): return (site.layout_folder.same_as(folder) or site.layout_folder.is_ancestor_of(folder)) - + @property def fragment(self): return self.folder.get_fragment(self.site.layout_folder) - + @property def type(self): return "layout" - + class MediaNode(SiteNode): @staticmethod @@ -416,7 +413,7 @@ def is_media(site, folder): @property def fragment(self): return self.folder.get_fragment(self.site.media_folder) - + @property def url(self): return url.join(self.site.settings.SITE_ROOT, @@ -426,20 +423,19 @@ def url(self): @property def type(self): return "media" - + @property def target_folder(self): deploy_folder = self.site.target_folder - return deploy_folder.child_folder_with_fragment(self.url) + return deploy_folder.child_folder_with_fragment( + Folder(self.site.media_folder.name).child(self.fragment)) @property def temp_folder(self): temp_folder = self.site.temp_folder - if sys.platform == 'win32': - return temp_folder.child_folder_with_fragment(self.fragment) - else: - return temp_folder.child_folder_with_fragment(self.url) - + return temp_folder.child_folder_with_fragment( + Folder(self.site.media_folder.name).child(self.fragment)) + class SiteInfo(SiteNode): def __init__(self, settings, site_path): super(SiteInfo, self).__init__(Folder(site_path)) @@ -448,7 +444,7 @@ def __init__(self, settings, site_path): self._stop = Event() self.nodemap = {site_path:self} self.resourcemap = {} - + @property def name(self): return self.settings.SITE_NAME @@ -456,27 +452,27 @@ def name(self): @property def content_node(self): return self.nodemap[self.content_folder.path] - + @property def fragment(self): return "" - + @property def media_node(self): return self.nodemap[self.media_folder.path] - + @property def layout_node(self): return self.nodemap[self.layout_folder.path] - + @property def content_folder(self): return Folder(self.settings.CONTENT_DIR) - + @property def layout_folder(self): return Folder(self.settings.LAYOUT_DIR) - + @property def media_folder(self): return Folder(self.settings.MEDIA_DIR) @@ -488,23 +484,23 @@ def temp_folder(self): @property def target_folder(self): return Folder(self.settings.DEPLOY_DIR) - + def child_added(self, node): self.nodemap[node.folder.path] = node - + def resource_added(self, resource): self.resourcemap[resource.file.path] = resource - + def resource_removed(self, resource): del self.resourcemap[resource.file.path] - + def remove_node(self, node): for node in node.walk(): del self.nodemap[node.folder.path] for resource in node.walk_resources(): self.resource_removed(resource) node.parent.children.remove(node) - + def monitor(self, queue=None, waittime=1): if self.m and self.m.isAlive(): raise "A monitor is currently running." @@ -513,14 +509,14 @@ def monitor(self, queue=None, waittime=1): kwargs={"waittime":waittime, "queue": queue}) self.m.start() return self.m - + def dont_monitor(self): if not self.m or not self.m.isAlive(): return self._stop.set() self.m.join() self._stop.clear() - + def __monitor_thread__(self, queue, waittime): while not self._stop.isSet(): try: @@ -532,21 +528,21 @@ def __monitor_thread__(self, queue, waittime): if self._stop.isSet(): break sleeper.sleep(waittime) - + def find_and_add_resource(self, a_file): resource = self.find_resource(a_file) if resource: return resource node = self.find_and_add_node(a_file.parent) return node.add_resource(a_file) - + def find_and_add_node(self, folder): node = self.find_node(folder) if node: return node node = self.find_and_add_node(folder.parent) return node.add_child(folder) - + def refresh(self, queue=None): site = self # Have to poll for changes since there is no reliable way @@ -554,7 +550,7 @@ def refresh(self, queue=None): class Visitor(object): def visit_folder(self, folder): return folder.allow(**site.settings.FILTER) - + def visit_file(self, a_file): if not a_file.allow(**site.settings.FILTER): return @@ -573,12 +569,12 @@ def visit_file(self, a_file): "resource": resource, "exception": False }) - + visitor = Visitor() self.layout_folder.walk(visitor) self.content_folder.walk(visitor) self.media_folder.walk(visitor) - + nodes_to_remove = [] for node in self.walk(): if not node.folder.exists: @@ -588,10 +584,10 @@ def visit_file(self, a_file): "exception": False }) nodes_to_remove += [node] - + for node in nodes_to_remove: self.remove_node(node) - + for resource in self.walk_resources(): if not resource.file.exists: if queue: @@ -600,4 +596,4 @@ def visit_file(self, a_file): "resource":resource, "exception": False }) - resource.node.remove_resource(resource) \ No newline at end of file + resource.node.remove_resource(resource) From 405dbb16ddcc8299089fd0b1c4f8547af95c4e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 15:29:39 +0200 Subject: [PATCH 05/41] All this red hurts me eyes --- hydeengine/__init__.py | 175 +++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 95 deletions(-) diff --git a/hydeengine/__init__.py b/hydeengine/__init__.py index 9440739d..e76ec9a6 100644 --- a/hydeengine/__init__.py +++ b/hydeengine/__init__.py @@ -5,20 +5,14 @@ import imp import os import sys -import shutil -import thread -import threading import subprocess from collections import defaultdict -from datetime import datetime from Queue import Queue, Empty from threading import Thread, Event from django.conf import settings -from django.core.management import setup_environ from django.template import add_to_builtins -from django.template.loader import render_to_string from file_system import File, Folder @@ -27,7 +21,6 @@ from siteinfo import SiteInfo class _HydeDefaults: - GENERATE_CLEAN_URLS = False GENERATE_ABSOLUTE_FS_URLS = False LISTING_PAGE_NAMES = ['index', 'default', 'listing'] @@ -40,10 +33,10 @@ class _HydeDefaults: RST_SETTINGS_OVERRIDES = {} def setup_env(site_path): - """ - Initializes the Django Environment. NOOP if the environment is + """ + Initializes the Django Environment. NOOP if the environment is initialized already. - + """ # Don't do it twice if hasattr(settings, "CONTEXT"): @@ -60,11 +53,11 @@ def setup_env(site_path): print "Cannot Import Site Settings" print err raise ValueError( - "The given site_path [%s] does not contain a hyde site. " - "Give a valid path or run -init to create a new site." - % site_path + "The given site_path [%s] does not contain a hyde site. " + "Give a valid path or run -init to create a new site." + % site_path ) - + try: from django.conf import global_settings defaults = global_settings.__dict__ @@ -74,42 +67,40 @@ def setup_env(site_path): print "Site settings are not defined properly" print err raise ValueError( - "The given site_path [%s] has invalid settings. " - "Give a valid path or run -init to create a new site." - % site_path + "The given site_path [%s] has invalid settings. " + "Give a valid path or run -init to create a new site." + % site_path ) def validate_settings(): """ Ensures the site settings are properly configured. - + """ if settings.GENERATE_CLEAN_URLS and settings.GENERATE_ABSOLUTE_FS_URLS: raise ValueError( - "GENERATE_CLEAN_URLS and GENERATE_ABSOLUTE_FS_URLS cannot " - "be enabled at the same time." + "GENERATE_CLEAN_URLS and GENERATE_ABSOLUTE_FS_URLS cannot " + "be enabled at the same time." ) class Server(object): - """ + """ Initializes and runs a cherrypy webserver serving static files from the deploy folder - + """ - def __init__(self, site_path, address='localhost', port=8080): super(Server, self).__init__() self.site_path = os.path.abspath(os.path.expandvars( - os.path.expanduser(site_path))) + os.path.expanduser(site_path))) self.address = address self.port = port - + def serve(self, deploy_path, exit_listner): """ - Starts the cherrypy server at the given `deploy_path`. If exit_listner is + Starts the cherrypy server at the given `deploy_path`. If exit_listner is provided, calls it when the engine exits. - """ try: import cherrypy @@ -117,7 +108,7 @@ def serve(self, deploy_path, exit_listner): except ImportError: print "Cherry Py is required to run the webserver" raise - + setup_env(self.site_path) validate_settings() deploy_folder = Folder( @@ -137,13 +128,13 @@ def serve(self, deploy_path, exit_listner): filename = page.target_file.path url = page.url.strip('/') url_file_mapping[url] = filename - + class WebRoot: @cherrypy.expose def index(self): page = site.listing_page return serve_file(deploy_folder.child(page.name)) - + if settings.GENERATE_CLEAN_URLS: @cherrypy.expose def default(self, *args): @@ -160,20 +151,20 @@ def default(self, *args): return serve_file(file) # try each filename in LISTING_PAGE_NAMES setting for listing_name in settings.LISTING_PAGE_NAMES: - file = os.path.join(deploy_folder.path, + file = os.path.join(deploy_folder.path, os.sep.join(args), listing_name + '.html') if os.path.isfile(file): return serve_file(file) # failing that, search for a non-listing page - file = os.path.join(deploy_folder.path, + file = os.path.join(deploy_folder.path, os.sep.join(args[:-1]), args[-1] + '.html') if os.path.isfile(file): return serve_file(file) # failing that, page not found raise cherrypy.NotFound - + cherrypy.config.update({'environment': 'production', 'log.error_file': 'site.log', 'log.screen': True, @@ -186,7 +177,7 @@ def default(self, *args): media_web_path = '/%s/media' % settings.SITE_ROOT.strip('/') # if SITE_ROOT is /, we end up with //media media_web_path = media_web_path.replace('//', '/') - + conf = {media_web_path: { 'tools.staticdir.dir':os.path.join(deploy_folder.path, settings.SITE_ROOT.strip('/'), @@ -202,24 +193,24 @@ def default(self, *args): if exit_listner: cherrypy.engine.subscribe('exit', exit_listner) cherrypy.engine.start() - + @property def alive(self): """ Checks if the webserver is alive. - + """ import cherrypy return cherrypy.engine.state == cherrypy.engine.states.STARTED - + def block(self): """ Blocks and waits for the engine to exit. - + """ import cherrypy cherrypy.engine.block() - + def quit(self): import cherrypy cherrypy.engine.exit() @@ -227,31 +218,29 @@ def quit(self): class Generator(object): """ Generates a deployable website from the templates. Can monitor the site for - - """ def __init__(self, site_path): super(Generator, self).__init__() self.site_path = os.path.abspath(os.path.expandvars( os.path.expanduser(site_path))) self.regenerate_request = Event() - self.regeneration_complete = Event() + self.regeneration_complete = Event() self.queue = Queue() self.watcher = Thread(target=self.__watch__) self.regenerator = Thread(target=self.__regenerate__) self.processor = Processor(settings) - self.quitting = False + self.quitting = False - def notify(self, title, message): + def notify(self, title, message): if hasattr(settings, "GROWL") and settings.GROWL and File(settings.GROWL).exists: try: - subprocess.call([settings.GROWL, "-n", "Hyde", "-t", title, "-m", message]) + subprocess.call([settings.GROWL, "-n", "Hyde", "-t", title, "-m", message]) except: - pass + pass def pre_process(self, node): self.processor.pre_process(node) - + def process(self, item, change="Added"): if change in ("Added", "Modified"): settings.CONTEXT['node'] = item.node @@ -259,18 +248,17 @@ def process(self, item, change="Added"): return self.processor.process(item) elif change in ("Deleted", "NodeRemoved"): return self.processor.remove(item) - - + def build_siteinfo(self, deploy_path=None): tmp_folder = Folder(settings.TMP_DIR) deploy_folder = Folder( (deploy_path, settings.DEPLOY_DIR) [not deploy_path]) - + if deploy_folder.exists and settings.BACKUP: backup_folder = Folder(settings.BACKUPS_DIR).make() deploy_folder.backup(backup_folder) - + tmp_folder.delete() tmp_folder.make() settings.DEPLOY_DIR = deploy_folder.path @@ -280,52 +268,52 @@ def build_siteinfo(self, deploy_path=None): add_to_builtins('hydeengine.templatetags.aym') add_to_builtins('hydeengine.templatetags.typogrify') self.create_siteinfo() - + def create_siteinfo(self): self.siteinfo = SiteInfo(settings, self.site_path) self.siteinfo.refresh() settings.CONTEXT['site'] = self.siteinfo.content_node - + def post_process(self, node): self.processor.post_process(node) - - def process_all(self): - self.notify(self.siteinfo.name, "Website Generation Started") + + def process_all(self): + self.notify(self.siteinfo.name, "Website Generation Started") try: self.pre_process(self.siteinfo) for resource in self.siteinfo.walk_resources(): self.process(resource) self.complete_generation() - except: + except: print >> sys.stderr, "Generation Failed" print >> sys.stderr, sys.exc_info() self.notify(self.siteinfo.name, "Generation Failed") return - self.notify(self.siteinfo.name, "Generation Complete") - + self.notify(self.siteinfo.name, "Generation Complete") + def complete_generation(self): self.post_process(self.siteinfo) self.siteinfo.target_folder.copy_contents_of( self.siteinfo.temp_folder, incremental=True) if(hasattr(settings, "post_deploy")): - settings.post_deploy() - + settings.post_deploy() + def __regenerate__(self): pending = False while True: try: - if self.quit_event.isSet(): + if self.quit_event.isSet(): self.notify(self.siteinfo.name, "Exiting Regenerator") print "Exiting regenerator..." break - + # Wait for the regeneration event to be set self.regenerate_request.wait(5) - + # Wait until there are no more requests # Got a request, we dont want to process it # immedietely since other changes may be under way. - + # Another request coming in renews the initil request. # When there are no more requests, we go are and process # the event. @@ -337,68 +325,67 @@ def __regenerate__(self): self.regeneration_complete.clear() pending = True self.regenerate_request.clear() - except: + except: print >> sys.stderr, "Error during regeneration" print >> sys.stderr, sys.exc_info() self.notify(self.siteinfo.name, "Error during regeneration") self.regeneration_complete.set() self.regenerate_request.clear() pending = False - + def __watch__(self): regenerating = False while True: try: if self.quit_event.isSet(): - print "Exiting watcher..." - self.notify(self.siteinfo.name, "Exiting Watcher") + print "Exiting watcher..." + self.notify(self.siteinfo.name, "Exiting Watcher") break try: pending = self.queue.get(timeout=10) except Empty: continue - + self.queue.task_done() if pending.setdefault("exception", False): self.quit_event.set() - print "Exiting watcher" - self.notify(self.siteinfo.name, "Exiting Watcher") + print "Exiting watcher" + self.notify(self.siteinfo.name, "Exiting Watcher") break - + if 'resource' in pending: resource = pending['resource'] - + if self.regeneration_complete.isSet(): - regenerating = False - + regenerating = False + if pending['change'] == "Deleted": self.process(resource, pending['change']) elif pending['change'] == "NodeRemoved": self.process(pending['node'], pending['change']) - + if (pending['change'] in ("Deleted", "NodeRemoved") or resource.is_layout or regenerating): regenerating = True self.regenerate_request.set() continue - - self.notify(self.siteinfo.name, "Processing " + resource.name) + + self.notify(self.siteinfo.name, "Processing " + resource.name) if self.process(resource, pending['change']): - self.complete_generation() - self.notify(self.siteinfo.name, "Completed processing " + resource.name) - except: + self.complete_generation() + self.notify(self.siteinfo.name, "Completed processing " + resource.name) + except: print >> sys.stderr, "Error during regeneration" - print >> sys.stderr, sys.exc_info() + print >> sys.stderr, sys.exc_info() self.notify(self.siteinfo.name, "Error during regeneration") self.regeneration_complete.set() self.regenerate_request.clear() regenerating = False - - def generate(self, deploy_path=None, - keep_watching=False, + def generate(self, deploy_path=None, + keep_watching=False, exit_listner=None): - + self.exit_listner = exit_listner self.quit_event = Event() setup_env(self.site_path) @@ -418,7 +405,6 @@ def generate(self, deploy_path=None, self.quit() raise - def block(self): try: while self.watcher.isAlive(): @@ -431,13 +417,13 @@ def block(self): except: self.quit() raise - + def quit(self): if self.quitting: return self.quitting = True - print "Shutting down..." - self.notify(self.siteinfo.name, "Shutting Down") + print "Shutting down..." + self.notify(self.siteinfo.name, "Shutting Down") self.siteinfo.dont_monitor() self.quit_event.set() if self.exit_listner: @@ -445,21 +431,20 @@ def quit(self): class Initializer(object): - def __init__(self, site_path): super(Initializer, self).__init__() self.site_path = Folder(site_path) - + def initialize(self, root, template=None, force=False): if not template: template = "default" root_folder = Folder(root) template_dir = root_folder.child_folder("templates", template) - + if not template_dir.exists: raise ValueError( "Cannot find the specified template[%s]." % template_dir) - + if self.site_path.exists: files = os.listdir(self.site_path.path) PathUtil.filter_hidden_inplace(files) From acba9019fee8ce2b0e88c4112c8bb9245d526891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 15:33:01 +0200 Subject: [PATCH 06/41] Retain exception message to give an idea why it fails --- hydeengine/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/hydeengine/__init__.py b/hydeengine/__init__.py index e76ec9a6..4e6a9dc2 100644 --- a/hydeengine/__init__.py +++ b/hydeengine/__init__.py @@ -41,6 +41,13 @@ def setup_env(site_path): # Don't do it twice if hasattr(settings, "CONTEXT"): return + + settings_file = os.path.join(site_path, "settings.py") + if not os.path.exists(settings_file): + print "No Site Settings File Found" + raise ValueError("The given site_path [%s] does not contain a hyde site. " + "Give a valid path or run -init to create a new site." % (site_path,)) + try: hyde_site_settings = imp.load_source("hyde_site_settings", os.path.join(site_path,"settings.py")) @@ -50,13 +57,9 @@ def setup_env(site_path): print err exit() except Exception, err: - print "Cannot Import Site Settings" - print err - raise ValueError( - "The given site_path [%s] does not contain a hyde site. " - "Give a valid path or run -init to create a new site." - % site_path - ) + print "Failed to import Site Settings" + print "The settings file [%s] contains errors." % (settings_file,) + raise try: from django.conf import global_settings From 9cdb0b27997d9ef252748b063fe97af8e9da9a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 17:28:18 +0200 Subject: [PATCH 07/41] Added information about smartypants.py --- README.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index ecaf00dd..569764cd 100644 --- a/README.markdown +++ b/README.markdown @@ -327,9 +327,10 @@ Render Article renders the html content bracketed by the `{%article%}` tag from ### Typogrify -To enable Typogrify, use ``{% filter typogrify %}`` in your code. Typogrify is "a collection of Django template filters that help prettify your web typography by preventing ugly quotes and widows", according to the [project web site][typogrify_site]. It is automatically enabled in the default template. +To enable Typogrify, use ``{% filter typogrify %}`` in your code. Typogrify is "a collection of Django template filters that help prettify your web typography by preventing ugly quotes and widows", according to the [project web site][typogrify_site]. It is automatically enabled in the default template. Some features require you to have [smartypants] installed. [typogrify_site]:http://code.google.com/p/typogrify/ +[smartypants]:http://web.chad.org/projects/smartypants.py/ ## Base Templates From 9a46043eca39f4ae816a6b3d51df1b50b9f04674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 17:58:28 +0200 Subject: [PATCH 08/41] Identation and unused imports --- hydeengine/site_pre_processors.py | 40 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/hydeengine/site_pre_processors.py b/hydeengine/site_pre_processors.py index 50d86f61..4a64c7b9 100644 --- a/hydeengine/site_pre_processors.py +++ b/hydeengine/site_pre_processors.py @@ -1,18 +1,18 @@ +# -*- coding: utf-8 -*- + from __future__ import with_statement -import sys -import os + import codecs -import urllib import operator -from hydeengine.siteinfo import ContentNode +import os +import urllib + from django.conf import settings from django.template.loader import render_to_string -from hydeengine.file_system import Folder -from siteinfo import SiteNode """ PRE PROCESSORS - + Can be launched before the parsing of each templates and after the loading of site info. """ @@ -22,7 +22,7 @@ def __init__(self): self.posts = [] self.feed_url = None self.archive_url = None - + @property def posts(self): return self.posts @@ -35,9 +35,8 @@ def feed_url(self): def archive_url(self): return self.archive_url - -class CategoriesManager: +class CategoriesManager: """ Fetch the category(ies) from every post under the given node and creates a reference on them in CONTEXT and the node. @@ -45,17 +44,17 @@ class CategoriesManager: @staticmethod def process(folder, params): context = settings.CONTEXT - site = context['site'] + site = context['site'] node = params['node'] - categories = {} + categories = {} for post in node.walk_pages(): if hasattr(post, 'categories') and post.categories != None: for category in post.categories: if categories.has_key(category) is False: categories[category] = Category() - categories[category].posts.append(post) + categories[category].posts.append(post) categories[category].posts.sort(key=operator.attrgetter("created"), reverse=True) - context['categories'] = categories + context['categories'] = categories node.categories = categories class CategoriesArchiveGenerator: @@ -84,16 +83,15 @@ def process(folder, params): raise ValueError("No template reference in CategoriesArchiveGenerator's settings") for name, category in categories.iteritems(): - archive_resource = "%s.html" % urllib.quote_plus(name) + archive_resource = urllib.quote_plus(name) + '.html' category.archive_url = "/%s/%s" % (folder.name, "%s/%s" % (relative_folder, archive_resource)) - + node.categories = categories for category_name, category_obj in categories.iteritems(): - name = urllib.quote_plus(category_name) + archive_resource = urllib.quote_plus(category_name) + '.html' posts = category_obj.posts - archive_resource = "%s.html" % (name) - settings.CONTEXT.update({'category':category_name, + settings.CONTEXT.update({'category':category_name, 'posts': posts, 'categories': categories}) output = render_to_string(template, settings.CONTEXT) @@ -101,10 +99,10 @@ def process(folder, params): archive_resource), \ "w", "utf-8") as file: file.write(output) - + class NodeInjector(object): """ - Finds the node that represents the given path and injects it with the given + Finds the node that represents the given path and injects it with the given variable name into all the posts contained in the current node. """ @staticmethod From 4f3f4dd7fdc3f014a41a8cd025055d683be8000d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 20:08:54 +0200 Subject: [PATCH 09/41] Fixed broken URL:s in the category archive --- hydeengine/site_pre_processors.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hydeengine/site_pre_processors.py b/hydeengine/site_pre_processors.py index 4a64c7b9..022d341c 100644 --- a/hydeengine/site_pre_processors.py +++ b/hydeengine/site_pre_processors.py @@ -10,6 +10,9 @@ from django.conf import settings from django.template.loader import render_to_string +import hydeengine.url as url + + """ PRE PROCESSORS @@ -85,6 +88,11 @@ def process(folder, params): for name, category in categories.iteritems(): archive_resource = urllib.quote_plus(name) + '.html' category.archive_url = "/%s/%s" % (folder.name, "%s/%s" % (relative_folder, archive_resource)) + # TODO break out checks from siteinfo.py + if settings.GENERATE_CLEAN_URLS: + category.archive_url = url.clean_url(category.archive_url) + if settings.APPEND_SLASH: + category.archive_url += '/' node.categories = categories From fcf13987ba3a4e017db664f5e6e34f59e386d487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 20:20:54 +0200 Subject: [PATCH 10/41] Formatting --- hydeengine/site_pre_processors.py | 78 ++++++++++++++++--------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/hydeengine/site_pre_processors.py b/hydeengine/site_pre_processors.py index 022d341c..b5c4332a 100644 --- a/hydeengine/site_pre_processors.py +++ b/hydeengine/site_pre_processors.py @@ -1,4 +1,10 @@ # -*- coding: utf-8 -*- +""" +PRE PROCESSORS + +Can be launched before the parsing of each templates and +after the loading of site info. +""" from __future__ import with_statement @@ -13,13 +19,6 @@ import hydeengine.url as url -""" - PRE PROCESSORS - - Can be launched before the parsing of each templates and - after the loading of site info. -""" - class Category: def __init__(self): self.posts = [] @@ -40,7 +39,8 @@ def archive_url(self): class CategoriesManager: - """ + """Category Manager + Fetch the category(ies) from every post under the given node and creates a reference on them in CONTEXT and the node. """ @@ -51,67 +51,71 @@ def process(folder, params): node = params['node'] categories = {} for post in node.walk_pages(): - if hasattr(post, 'categories') and post.categories != None: - for category in post.categories: - if categories.has_key(category) is False: - categories[category] = Category() - categories[category].posts.append(post) - categories[category].posts.sort(key=operator.attrgetter("created"), reverse=True) + if not getattr(post, 'categories', None): + continue + + for category_name in post.categories: + category = categories.setdefault(category_name, Category()) + category.posts.append(post) + category.posts.sort(key=operator.attrgetter('created'), + reverse=True) + context['categories'] = categories node.categories = categories + class CategoriesArchiveGenerator: @staticmethod def process(folder, params): node = params['node'] - if hasattr(node, 'categories'): - categories = node.categories - else: + categories = getattr(node, 'categories', None) + if categories is None: raise ValueError("No categories member on node %s" % (node)) - #: defining the output folder - customisable + # define output folder (customizable) relative_folder = output_folder = 'archives' - if 'output_folder' in params and params['output_folder'] is not None \ - and len(params['output_folder']) > 0: + if params.get('output_folder'): relative_folder = output_folder = params['output_folder'] + output_folder = os.path.join(settings.TMP_DIR, folder.name, output_folder) if not os.path.isdir(output_folder): os.makedirs(output_folder) - #: fetching default archive template - template = None - if 'template' in params: + # fetching default archive template + try: template = os.path.join(settings.LAYOUT_DIR, params['template']) - else: + except KeyError: raise ValueError("No template reference in CategoriesArchiveGenerator's settings") + # setup urls for all categories for name, category in categories.iteritems(): archive_resource = urllib.quote_plus(name) + '.html' category.archive_url = "/%s/%s" % (folder.name, "%s/%s" % (relative_folder, archive_resource)) + # TODO break out checks from siteinfo.py if settings.GENERATE_CLEAN_URLS: category.archive_url = url.clean_url(category.archive_url) if settings.APPEND_SLASH: category.archive_url += '/' - node.categories = categories - + # write all files for category_name, category_obj in categories.iteritems(): + settings.CONTEXT.update({'category': category_name, + 'posts': category_obj.posts, + 'categories': categories}) + archive_resource = urllib.quote_plus(category_name) + '.html' - posts = category_obj.posts - settings.CONTEXT.update({'category':category_name, - 'posts': posts, - 'categories': categories}) output = render_to_string(template, settings.CONTEXT) - with codecs.open(os.path.join(output_folder, \ - archive_resource), \ - "w", "utf-8") as file: + output_filename = os.path.join(output_folder, archive_resource) + with codecs.open(output_filename, "w", "utf-8") as file: file.write(output) + class NodeInjector(object): - """ - Finds the node that represents the given path and injects it with the given - variable name into all the posts contained in the current node. + """Node Injector + + Finds the node that represents the given path and injects it with the given + variable name into all the posts contained in the current node. """ @staticmethod def process(folder, params): @@ -124,10 +128,10 @@ def process(folder, params): params['injections'] = { varName: path } except KeyError: pass + for varName, path in params['injections'].iteritems(): nodeFromPathFragment = site.find_node(site.folder.parent.child_folder(path)) if not nodeFromPathFragment: continue for post in node.walk_pages(): setattr(post, varName, nodeFromPathFragment) - From 7a308a260f6363a00695ad8e59ccb6a8cad7c614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 20:55:59 +0200 Subject: [PATCH 11/41] Trailing spaces --- hyde.py | 62 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/hyde.py b/hyde.py index 63b8dd57..17121ec0 100755 --- a/hyde.py +++ b/hyde.py @@ -1,84 +1,81 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + +import optparse import os import sys -import threading -from optparse import OptionParser from hydeengine import Generator, Initializer, Server -#import cProfile - PROG_ROOT = os.path.dirname(os.path.realpath( __file__ )) def main(argv): - - parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 0.4") - parser.add_option("-s", "--sitepath", - dest = "site_path", + parser = optparse.OptionParser(usage="%prog [-f] [-q]", version="%prog 0.4") + parser.add_option("-s", "--sitepath", + dest = "site_path", help = "Change the path of the site folder.") - parser.add_option("-i", "--init", action = 'store_true', - dest = "init", default = False, + parser.add_option("-i", "--init", action = 'store_true', + dest = "init", default = False, help = "Create a new hyde site.") - parser.add_option("-f", "--force", action = 'store_true', + parser.add_option("-f", "--force", action = 'store_true', dest = "force_init", default = False, help = "") - parser.add_option("-t", "--template", - dest = "template", + parser.add_option("-t", "--template", + dest = "template", help = "Choose which template you want to use.") parser.add_option("-g", "--generate", action = "store_true", - dest = "generate", default = False, + dest = "generate", default = False, help = "Generate the source for your hyde site.") parser.add_option("-k", "--keep_watching", action = "store_true", dest = "keep_watching", default = False, - help = "Start monitoring the source folder for changes.") - parser.add_option("-d", "--deploy_to", - dest = "deploy_to", + help = "Start monitoring the source folder for changes.") + parser.add_option("-d", "--deploy_to", + dest = "deploy_to", help = "Change the path of the deploy folder.") parser.add_option("-w", "--webserve", action = "store_true", - dest = "webserve", default = False, + dest = "webserve", default = False, help = "Start an instance of the CherryPy webserver.") parser.add_option("-p", "--port", - dest = "port", default=8080, + dest = "port", default=8080, type='int', help = "Port webserver should listen on (8080).") parser.add_option("-a", "--address", dest = "address", default='localhost', help = "Address webserver should listen on (localhost).") - + (options, args) = parser.parse_args() - + if len(args): parser.error("Unexpected arguments encountered.") - + if not options.site_path: options.site_path = os.getcwdu() if options.deploy_to: options.deploy_to = os.path.abspath(options.deploy_to) - + if options.init: initializer = Initializer(options.site_path) initializer.initialize(PROG_ROOT, options.template, options.force_init) generator = None - server = None - + server = None + def quit(*args, **kwargs): if server and server.alive: server.quit() if generator: generator.quit() - if options.generate: generator = Generator(options.site_path) - generator.generate(options.deploy_to, options.keep_watching, quit) + generator.generate(options.deploy_to, options.keep_watching, quit) if options.webserve: server = Server(options.site_path, address=options.address, port=options.port) server.serve(options.deploy_to, quit) - - if ((options.generate and options.keep_watching) + + if ((options.generate and options.keep_watching) or options.webserve): try: @@ -92,13 +89,14 @@ def quit(*args, **kwargs): except: print sys.exc_info() quit() - + if argv == []: print parser.format_option_help() - - + + if __name__ == "__main__": main(sys.argv[1:]) + # import cProfile # cProfile.run('main(sys.argv[1:])', filename='hyde.cprof') # import pstats # stats = pstats.Stats('hyde.cprof') From 595ea455586f1608445d88226e7817f7f82e4258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Thu, 8 Apr 2010 23:50:52 +0200 Subject: [PATCH 12/41] Experimental and unfinished support for gevent --- hyde.py | 19 +++++-- hydeengine/__init__.py | 121 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/hyde.py b/hyde.py index 17121ec0..57bdc0b1 100755 --- a/hyde.py +++ b/hyde.py @@ -5,7 +5,7 @@ import os import sys -from hydeengine import Generator, Initializer, Server +import hydeengine PROG_ROOT = os.path.dirname(os.path.realpath( __file__ )) @@ -33,7 +33,8 @@ def main(argv): help = "Change the path of the deploy folder.") parser.add_option("-w", "--webserve", action = "store_true", dest = "webserve", default = False, - help = "Start an instance of the CherryPy webserver.") + help = "Start serving using a webserver.") + parser.add_option("--web-flavor", metavar='NAME', default="CherryPy", help="Specify the flavor of the server (CherryPy, gevent)") parser.add_option("-p", "--port", dest = "port", default=8080, type='int', @@ -47,6 +48,16 @@ def main(argv): if len(args): parser.error("Unexpected arguments encountered.") + if options.webserve: + servers = {'cherrypy': hydeengine.Server, + 'gevent': hydeengine.GeventServer} + + Server = servers.get(options.web_flavor.lower()) + if not Server: + parser.error('Invalid web service flavor "%s" (valid: %s)' % \ + (options.web_flavor, ', '.join(servers.keys()))) + + if not options.site_path: options.site_path = os.getcwdu() @@ -54,7 +65,7 @@ def main(argv): options.deploy_to = os.path.abspath(options.deploy_to) if options.init: - initializer = Initializer(options.site_path) + initializer = hydeengine.Initializer(options.site_path) initializer.initialize(PROG_ROOT, options.template, options.force_init) @@ -68,7 +79,7 @@ def quit(*args, **kwargs): generator.quit() if options.generate: - generator = Generator(options.site_path) + generator = hydeengine.Generator(options.site_path) generator.generate(options.deploy_to, options.keep_watching, quit) if options.webserve: diff --git a/hydeengine/__init__.py b/hydeengine/__init__.py index 4e6a9dc2..ec6881b3 100644 --- a/hydeengine/__init__.py +++ b/hydeengine/__init__.py @@ -3,9 +3,11 @@ """ import imp +import mimetypes import os import sys import subprocess +import urllib from collections import defaultdict from Queue import Queue, Empty @@ -19,6 +21,7 @@ from path_util import PathUtil from processor import Processor from siteinfo import SiteInfo +from url import clean_url class _HydeDefaults: GENERATE_CLEAN_URLS = False @@ -141,6 +144,10 @@ def index(self): if settings.GENERATE_CLEAN_URLS: @cherrypy.expose def default(self, *args): + # TODO notice that this method has security flaws + # TODO for every url_file_mapping not found, we will + # save that in url_file_mapping. not optimal. + # first, see if the url is in the url_file_mapping # dictionary file = url_file_mapping[os.sep.join(args)] @@ -218,6 +225,120 @@ def quit(self): import cherrypy cherrypy.engine.exit() + +# TODO split into a generic wsgi handler, combine it with the current server +def GeventServer(*args, **kwargs): + import gevent.greenlet + import gevent.wsgi + + class GeventServerWrapper(Server): + STOP_TIMEOUT = 10 + CHUNK_SIZE = 4096 + FALLBACK_CONTENT_TYPE = 'application/octet-stream' + + def __init__(self, *args, **kwargs): + super(GeventServerWrapper, self).__init__(*args, **kwargs) + + addr = (self.address, self.port) + self.server = gevent.wsgi.WSGIServer(addr, self.request_handler) + + self.paths = {} + self.root = None + + # FIXME + mimetypes.init() + + def serve(self, deploy_path, exit_listener): + setup_env(self.site_path) + validate_settings() + + self.root = deploy_path or settings.DEPLOY_DIR + + if not 'site' in settings.CONTEXT: + generator = Generator(self.site_path) + generator.create_siteinfo() + + def add_url(url, path, listing): + # TODO fix this ugly url hack + if settings.GENERATE_CLEAN_URLS and '.' in url: + url = clean_url(url) + + # strip names from listing pages + head, tail = os.path.split(url) + parent = os.path.basename(head) + name = os.path.splitext(tail)[0] + if listing and (name == parent or + name in settings.LISTING_PAGE_NAMES): + url = os.path.dirname(url) + self.paths.setdefault(url, path) + + # gather all urls (only works if you generate it) + """ + site = settings.CONTEXT['site'] + for page in site.walk_pages(): + url = page.url.strip('/') + add_url(url, page.target_file.path, page.listing) + """ + + # register all other static files + # FIXME we just register all files, we should do this properly + # FIXME we're relying on os.sep is /, fine for now + for dirpath, dirnames, filenames in os.walk(self.root): + path = dirpath[len(self.root)+1:] + for filename in filenames: + url = os.path.join(path, filename) + # do we know if it's listing or not? + add_url(url, os.path.join(dirpath, filename), listing=True) + + import pprint + print 'I currently serve: \n', pprint.pformat(sorted(self.paths.items())) + + self.server.start() + print 'Started %s on %s:%s' % (self.server.base_env['SERVER_SOFTWARE'], + self.server.server_host, + self.server.server_port) + + + def block(self): + # XXX is there a nicer way of doing this? + try: + self.server._stopped_event.wait() + finally: + gevent.greenlet.Greenlet.spawn(self.server.stop, + timeout=self.STOP_TIMEOUT).join() + + def quit(self): + self.server.stop(timeout=self.STOP_TIMEOUT) + + def request_handler(self, env, start_response): + # extract the real requested file + path = os.path.abspath(urllib.unquote_plus(env['PATH_INFO'])) + + # check if file exists + filename = self.paths.get(path.strip('/')) + if not filename or not os.path.exists(filename): + start_response('404 Not Found', [('Content-Type', 'text/plain')]) + yield 'Not Found\n' + return + + # TODO how do we easiest detect mime types? + content_type, encoding = mimetypes.guess_type(filename) + if not content_type: + content_type = self.FALLBACK_CONTENT_TYPE + start_response('200 OK', [('Content-Type', content_type)]) + + # TODO make this async? + f = file(filename, 'rb') + try: + chunk = f.read(self.CHUNK_SIZE) + while chunk: + yield chunk + chunk = f.read(self.CHUNK_SIZE) + finally: + f.close() + + return GeventServerWrapper(*args, **kwargs) + class Generator(object): """ Generates a deployable website from the templates. Can monitor the site for From cb60cca5eaa018f4d491f7fe53557906cc5a7e78 Mon Sep 17 00:00:00 2001 From: wamberg Date: Sat, 24 Apr 2010 12:22:55 -0400 Subject: [PATCH 13/41] Removed the \n for the marker_start because it was causing problems with spaceless HTML. --- hydeengine/templatetags/hydetags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydeengine/templatetags/hydetags.py b/hydeengine/templatetags/hydetags.py index aec60ed5..f4012d2b 100644 --- a/hydeengine/templatetags/hydetags.py +++ b/hydeengine/templatetags/hydetags.py @@ -16,7 +16,7 @@ from hydeengine.file_system import Folder -marker_start = "\n" +marker_start = "" marker_end = "" register = Library() From effafa7245e8f5c7a8a81e776a05a3bd564ee13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Persson?= Date: Fri, 14 May 2010 11:49:27 +0200 Subject: [PATCH 14/41] Added media resource with content pairing and metadata extraction. --- hydeengine/__init__.py | 4 +- hydeengine/media_processors.py | 52 +++++---- hydeengine/site_pre_processors.py | 175 ++++++++++++++++++++++++++++ hydeengine/siteinfo.py | 4 +- hydeengine/templatetags/hydetags.py | 125 ++++++++++++-------- 5 files changed, 286 insertions(+), 74 deletions(-) diff --git a/hydeengine/__init__.py b/hydeengine/__init__.py index ec6881b3..5e3522aa 100644 --- a/hydeengine/__init__.py +++ b/hydeengine/__init__.py @@ -291,7 +291,7 @@ def add_url(url, path, listing): add_url(url, os.path.join(dirpath, filename), listing=True) import pprint - print 'I currently serve: \n', pprint.pformat(sorted(self.paths.items())) + #print 'I currently serve: \n', pprint.pformat(sorted(self.paths.items())) self.server.start() print 'Started %s on %s:%s' % (self.server.base_env['SERVER_SOFTWARE'], @@ -408,7 +408,7 @@ def process_all(self): for resource in self.siteinfo.walk_resources(): self.process(resource) self.complete_generation() - except: + except Exception, e: print >> sys.stderr, "Generation Failed" print >> sys.stderr, sys.exc_info() self.notify(self.siteinfo.name, "Generation Failed") diff --git a/hydeengine/media_processors.py b/hydeengine/media_processors.py index 1dc4804b..ea020994 100644 --- a/hydeengine/media_processors.py +++ b/hydeengine/media_processors.py @@ -1,8 +1,10 @@ -import os, commands, sys -import fnmatch +import os +import commands +import re +import sys from django.template.loader import render_to_string from django.conf import settings -from file_system import File +from file_system import File from subprocess import check_call, CalledProcessError class TemplateProcessor: @@ -15,11 +17,11 @@ def process(resource): print >> sys.stderr, \ "***********************\nError while rendering page %s\n***********************" % \ resource.url - raise - + raise + ## aym-cms code refactored into processors. -class CleverCSS: +class CleverCSS: @staticmethod def process(resource): import clevercss @@ -36,7 +38,7 @@ def process(resource): raise ValueError("HSS Processor cannot be found at [%s]" % hss) status, output = commands.getstatusoutput( u"%s %s -output %s/" % (hss, resource.source_file.path, out_file.parent.path)) - if status > 0: + if status > 0: print output return None resource.source_file.delete() @@ -52,11 +54,11 @@ def process(resource): raise ValueError("SASS Processor cannot be found at [%s]" % sass) status, output = commands.getstatusoutput( u"%s %s %s" % (sass, resource.source_file.path, out_file)) - if status > 0: + if status > 0: print output return None - resource.source_file.delete() - + resource.source_file.delete() + class LessCSS: @staticmethod def process(resource): @@ -67,34 +69,34 @@ def process(resource): if not less or not os.path.exists(less): raise ValueError("Less CSS Processor cannot be found at [%s]" % less) try: - check_call([less, resource.source_file.path, out_file.path]) + check_call([less, resource.source_file.path, out_file.path]) except CalledProcessError, e: print 'Syntax Error when calling less' raise - else: - resource.source_file.delete() - if not out_file.exists: + else: + resource.source_file.delete() + if not out_file.exists: print 'Error Occurred when processing with Less' class YUICompressor: @staticmethod - def process(resource): + def process(resource): if settings.YUI_COMPRESSOR == None: return - compress = settings.YUI_COMPRESSOR + compress = settings.YUI_COMPRESSOR if not os.path.exists(compress): compress = os.path.join( os.path.dirname( os.path.abspath(__file__)), "..", compress) - + if not compress or not os.path.exists(compress): raise ValueError( "YUI Compressor cannot be found at [%s]" % compress) - + tmp_file = File(resource.source_file.path + ".z-tmp") status, output = commands.getstatusoutput( u"java -jar %s %s > %s" % (compress, resource.source_file.path, tmp_file.path)) - if status > 0: + if status > 0: print output else: resource.source_file.delete() @@ -108,11 +110,11 @@ def process(resource): compress = os.path.join( os.path.dirname( os.path.abspath(__file__)), "..", compress) - + if not compress or not os.path.exists(compress): raise ValueError( "Closure Compiler cannot be found at [%s]" % compress) - + tmp_file = File(resource.source_file.path + ".z-tmp") status, output = commands.getstatusoutput( u"java -jar %s --js=%s --js_output_file=%s" % (compress, resource.source_file.path, tmp_file.path)) @@ -126,18 +128,18 @@ class Thumbnail: @staticmethod def process(resource): from PIL import Image - + i = Image.open(resource.source_file.path) i.thumbnail( (settings.THUMBNAIL_MAX_WIDTH, settings.THUMBNAIL_MAX_HEIGHT), Image.ANTIALIAS ) - + orig_path, _, orig_extension = resource.source_file.path.rpartition('.') if "THUMBNAIL_FILENAME_POSTFIX" in dir(settings): postfix = settings.THUMBNAIL_FILENAME_POSTFIX else: postfix = "-thumb" thumb_path = "%s%s.%s" % (orig_path, postfix, orig_extension) - - i.save(thumb_path) \ No newline at end of file + + i.save(thumb_path) diff --git a/hydeengine/site_pre_processors.py b/hydeengine/site_pre_processors.py index b5c4332a..8a7ce323 100644 --- a/hydeengine/site_pre_processors.py +++ b/hydeengine/site_pre_processors.py @@ -135,3 +135,178 @@ def process(folder, params): continue for post in node.walk_pages(): setattr(post, varName, nodeFromPathFragment) + + +class ResourcePairer(object): + @staticmethod + def process(folder, params): + site = settings.CONTEXT['site'] + node = params['node'] + + # fetch or setup content and pairs + content_name = params.get('name', 'media_content') + content = site.__dict__.setdefault(content_name, {}) + + variable = params.get('variable', 'media') + rvariable = params.get('recursive_variable', 'media') + recursive = params.get('recursive', True) + + if node.type == 'content': + while content: + path, node = content.popitem() + setattr(node, variable, []) + setattr(node, rvariable, []) + content.update(dict([(page.url, page.node) for page in node.walk_pages()])) + elif node.type == 'media': + for resource in node.walk_resources(): + # strip top directories (eg. media/images/) to be able to match + path = resource.node.fragment + path = path[path.index('/', 1):] + + # append the resource for all matching directories + key = variable + node = content.get(path) + while node is not None: + resources = node.__dict__.setdefault(key, []) + resources.append(resource) + resources.sort(key=lambda x: x.file.last_modified, reverse=True) + + key = rvariable + node = recursive and node.parent or None + + +class RecursiveAttributes(object): + """Adds recursivity base on attributes with dots""" + def __init__(self): + self._setup = False + + def __setattr__(self, key, value): + parts = key.split('.', 1) + if len(parts) == 1: + self.__dict__[key] = value + else: + target = getattr(self, parts[0], None) + if target is None: + target = RecursiveAttributes() + self.__dict__[parts[0]] = target + + setattr(target, parts[1], value) + + def __getattr__(self, key): + parts = key.split('.', 1) + try: + if len(parts) == 1: + return self.__dict__[key] + else: + return getattr(self.__dict__[parts[0]], parts[1]) + except KeyError: + raise AttributeError('Unknown attribute: %s' % (key,)) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(['%s=%r' % (k, v) for k, v in self.__dict__.items() if not k.startswith('_')])) + + +class ImageMetadata(object): + """Image metadata retriever based on PIL""" + DEFAULT_MAPPING = {'iptc.description': 'caption'} + + # A subset of the IPTC Information Interchange Model + # mapping extracted from: + # http://www.iptc.org/std/IIM/ + IIM_MAPPING = {(2, 90): 'city', + (2, 101): 'country', + (2, 100): 'country_code', + (2, 120): 'description', + (2, 25): 'keywords', + (2, 116): 'copyright', + (2, 105): 'headline', + (2, 5): 'title', + (2, 15): 'category', + (2, 20): 'supplemental_category'} + + @staticmethod + def process(folder, params): + import PIL.ExifTags + import PIL.Image + import PIL.IptcImagePlugin + + class AttributeMapper(RecursiveAttributes): + def __init__(self, values, mappings): + if values is None: + return + + for mapping in mappings: + for tag, name in mapping.iteritems(): + value = values.get(tag) + if value is not None: + setattr(self, name, value) + + # setup the mapping + mapping = ImageMetadata.DEFAULT_MAPPING.copy() + mapping.update(params.get('mapping', {})) + + # loop through all metadata resources + node = params['node'] + if node.type == 'media': + for resource in node.walk_resources(): + try: + image = PIL.Image.open(resource.source_file.path) + except: + continue + + # not all images have exif information + try: + resource.meta = RecursiveAttributes() + resource.meta.exif = AttributeMapper(image._getexif(), [PIL.ExifTags.TAGS, PIL.ExifTags.GPSTAGS]) + except AttributeError: + pass + + iptc = PIL.IptcImagePlugin.getiptcinfo(image) + resource.meta.iptc = AttributeMapper(iptc, [ImageMetadata.IIM_MAPPING]) + + # use the mapping to add easier access to some attributes + for key, attr in mapping.items(): + try: + setattr(resource.meta, attr, getattr(resource.meta, key)) + except AttributeError: + pass + + +class ImageMetadataPyExiv2(object): + """Image metadata retriever based on pyexiv2""" + DEFAULT_MAPPING = {'Iptc.Application2.Caption': 'caption'} + + @staticmethod + def process(folder, params): + import pyexiv2 + + class AttributeMapper(RecursiveAttributes): + def __init__(self, keys, image): + super(AttributeMapper, self).__init__() + for key in keys: + setattr(self, key, image[key]) + self._setup = True + + # setup default mapping + local overrides + mapping = ImageMetadataPyExiv2.DEFAULT_MAPPING.copy() + mapping.update(params.get('mapping', {})) + + # loop through all media resources + node = params['node'] + if node.type == 'media': + for resource in node.walk_resources(): + try: + image = pyexiv2.Image(resource.source_file.path) + image.readMetadata() + except: + continue + + # setup all retrieved keys in resource.meta + keys = set(image.exifKeys() + image.iptcKeys()) + resource.meta = AttributeMapper(keys, image) + + # use the mapping to add easier access to some attributes + for key, attr in mapping.items(): + if key in keys: + setattr(resource.meta, attr, getattr(resource.meta, key)) diff --git a/hydeengine/siteinfo.py b/hydeengine/siteinfo.py index 24018dc2..d03cc13c 100644 --- a/hydeengine/siteinfo.py +++ b/hydeengine/siteinfo.py @@ -255,8 +255,10 @@ def _add_resource(self, a_file): def find_node(self, folder): try: + #print 'FIND NODE', folder, self.site.nodemap.get(folder.path) return self.site.nodemap[folder.path] except KeyError: + #print 'FAILED FIND NODE', folder return None find_child = find_node @@ -595,4 +597,4 @@ def visit_file(self, a_file): "resource":resource, "exception": False }) - resource.node.remove_resource(resource) \ No newline at end of file + resource.node.remove_resource(resource) diff --git a/hydeengine/templatetags/hydetags.py b/hydeengine/templatetags/hydetags.py index aec60ed5..6a0fee99 100644 --- a/hydeengine/templatetags/hydetags.py +++ b/hydeengine/templatetags/hydetags.py @@ -2,18 +2,14 @@ from django.conf import settings from django.template import Template from django.template.loader import render_to_string -from django.template.defaultfilters import truncatewords_html +from django.template.defaultfilters import truncatewords_html, stringfilter from django.template.loader_tags import do_include from django.template import Library -from django.utils.safestring import mark_safe from hydeengine.file_system import Folder import re import string -import os import operator from datetime import datetime -from datetime import timedelta -from hydeengine.file_system import Folder marker_start = "\n" @@ -22,12 +18,12 @@ register = Library() class HydeContextNode(template.Node): - def __init__(self): + def __init__(self): pass - + def render(self, context): return "" - + @register.tag(name="hyde") def hyde_context(parser, token): return HydeContextNode() @@ -36,8 +32,8 @@ def hyde_context(parser, token): def excerpt(parser, token): nodelist = parser.parse(('endexcerpt',)) parser.delete_first_token() - return BracketNode("Excerpt", nodelist) - + return BracketNode("Excerpt", nodelist) + @register.tag(name="article") def excerpt(parser, token): nodelist = parser.parse(('endarticle',)) @@ -60,7 +56,7 @@ class LatestExcerptNode(template.Node): def __init__(self, path, words = 50): self.path = path self.words = words - + def render(self, context): sitemap_node = None if not self.words == 50: @@ -84,34 +80,33 @@ def later(page1, page2): end = rendered.find(excerpt_end, start) return truncatewords_html(rendered[start:end], self.words) else: - return "" - + return "" + class RecentPostsNode(template.Node): def __init__(self, var='recent_posts', count=5, node=None, categories=None): self.var = var - self.count = count + self.count = count self.node=node self.categories = categories - + def render(self, context): if not self.node: self.node = context['site'] else: - self.node = self.node.resolve(context) + self.node = self.node.resolve(context) if not self.count == 5: - self.count = self.count.render(context) - + self.count = self.count.render(context) + if not self.var == 'recent_posts': - self.var = self.var.render(context) - + self.var = self.var.render(context) + category_filter = None if not self.categories is None: - import re category_filter = re.compile(self.categories) - if (not hasattr(self.node, 'complete_page_list') or - not self.node.complete_page_list): + if (not hasattr(self.node, 'complete_page_list') or + not self.node.complete_page_list): complete_page_list = sorted( self.node.walk_pages(), key=operator.attrgetter("created"), reverse=True) @@ -127,25 +122,25 @@ def render(self, context): print self.categories,posts context[self.var] = posts[:int(self.count)] return '' - - + + @register.tag(name="recent_posts") def recent_posts(parser, token): tokens = token.split_contents() count = 5 - node = None + node = None categories = None - var = 'recent_posts' + var = 'recent_posts' if len(tokens) > 1: - var = Template(tokens[1]) + var = Template(tokens[1]) if len(tokens) > 2: count = Template(tokens[2]) if len(tokens) > 3: - node = parser.compile_filter(tokens[3]) + node = parser.compile_filter(tokens[3]) if len(tokens) > 4: - categories = tokens[4] + categories = tokens[4] return RecentPostsNode(var, count, node, categories) - + @register.tag(name="latest_excerpt") def latest_excerpt(parser, token): tokens = token.split_contents() @@ -157,7 +152,7 @@ def latest_excerpt(parser, token): words = Template(tokens[2]) return LatestExcerptNode(path, words) -@register.tag(name="render_excerpt") +@register.tag(name="render_excerpt") def render_excerpt(parser, token): tokens = token.split_contents() path = None @@ -168,14 +163,14 @@ def render_excerpt(parser, token): words = Template(tokens[2]) return RenderExcerptNode(path, words) -@register.tag(name="render_article") +@register.tag(name="render_article") def render_article(parser, token): tokens = token.split_contents() path = None if len(tokens) > 1: path = parser.compile_filter(tokens[1]) return RenderArticleNode(path) - + class RenderExcerptNode(template.Node): def __init__(self, page, words = 50): self.page = page @@ -189,7 +184,7 @@ def render(self, context): context["excerpt_title"] = page.title rendered = get_bracketed_content(context, page, "Excerpt") return truncatewords_html(rendered, self.words) - + class RenderArticleNode(template.Node): def __init__(self, page): @@ -199,7 +194,7 @@ def render(self, context): page = self.page.resolve(context) return get_bracketed_content(context, page, "Article") - + def get_bracketed_content(context, page, marker): rendered = None original_page = context['page'] @@ -216,11 +211,18 @@ def get_bracketed_content(context, page, marker): return "" +def hyde_thumbnail(url): + postfix = getattr(settings, 'THUMBNAIL_FILENAME_POSTFIX', '-thumb') + path, ext = url.rsplit('.', 1) + return ''.join([path, postfix, '.', ext]) +register.filter(stringfilter(hyde_thumbnail)) + + @register.filter def value_for_key(dictionary, key): - if not dictionary: + if not dictionary: return "" - if not dictionary.has_key(key): + if not dictionary.has_key(key): return "" value = dictionary[key] return value @@ -237,7 +239,7 @@ def xmldatetime(dt): @register.filter def remove_date_prefix(slug, sep="-"): - expr = sep.join([r"\d{2,4}"]*3 + ["(.*)"]) + expr = sep.join([r"\d{2,4}"]*3 + ["(.*)"]) match = re.match(expr, slug) if not match: return slug @@ -249,7 +251,7 @@ def unslugify(slug): words = slug.replace("_", " ").\ replace("-", " ").\ replace(".", "").split() - + return ' '.join(map(lambda str: str.capitalize(), words)) @register.tag(name="hyde_listing_page_rewrite_rules") @@ -283,7 +285,7 @@ def render(self, context): "### BEGIN GENERATED REWRITE RULES ####\n" \ + ''.join(rules) \ + "\n#### END GENERATED REWRITE RULES ####" - + class IncludeTextNode(template.Node): def __init__(self, include_node): self.include_node = include_node @@ -296,10 +298,41 @@ def render(self, context): print u"`includetext` requires Markdown and Typogrify." raise output = self.include_node.render(context) - output = markdown.markdown(output) - output = typogrify.typogrify(output) - return output - + output = markdown.markdown(output) + output = typogrify.typogrify(output) + return output + @register.tag(name="includetext") -def includetext(parser, token): +def includetext(parser, token): return IncludeTextNode(do_include(parser, token)) + + +class RecentResourcesNode(template.Node): + def __init__(self, tag_name, count=0, page='page', var_name='resources'): + self.tag_name = tag_name + self.count = int(count) + self.page = template.Variable(page) + self.var_name = var_name + + def render(self, context): + page = self.page.resolve(context) + resources = page is not None and page.node.media or [] + + if self.count: + resources = resources[:self.count] + + context[self.var_name] = resources + + return '' + + +@register.tag(name='recent_resources') +def recent_resources(parser, token): + args = list(token.split_contents()) + kwargs = {} + + if len(args) >= 3 and args[-2] == 'as': + kwargs['var_name'] = args.pop(-1) + args.pop(-1) + + return RecentResourcesNode(*args, **kwargs) From 2e73ef71821343fc718f74f20622433e25106550 Mon Sep 17 00:00:00 2001 From: Dave Dash Date: Sat, 15 May 2010 23:57:18 -0700 Subject: [PATCH 15/41] Using pip and requirements.txt --- README.markdown | 80 ++++++++++++++++++++++++++---------------------- requirements.txt | 3 ++ 2 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 requirements.txt diff --git a/README.markdown b/README.markdown index ecaf00dd..bc4b3665 100644 --- a/README.markdown +++ b/README.markdown @@ -2,22 +2,27 @@ 0.4 -This document should give enough information to get you up and running. Check the [wiki](http://wiki.github.com/lakshmivyas/hyde) for detailed documentation. To use Clyde, the online content editor for hyde see the [clyde readme](http://github.com/lakshmivyas/hyde/tree/master/clydeweb/). +This document should give enough information to get you up and running. Check +the [wiki](http://wiki.github.com/lakshmivyas/hyde) for detailed documentation. +To use Clyde, the online content editor for hyde see the +[clyde readme](http://github.com/lakshmivyas/hyde/tree/master/clydeweb/). -Hyde is a static website generator with the power of Django templates behind it. You can read more about its conception, history and features [here][1] and [here][2]. +Hyde is a static website generator with the power of Django templates behind it. +You can read more about its conception, history and features [here][1] and +[here][2]. [1]: http://www.ringce.com/products/hyde/hyde.html [2]: http://www.ringce.com/blog/2009/introducing_hyde.html + ## Basic Installation -The very basic installation of hyde only needs Django, Markdown and pyYAML. More python goodies are needed based on the features you may use. +Get the hyde source by cloning this repository. - sudo easy_install django - sudo easy_install pyYAML - sudo easy_install markdown +The very basic installation of hyde only needs Django, Markdown and pyYAML. More +python goodies are needed based on the features you may use. -Get the hyde source by [git cloning](http://github.com/guides/home) this repository. + pip install -r requirements.txt ## Running with Hyde @@ -27,33 +32,36 @@ The hyde engine has three entry points: 1. Initializer python hyde.py -i -s path/to/your/site [-t template_name = default] [-f] - During initialization hyde creates a basic website by copying the specified template (or default). This template contains the skeleton site layout, some content pages and settings.py. - + + During initialization hyde creates a basic website by copying the specified + template (or default). This template contains the skeleton site layout, some + content pages and settings.py. + Be careful with the -f setting, though: it will overwrite your website. 2. Generator python hyde.py -g -s path/to/your/site [-d deploy_dir=path/to/your/site/deploy] [-k] - - This will process the content and media and copy the generated website to your deploy directory. - If the -k option is specified, hyde will monitor the source folder for changes and automatically process them when the changes are encountered. This option is very handy when tweaking css or markup to quickly check the results. Note of caution: This option does not update listing files or excerpt files. It is recommended that you run -g again before you deploy the website. - + This will process the content and media and copy the generated website to your deploy directory. + + If the -k option is specified, hyde will monitor the source folder for changes and automatically process them when the changes are encountered. This option is very handy when tweaking css or markup to quickly check the results. Note of caution: This option does not update listing files or excerpt files. It is recommended that you run -g again before you deploy the website. + If you are on Mac OS X and would like to get Growl notifications, just set the GROWL setting to the `growlnotify` script path. 3. Web Server python hyde.py -w -s path/to/your/site [-d deploy_dir=path/to/your/site/deploy] - + This will start an instance of a cherrypy server and serve the generated website at localhost:8080. - + ## Site structure * layout - Template files that are used as base templates for content. None of the files in the layout folder are copied over to the deploy directory. * content - Any file that is not prefixed with \_, . or suffixed with ~ are processed by running through the template engine. -* media - Contains site media, css, js and images. +* media - Contains site media, css, js and images. * settings.py - Django and hyde settings. ### Recommended conventions @@ -74,22 +82,22 @@ Media processors are defined in the following format: {:{ :(, )} } - + The processors are executed in the order in which they are defined. The output from the first processor becomes the input of the next. A \* instead of folder will apply the setting to all folders. There is no wildcard support for folder name yet, \* is just a catch all special case. -File extensions should be specified as .css, .js, .png etc. Again no wildcard support yet. +File extensions should be specified as .css, .js, .png etc. Again no wildcard support yet. Hyde retains the YUI Compressor, Clever CSS and HSS processors from aym-cms. -#### Template Processor +#### Template Processor -Template processor allows the use of context variables inside your media files. +Template processor allows the use of context variables inside your media files. #### YUI Compressor -Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.YUICompressor'`` and compresses them. +Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.YUICompressor'`` and compresses them. [yuic]: http://developer.yahoo.com/yui/compressor/ @@ -98,7 +106,7 @@ be a path to a [YUI Compressor][yuic] jar on your computer. #### Closure Compiler -Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.ClosureCompiler'`` and compresses them. +Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.ClosureCompiler'`` and compresses them. [closure]: http://closure-compiler.googlecode.com/ @@ -107,7 +115,7 @@ be a path to a [Closure Compiler][closure] jar on your computer. #### Clever CSS Processor -Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.CleverCSS'`` and converts them to css. +Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.CleverCSS'`` and converts them to css. You need to install Clever CSS using ``sudo easy_install CleverCSS`` command for this processor to work. @@ -115,7 +123,7 @@ You need to install Clever CSS using ``sudo easy_install CleverCSS`` command for #### HSS Processor -Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.HSS'`` and converts them to css. +Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.HSS'`` and converts them to css. You need to download HSS from [the project website][hss] and set the ``HSS_PATH`` variable to the downloaded path. A version for OS X is installed in the ``lib`` folder by default. To use it, just uncomment the ``HSS_PATH`` line in the settings.py file of your template. @@ -123,15 +131,15 @@ You need to download HSS from [the project website][hss] and set the ``HSS_PATH` #### SASS Processor -Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.SASS'`` and converts them to css. +Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.SASS'`` and converts them to css. You need to install SASS (see [the project website][sass]) and set the ``SASS_PATH`` variable to the path to the ``sass`` script. -[sass]: http://sass-lang.com/ +[sass]: http://sass-lang.com/ #### Less CSS Processor -Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.LessCSS'`` and converts them to css. +Runs through the all the files defined in the configuration associated with ``'hydeengine.media_processors.LessCSS'`` and converts them to css. You need to install Less (see [the project website][lesscss]) and set the ``LESS_CSS_PATH`` variable to the path to the ``lessc`` script. @@ -168,7 +176,7 @@ On your content pages you can define the page variables using the standard YAML {%hyde title: A New Post - list: + list: - One - Two - Three @@ -196,7 +204,7 @@ It is used as follows: 1. Or at least that is my opinion. 2. What about you? - {% endmarkdown %} + {% endmarkdown %} #### Markdown2 @@ -221,7 +229,7 @@ It is used as follows: # Or at least that is my opinion. # What about you? - + {% endtextile %} @@ -243,7 +251,7 @@ It is used as follows: #. Or at least that is my opinion. #. What about you? - + {% endrestructuredtext %} The default reStructuredText settings may be changed by assigning a dictionary of setting names and values to the ``RST_SETTINGS_OVERRIDES`` setting in the settings file. For information on the various configuration options, see the [docutils configuration documentation](http://docutils.sourceforge.net/docs/user/config.html). @@ -335,12 +343,12 @@ To enable Typogrify, use ``{% filter typogrify %}`` in your code. Typogrify is " There are two layouts currently available: default and simple. -The default site layout contains templates for basic site structure, navigation, breadcrumbs, listing, posts and Atom feed and a very basic stylesheet. +The default site layout contains templates for basic site structure, navigation, breadcrumbs, listing, posts and Atom feed and a very basic stylesheet. # Examples -The following websites are built using hyde and are open sourced. +The following websites are built using hyde and are open sourced. * [SteveLosh.com][stevelosh] * [The Old Ringce Website][ringce] @@ -360,10 +368,10 @@ The following websites are built using hyde and are open sourced. - [Valentin Jacquemin](http://github.com/poxd) - [Johannes Reinhard](http://github.com/SpeckFleck) - [Steve Losh](http://github.com/sjl) -- [William Amberg](http://github.com/wamberg) +- [William Amberg](http://github.com/wamberg) - [James Clarke](http://github.com/jc) -- [Benjamin Pollack](http://github.com/bpollack) -- [Andrey](http://github.com/andrulik) +- [Benjamin Pollack](http://github.com/bpollack) +- [Andrey](http://github.com/andrulik) - [Toby White](http://github.com/tow) - [Tim Freund](http://github.com/timfreund) - [Russell H](http://github.com/russellhaering) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c3cd5608 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +django +pyYAML +markdown From 4b5aeade79fac1c6ac4a3a1cf2dff98aa4a7a66b Mon Sep 17 00:00:00 2001 From: Ardekantur Date: Mon, 10 May 2010 07:23:05 +0800 Subject: [PATCH 16/41] setup.py --- setup.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..fb15521a --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_packages + +setup(name='hyde', + verison='0.4', + description='static site generator', + packages=find_packages(), + install_requires=( + 'django', + 'pyYAML', + 'markdown2', + 'pygments', + 'pyrss2gen', + ), +) From e5b4b0d0ddd65ca5fbedda6049d28b78fe9ff18b Mon Sep 17 00:00:00 2001 From: Hugo Vincent Date: Sun, 30 May 2010 14:11:05 +0100 Subject: [PATCH 17/41] Update to YUI Compressor 2.4.2. --- ...ssor-2.4.1.jar => yuicompressor-2.4.2.jar} | Bin 851220 -> 851357 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{yuicompressor-2.4.1.jar => yuicompressor-2.4.2.jar} (90%) mode change 100644 => 100755 diff --git a/lib/yuicompressor-2.4.1.jar b/lib/yuicompressor-2.4.2.jar old mode 100644 new mode 100755 similarity index 90% rename from lib/yuicompressor-2.4.1.jar rename to lib/yuicompressor-2.4.2.jar index 31f164a6ae9774aa4fe141f9cee1002d340f91b5..d5bbf1981c77b9181140ff40d8844937dcf6b462 GIT binary patch delta 67413 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 GgZ~fLM?ca4 delta 66970 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 XpLD^d?g+f<@%aI<;+^wHzY_gF?6@>p From 4da62b196d51a7cbb4bff2c27bf5a2e49dc1174d Mon Sep 17 00:00:00 2001 From: Hugo Vincent Date: Sun, 30 May 2010 14:13:51 +0100 Subject: [PATCH 18/41] Add YUI Compressor as a site post-processor (in addtion to the existing media processor), so that CSS generated by LessCSS (and possibly others) can be compressed properly. --- hydeengine/site_post_processors.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/hydeengine/site_post_processors.py b/hydeengine/site_post_processors.py index 3efea16a..f67dba44 100644 --- a/hydeengine/site_post_processors.py +++ b/hydeengine/site_post_processors.py @@ -9,6 +9,35 @@ import commands import codecs +class YUICompressor: + + @staticmethod + def process(folder, params): + class Compressor: + def visit_file(self, thefile): + if settings.YUI_COMPRESSOR == None: + return + compress = settings.YUI_COMPRESSOR + if not os.path.exists(compress): + compress = os.path.join( + os.path.dirname( + os.path.abspath(__file__)), "..", compress) + + if not compress or not os.path.exists(compress): + raise ValueError( + "YUI Compressor cannot be found at [%s]" % compress) + + tmp_file = File(thefile.path + ".z-tmp") + status, output = commands.getstatusoutput( + u"java -jar %s %s > %s" % (compress, thefile.path, tmp_file.path)) + if status > 0: + print output + else: + thefile.delete() + tmp_file.move_to(thefile.path) + + folder.walk(Compressor(), "*.css") + class FolderFlattener: @staticmethod From 71491e5fe947724a2d7dcf572fd757659145c26b Mon Sep 17 00:00:00 2001 From: Hugo Vincent Date: Sun, 30 May 2010 14:14:12 +0100 Subject: [PATCH 19/41] Fix bugs surrounding generation of excerpts using the {% excerpt %} tag. --- hydeengine/templatetags/hydetags.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/hydeengine/templatetags/hydetags.py b/hydeengine/templatetags/hydetags.py index aec60ed5..88a33e4e 100644 --- a/hydeengine/templatetags/hydetags.py +++ b/hydeengine/templatetags/hydetags.py @@ -39,7 +39,7 @@ def excerpt(parser, token): return BracketNode("Excerpt", nodelist) @register.tag(name="article") -def excerpt(parser, token): +def article(parser, token): nodelist = parser.parse(('endarticle',)) parser.delete_first_token() return BracketNode("Article", nodelist) @@ -63,8 +63,6 @@ def __init__(self, path, words = 50): def render(self, context): sitemap_node = None - if not self.words == 50: - self.words = self.words.render(context) self.path = self.path.render(context).strip('"') sitemap_node = context["site"].find_node(Folder(self.path)) if not sitemap_node: @@ -154,7 +152,7 @@ def latest_excerpt(parser, token): if len(tokens) > 1: path = Template(tokens[1]) if len(tokens) > 2: - words = Template(tokens[2]) + words = int(tokens[2]) return LatestExcerptNode(path, words) @register.tag(name="render_excerpt") @@ -165,7 +163,7 @@ def render_excerpt(parser, token): if len(tokens) > 1: path = parser.compile_filter(tokens[1]) if len(tokens) > 2: - words = Template(tokens[2]) + words = int(tokens[2]) return RenderExcerptNode(path, words) @register.tag(name="render_article") @@ -182,8 +180,6 @@ def __init__(self, page, words = 50): self.words = words def render(self, context): - if not self.words == 50: - self.words = self.words.render(context) page = self.page.resolve(context) context["excerpt_url"] = page.url context["excerpt_title"] = page.title From ad5961d6eb1716a1ec998bf5079bc06c05cc56c1 Mon Sep 17 00:00:00 2001 From: Hugo Vincent Date: Sun, 30 May 2010 15:34:39 +0100 Subject: [PATCH 20/41] Add compression quality option to JPEG thumbnail generation. --- README.markdown | 2 ++ hydeengine/media_processors.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index ecaf00dd..806d570a 100644 --- a/README.markdown +++ b/README.markdown @@ -147,6 +147,8 @@ You also need to set the ``THUMBNAIL_MAX_WIDTH`` and ``THUMBNAIL_MAX_HEIGHT`` va You can set the ``THUMBNAIL_FILENAME_POSTFIX`` to change the string that is appended to the filename of thumbnails. By default this is ``-thumb`` (i.e. the thumbnail of ``my-image.png`` will be called ``my-image-thumb.png``). +You can optionally set the ``THUMBNAIL_JPEG_QUALITY`` (between 0 and 100) to control the JPEG compression quality. + [PIL]: http://www.pythonware.com/products/pil/ ### Content Processors diff --git a/hydeengine/media_processors.py b/hydeengine/media_processors.py index 1dc4804b..951bac9f 100644 --- a/hydeengine/media_processors.py +++ b/hydeengine/media_processors.py @@ -140,4 +140,7 @@ def process(resource): postfix = "-thumb" thumb_path = "%s%s.%s" % (orig_path, postfix, orig_extension) - i.save(thumb_path) \ No newline at end of file + if i.format == "JPEG" and "THUMBNAIL_JPEG_QUALITY" in dir(settings): + i.save(thumb_path, quality = settings.THUMBNAIL_JPEG_QUALITY, optimize = True) + else: + i.save(thumb_path) From 5f16df67e63e95f316f7388a6cac33998dc097e2 Mon Sep 17 00:00:00 2001 From: James Willcox Date: Tue, 8 Jun 2010 10:11:44 -0400 Subject: [PATCH 21/41] Add snorp.net --- Makefile | 7 + snorp.net/content/.htaccess | 15 ++ snorp.net/content/about/about.html | 17 ++ snorp.net/content/blog/2003/back-to-work.html | 17 ++ .../content/blog/2003/car-and-stuff.html | 16 ++ snorp.net/content/blog/2003/chema.html | 16 ++ .../2003/give-credit-where-credit-is-due.html | 16 ++ snorp.net/content/blog/2003/i-am-a-moron.html | 15 ++ .../blog/2003/im-sorry-but-it-was-funny.html | 20 ++ .../2003/im-trying-out-seths-blogger.html | 15 ++ .../2003/lets-get-this-party-started.html | 17 ++ snorp.net/content/blog/2003/lifestart.html | 16 ++ snorp.net/content/blog/2003/new-site.html | 19 ++ .../blog/2003/ok-ok-stop-hounding-me.html | 18 ++ .../blog/2003/take-my-word-for-it.html | 16 ++ snorp.net/content/blog/2003/titles-suck.html | 21 ++ snorp.net/content/blog/2003/untitled-1.html | 18 ++ snorp.net/content/blog/2003/update.html | 15 ++ .../blog/2003/whats-that-on-your-face.html | 18 ++ snorp.net/content/blog/2003/wow-im-lame.html | 18 ++ .../content/blog/2004/down-with-the-man.html | 16 ++ snorp.net/content/blog/2004/fud.html | 21 ++ snorp.net/content/blog/2004/guadec.html | 17 ++ snorp.net/content/blog/2004/hacking.html | 21 ++ snorp.net/content/blog/2004/hmmm.html | 19 ++ .../content/blog/2004/lightning-sucks.html | 22 +++ .../content/blog/2004/lots-of-stuff.html | 17 ++ .../blog/2004/more-wireless-stuff.html | 18 ++ snorp.net/content/blog/2004/new-place.html | 18 ++ snorp.net/content/blog/2004/this-just-in.html | 15 ++ .../blog/2004/updated-wireless-stuff.html | 23 +++ .../content/blog/2004/wedding-photos.html | 16 ++ snorp.net/content/blog/2004/whirl2il.html | 15 ++ snorp.net/content/blog/2004/whoa-deja-vu.html | 19 ++ .../2004/yet-more-wireless-applet-stuff.html | 16 ++ .../2004/you-want-me-to-give-you-what.html | 17 ++ snorp.net/content/blog/2004/zmd.html | 17 ++ snorp.net/content/blog/2005/avahi-sharp.html | 48 +++++ .../blog/2005/cd-burning-for-muine.html | 15 ++ .../content/blog/2005/firefox-rules.html | 17 ++ .../blog/2005/grumpy-sneezy-sleepy.html | 20 ++ .../content/blog/2005/hula-hula-hula.html | 16 ++ snorp.net/content/blog/2005/ipod-sharp.html | 17 ++ .../blog/2005/ipod-syncing-for-muine.html | 19 ++ .../content/blog/2005/more-muine-burning.html | 15 ++ .../content/blog/2005/more-muineipod.html | 18 ++ snorp.net/content/blog/2005/nokiamaemo.html | 17 ++ .../blog/2005/rcd-for-fedora-core-3.html | 15 ++ snorp.net/content/blog/2005/utah.html | 19 ++ .../content/blog/2005/vengeance-is-mine.html | 16 ++ .../blog/2006/allegedly-very-tasty.html | 17 ++ snorp.net/content/blog/2006/apple-sucks.html | 24 +++ .../content/blog/2006/blowing-up-pluto.html | 15 ++ .../blog/2006/daap-sharp-on-windows.html | 16 ++ .../content/blog/2006/flaming-skulls.html | 17 ++ snorp.net/content/blog/2006/fruity.html | 19 ++ .../content/blog/2006/gnome-1-slashdot-0.html | 15 ++ ...-in-ur-virtual-machines-managing-them.html | 22 +++ snorp.net/content/blog/2006/itunes-7.html | 15 ++ .../maybe-theyre-not-as-bad-as-i-thought.html | 17 ++ .../blog/2006/more-vmx-manager-stuff.html | 16 ++ ...erines-on-a-muthafuckin-planewwindows.html | 21 ++ .../photos-and-cover-art-in-ipod-sharp.html | 18 ++ .../content/blog/2006/sleep-at-last.html | 16 ++ .../content/blog/2006/tangerine-muine.html | 15 ++ .../blog/2006/tangerine-with-cocoa.html | 20 ++ .../up-to-10-times-more-sharing-or-more.html | 17 ++ .../blog/2006/why-i-hate-apple-still.html | 15 ++ .../blog/2006/yumier-than-bacardi.html | 23 +++ .../content/blog/2007/banshee-and-awn.html | 17 ++ snorp.net/content/blog/2007/listing.html | 5 + .../blog/2007/monster-truck-lloyd.html | 22 +++ .../blog/2007/opensuse-livecd-installer.html | 21 ++ .../blog/2007/return-of-the-carpet.html | 55 ++++++ .../2007/rpm-transaction-enhancements.html | 17 ++ .../blog/2007/thoughts-on-sincerity.html | 16 ++ snorp.net/content/blog/2008/2-1.html | 16 ++ snorp.net/content/blog/2008/2008.html | 5 + .../2008/application-usage-monitoring.html | 18 ++ snorp.net/content/blog/2008/mango-lassi.html | 16 ++ snorp.net/content/blog/2008/more-mango.html | 16 ++ snorp.net/content/blog/2008/nas-for-home.html | 16 ++ snorp.net/content/blog/2008/new-job.html | 16 ++ .../2008/novell-bugzillauserjs-updates.html | 16 ++ .../blog/2008/out-weaseling-firefox.html | 15 ++ .../slashdot-looking-for-open-proxies.html | 20 ++ .../blog/2008/some-things-do-change.html | 16 ++ snorp.net/content/blog/2009/2009.html | 5 + .../content/blog/2009/fun-with-studio.html | 19 ++ .../blog/2009/yeah-im-a-rails-fanboy-now.html | 17 ++ snorp.net/content/blog/atom.xml | 1 + snorp.net/content/blog/blog.html | 5 + snorp.net/content/index.html | 26 +++ snorp.net/layout/_about.html | 14 ++ snorp.net/layout/_post.html | 24 +++ snorp.net/layout/skeleton/_atom.xml | 30 +++ snorp.net/layout/skeleton/_base.html | 24 +++ snorp.net/layout/skeleton/_base.html.orig | 27 +++ snorp.net/layout/skeleton/_body.html | 15 ++ snorp.net/layout/skeleton/_breadcrumbs.html | 15 ++ snorp.net/layout/skeleton/_context_nav.html | 10 + snorp.net/layout/skeleton/_footer.html | 30 +++ .../layout/skeleton/_frontpage_item.html | 16 ++ .../layout/skeleton/_frontpage_item.html.bak | 41 ++++ snorp.net/layout/skeleton/_header.html | 25 +++ snorp.net/layout/skeleton/_innerlisting.html | 25 +++ snorp.net/layout/skeleton/_listing.html | 10 + snorp.net/layout/skeleton/_root.html | 1 + snorp.net/media/css/base.css | 186 ++++++++++++++++++ snorp.net/media/js/base.js | 42 ++++ snorp.net/settings.py | 136 +++++++++++++ 111 files changed, 2294 insertions(+) create mode 100644 Makefile create mode 100644 snorp.net/content/.htaccess create mode 100644 snorp.net/content/about/about.html create mode 100644 snorp.net/content/blog/2003/back-to-work.html create mode 100644 snorp.net/content/blog/2003/car-and-stuff.html create mode 100644 snorp.net/content/blog/2003/chema.html create mode 100644 snorp.net/content/blog/2003/give-credit-where-credit-is-due.html create mode 100644 snorp.net/content/blog/2003/i-am-a-moron.html create mode 100644 snorp.net/content/blog/2003/im-sorry-but-it-was-funny.html create mode 100644 snorp.net/content/blog/2003/im-trying-out-seths-blogger.html create mode 100644 snorp.net/content/blog/2003/lets-get-this-party-started.html create mode 100644 snorp.net/content/blog/2003/lifestart.html create mode 100644 snorp.net/content/blog/2003/new-site.html create mode 100644 snorp.net/content/blog/2003/ok-ok-stop-hounding-me.html create mode 100644 snorp.net/content/blog/2003/take-my-word-for-it.html create mode 100644 snorp.net/content/blog/2003/titles-suck.html create mode 100644 snorp.net/content/blog/2003/untitled-1.html create mode 100644 snorp.net/content/blog/2003/update.html create mode 100644 snorp.net/content/blog/2003/whats-that-on-your-face.html create mode 100644 snorp.net/content/blog/2003/wow-im-lame.html create mode 100644 snorp.net/content/blog/2004/down-with-the-man.html create mode 100644 snorp.net/content/blog/2004/fud.html create mode 100644 snorp.net/content/blog/2004/guadec.html create mode 100644 snorp.net/content/blog/2004/hacking.html create mode 100644 snorp.net/content/blog/2004/hmmm.html create mode 100644 snorp.net/content/blog/2004/lightning-sucks.html create mode 100644 snorp.net/content/blog/2004/lots-of-stuff.html create mode 100644 snorp.net/content/blog/2004/more-wireless-stuff.html create mode 100644 snorp.net/content/blog/2004/new-place.html create mode 100644 snorp.net/content/blog/2004/this-just-in.html create mode 100644 snorp.net/content/blog/2004/updated-wireless-stuff.html create mode 100644 snorp.net/content/blog/2004/wedding-photos.html create mode 100644 snorp.net/content/blog/2004/whirl2il.html create mode 100644 snorp.net/content/blog/2004/whoa-deja-vu.html create mode 100644 snorp.net/content/blog/2004/yet-more-wireless-applet-stuff.html create mode 100644 snorp.net/content/blog/2004/you-want-me-to-give-you-what.html create mode 100644 snorp.net/content/blog/2004/zmd.html create mode 100644 snorp.net/content/blog/2005/avahi-sharp.html create mode 100644 snorp.net/content/blog/2005/cd-burning-for-muine.html create mode 100644 snorp.net/content/blog/2005/firefox-rules.html create mode 100644 snorp.net/content/blog/2005/grumpy-sneezy-sleepy.html create mode 100644 snorp.net/content/blog/2005/hula-hula-hula.html create mode 100644 snorp.net/content/blog/2005/ipod-sharp.html create mode 100644 snorp.net/content/blog/2005/ipod-syncing-for-muine.html create mode 100644 snorp.net/content/blog/2005/more-muine-burning.html create mode 100644 snorp.net/content/blog/2005/more-muineipod.html create mode 100644 snorp.net/content/blog/2005/nokiamaemo.html create mode 100644 snorp.net/content/blog/2005/rcd-for-fedora-core-3.html create mode 100644 snorp.net/content/blog/2005/utah.html create mode 100644 snorp.net/content/blog/2005/vengeance-is-mine.html create mode 100644 snorp.net/content/blog/2006/allegedly-very-tasty.html create mode 100644 snorp.net/content/blog/2006/apple-sucks.html create mode 100644 snorp.net/content/blog/2006/blowing-up-pluto.html create mode 100644 snorp.net/content/blog/2006/daap-sharp-on-windows.html create mode 100644 snorp.net/content/blog/2006/flaming-skulls.html create mode 100644 snorp.net/content/blog/2006/fruity.html create mode 100644 snorp.net/content/blog/2006/gnome-1-slashdot-0.html create mode 100644 snorp.net/content/blog/2006/im-in-ur-virtual-machines-managing-them.html create mode 100644 snorp.net/content/blog/2006/itunes-7.html create mode 100644 snorp.net/content/blog/2006/maybe-theyre-not-as-bad-as-i-thought.html create mode 100644 snorp.net/content/blog/2006/more-vmx-manager-stuff.html create mode 100644 snorp.net/content/blog/2006/muthafuckin-tangerines-on-a-muthafuckin-planewwindows.html create mode 100644 snorp.net/content/blog/2006/photos-and-cover-art-in-ipod-sharp.html create mode 100644 snorp.net/content/blog/2006/sleep-at-last.html create mode 100644 snorp.net/content/blog/2006/tangerine-muine.html create mode 100644 snorp.net/content/blog/2006/tangerine-with-cocoa.html create mode 100644 snorp.net/content/blog/2006/up-to-10-times-more-sharing-or-more.html create mode 100644 snorp.net/content/blog/2006/why-i-hate-apple-still.html create mode 100644 snorp.net/content/blog/2006/yumier-than-bacardi.html create mode 100644 snorp.net/content/blog/2007/banshee-and-awn.html create mode 100644 snorp.net/content/blog/2007/listing.html create mode 100644 snorp.net/content/blog/2007/monster-truck-lloyd.html create mode 100644 snorp.net/content/blog/2007/opensuse-livecd-installer.html create mode 100644 snorp.net/content/blog/2007/return-of-the-carpet.html create mode 100644 snorp.net/content/blog/2007/rpm-transaction-enhancements.html create mode 100644 snorp.net/content/blog/2007/thoughts-on-sincerity.html create mode 100644 snorp.net/content/blog/2008/2-1.html create mode 100644 snorp.net/content/blog/2008/2008.html create mode 100644 snorp.net/content/blog/2008/application-usage-monitoring.html create mode 100644 snorp.net/content/blog/2008/mango-lassi.html create mode 100644 snorp.net/content/blog/2008/more-mango.html create mode 100644 snorp.net/content/blog/2008/nas-for-home.html create mode 100644 snorp.net/content/blog/2008/new-job.html create mode 100644 snorp.net/content/blog/2008/novell-bugzillauserjs-updates.html create mode 100644 snorp.net/content/blog/2008/out-weaseling-firefox.html create mode 100644 snorp.net/content/blog/2008/slashdot-looking-for-open-proxies.html create mode 100644 snorp.net/content/blog/2008/some-things-do-change.html create mode 100644 snorp.net/content/blog/2009/2009.html create mode 100644 snorp.net/content/blog/2009/fun-with-studio.html create mode 100644 snorp.net/content/blog/2009/yeah-im-a-rails-fanboy-now.html create mode 100644 snorp.net/content/blog/atom.xml create mode 100644 snorp.net/content/blog/blog.html create mode 100644 snorp.net/content/index.html create mode 100644 snorp.net/layout/_about.html create mode 100644 snorp.net/layout/_post.html create mode 100644 snorp.net/layout/skeleton/_atom.xml create mode 100644 snorp.net/layout/skeleton/_base.html create mode 100644 snorp.net/layout/skeleton/_base.html.orig create mode 100644 snorp.net/layout/skeleton/_body.html create mode 100644 snorp.net/layout/skeleton/_breadcrumbs.html create mode 100644 snorp.net/layout/skeleton/_context_nav.html create mode 100644 snorp.net/layout/skeleton/_footer.html create mode 100644 snorp.net/layout/skeleton/_frontpage_item.html create mode 100644 snorp.net/layout/skeleton/_frontpage_item.html.bak create mode 100644 snorp.net/layout/skeleton/_header.html create mode 100644 snorp.net/layout/skeleton/_innerlisting.html create mode 100644 snorp.net/layout/skeleton/_listing.html create mode 100644 snorp.net/layout/skeleton/_root.html create mode 100644 snorp.net/media/css/base.css create mode 100644 snorp.net/media/js/base.js create mode 100644 snorp.net/settings.py diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7141cd8d --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all: refresh + +refresh: + ./hyde.py -g -s ./snorp.net + +run: refresh + ./hyde.py -k -w -s ./snorp.net \ No newline at end of file diff --git a/snorp.net/content/.htaccess b/snorp.net/content/.htaccess new file mode 100644 index 00000000..c02394b2 --- /dev/null +++ b/snorp.net/content/.htaccess @@ -0,0 +1,15 @@ +{% if GENERATE_CLEAN_URLS %} +RewriteEngine on +RewriteBase {{ node.site.settings.SITE_ROOT }} + +{% hyde_listing_page_rewrite_rules %} + +# listing pages whose names are the same as their enclosing folder's +RewriteCond %{REQUEST_FILENAME}/$1.html -f +RewriteRule ^([^/]*)/$ %{REQUEST_FILENAME}/$1.html + +# regular pages +RewriteCond %{REQUEST_FILENAME}.html -f +RewriteRule ^.*$ %{REQUEST_FILENAME}.html + +{% endif %} diff --git a/snorp.net/content/about/about.html b/snorp.net/content/about/about.html new file mode 100644 index 00000000..49c44d6c --- /dev/null +++ b/snorp.net/content/about/about.html @@ -0,0 +1,17 @@ +{% extends "_about.html" %} +{%hyde + title: "About" +%} +{% block article %} + +Hyde is a static website generator using django templates & an "extensible generation engine". + +Read more about Hyde [here:]({{content}}/{{links.introducing_hyde}}) + + +{% endblock %} + + + + + diff --git a/snorp.net/content/blog/2003/back-to-work.html b/snorp.net/content/blog/2003/back-to-work.html new file mode 100644 index 00000000..497a438c --- /dev/null +++ b/snorp.net/content/blog/2003/back-to-work.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "back to work" + created: 2003-07-07 14:26:52 +%} + +{% block article %} +{%article%} + +

I’m back home now. Had a great weekend at the lake, as usual. Went sailing on the catamaran, watched fireworks, ate, etc.

+

Some friends of the people I was visiting are moving, and they had an old desk they were trying to get rid of. Amazingly, I found a way to transport it back down to Bloomington, so now I have a desk. Sweet.

+

Looks like Nat (and his trusty sidekicks) made a bunch of progress on Dashboard over the weekend. I wonder if that guy sleeps. It appears my ephy patch to make it send cluepackets is outdated now, and needs some more work. I should find time to fix it.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/car-and-stuff.html b/snorp.net/content/blog/2003/car-and-stuff.html new file mode 100644 index 00000000..0d8281c1 --- /dev/null +++ b/snorp.net/content/blog/2003/car-and-stuff.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "car and stuff" + created: 2003-12-04 15:00:14 +%} + +{% block article %} +{%article%} + +

My Dad’s old car (which I have been driving) was getting in pretty bad shape, so I bought a new (used) car on Saturday. It’s a green VW Passat. It’s totally sexy. I took pictures but don’t have them up anywhere.

+

Work isn’t taking as much of my time now, which is good I guess. Unfortunately, after hacking all day at work I have little desire to hack on GNOME, which is bad. The API/ABI freeze is on Monday and recent-files STILL isn’t ready to go in. Paolo has been working hard on a new document model and other good stuff, though, so maybe we’ll make it. OO.o has its own implementation, though, so we need to coordinate with those guys on what is happening…

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/chema.html b/snorp.net/content/blog/2003/chema.html new file mode 100644 index 00000000..1080c11a --- /dev/null +++ b/snorp.net/content/blog/2003/chema.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Chema" + created: 2003-11-10 21:02:43 +%} + +{% block article %} +{%article%} + +

Wow, talk about some shocking news. Chema Celorio died last night after a skydiving accident.

+

Chema was the person who got me involved with GNOME. I saw his post to the gnome-love mailing list, and decided I wanted to help out. He was more than willing to guide me along, often sinking quite a bit of time into answering my questions, etc. I met him for the first time in real life this summer on my first trip to Boston after being hired. He was staying at the company apartment in between sales trips. One night we were bored and watched a bunch of video he had recorded from his jumps. Chema lived life to the max, and he will certainly be missed.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/give-credit-where-credit-is-due.html b/snorp.net/content/blog/2003/give-credit-where-credit-is-due.html new file mode 100644 index 00000000..9aec6d2f --- /dev/null +++ b/snorp.net/content/blog/2003/give-credit-where-credit-is-due.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "give credit where credit is due" + created: 2003-08-17 05:18:38 +%} + +{% block article %} +{%article%} + +

I’ve been listening to music for like 12 hours straight now using the latest Rhythmbox from CVS. I’m happy to say that it hasn’t crashed even ONCE during this time. The UI feels snappy, and everything Just Works (with the exception of it not playing a couple of my mp3s, but whatever). MAD PROPS TO WALTERS AND THE REST OF THE RHYTHMBOX TEAM!

+

So, what I’m trying to say is, if you gave up on Rhythmbox back in the day, give it another chance. Oh, here’s a screenshot.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/i-am-a-moron.html b/snorp.net/content/blog/2003/i-am-a-moron.html new file mode 100644 index 00000000..18be5e18 --- /dev/null +++ b/snorp.net/content/blog/2003/i-am-a-moron.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "I am a moron" + created: 2003-07-20 10:25:34 +%} + +{% block article %} +{%article%} + +

I had a pretty nice week in Boston, generally. All the Ximian guys/gals were cool (except Ian :) ). I even managed to get some actual work done. So, things were going well up until Friday. That’s when I missed my plane, because I thought it was leaving on Saturday. Ugh. I called the travel agency, and managed to get another flight out at 6:00 AM. I ended up getting to Indy around 11 or so. Then, I discover that my car’s battery is dead. Nice. Eventually, I get a jump from the parking lot dudes, and I’m on my way. Pretty tired by the time I get home, having not slept for about 30 hours. Slept the rest of the day. Have to deal with the car today, and find out what ran the battery down. I pulled a fuse to what I thought was the culprit yesterday (there is a penny in the cigarette lighter — seems a likely cause), so we’ll see how it goes.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/im-sorry-but-it-was-funny.html b/snorp.net/content/blog/2003/im-sorry-but-it-was-funny.html new file mode 100644 index 00000000..9504d141 --- /dev/null +++ b/snorp.net/content/blog/2003/im-sorry-but-it-was-funny.html @@ -0,0 +1,20 @@ + +{% extends "_post.html" %} +{%hyde + title: "I'm sorry, but it was funny." + created: 2003-08-01 21:34:56 +%} + +{% block article %} +{%article%} + +

I’m in Boston again. Been here since Sunday. Things have been going fairly well I suppose. It seems I am starting to get my arms around red carpet now, as I was able to fix a few things today.

+

A lot of other remote guys are in town too, so that’s cool. I finally got to meet Tambet and Aidan.

+

I fixed a small bug in nautilus yesterday. I miss hacking on it. I guess there are big plans for 2.6, though, so maybe I can get in on that if I have time. It’s really great to see medusa getting some love. I haven’t tried it out yet, but I hear Curtis Hovey has been doing great things.

+

I’ve totally neglected dashboard this week. I have a little patch sitting on my machine at home that fixes up the epiphany frontend and turns it into a plugin. Hopefully it won’t be too out of date when I get back next week…..

+

I bought “Cat’s Cradle” by Kurt Vonnegut and read it on the trip to Boston. I was only expecting to get maybe half-way done with it, but it turns out I was able to read the whole thing due to my flight(s) taking so long. In Indianapolis, we sat on the tarmac for like 45 minutes waiting for a new flight plan (apparently there was some bad weather in the original route), so I was like an hour late to JFK. At JFK, we had to wait for a flight plan again. Then, they found out they lost the captain’s luggage so we had to wait on that. When we finally got out of the terminal, we were like 46th in line for takeoff, so we slowly taxiied around for a while. During the taxi, the cabin filled with smoke due to some trivial air-conditioning snafu. Everyone freaked out and we went back to the tarmac. Two hours later, or so, we were finally in the air.

+

Anyway, “Cat’s Cradle” was really good. I think I’m going to get another Vonnegut to read on the way back. Anyone have a suggestion?

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/im-trying-out-seths-blogger.html b/snorp.net/content/blog/2003/im-trying-out-seths-blogger.html new file mode 100644 index 00000000..b8daec7a --- /dev/null +++ b/snorp.net/content/blog/2003/im-trying-out-seths-blogger.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "I'm trying out Seth's blogger" + created: 2003-09-02 12:57:23 +%} + +{% block article %} +{%article%} + +

I’m trying out Seth’s blogger applet. Testing 1..2..3….

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/lets-get-this-party-started.html b/snorp.net/content/blog/2003/lets-get-this-party-started.html new file mode 100644 index 00000000..81f3aacc --- /dev/null +++ b/snorp.net/content/blog/2003/lets-get-this-party-started.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "let's get this party started" + created: 2003-07-12 21:21:11 +%} + +{% block article %} +{%article%} + +

I’m leaving tomorrow for my first trip to Boston. I’m pretty psyched about it.

+

Worked a bit on epiphany/dashboard today. Updated the frontend patch to work with the new API, and also built an RPM (http://www.snorp.net/files/packages/epiphany-0.7.3-2.i386.rpm). It requires mozilla from xd2 (sorry, it’s what I have).

+

It sounds like the dashboard demo at the o’reilly conference went pretty well. I suppose it will be getting even more attention now. Good stuff.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/lifestart.html b/snorp.net/content/blog/2003/lifestart.html new file mode 100644 index 00000000..6fe9fd86 --- /dev/null +++ b/snorp.net/content/blog/2003/lifestart.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "life.start()" + created: 2003-06-26 23:43:44 +%} + +{% block article %} +{%article%} + +

I moved into my new apartment yesterday. It was a fairly painless experience all in all. I don’t have any furniture yet (it’s coming this weekend), so that kind of sucks. Sleeping on the floor last night especially sucked.

+

Today was officially my first day working for Ximian. It went pretty well I think. Hopefully, I’ll be able to get up to speed quickly and start making useful contributions soon. I was able to get a mostly working development version of RCE on my machine today, which was about the only semi-productive thing I did. It’s clear I have a lot to learn, but I’m looking forward to it. Also, my first trip to Boston was arranged today. I’m really looking forward to that too. It’ll be totally cool meeting all the [ex-]gnomies and hopefully getting a good handle on my job.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/new-site.html b/snorp.net/content/blog/2003/new-site.html new file mode 100644 index 00000000..58386041 --- /dev/null +++ b/snorp.net/content/blog/2003/new-site.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "New Site" + created: 2003-06-16 18:54:37 +%} + +{% block article %} +{%article%} + +

I have snorp.net now. Hooray. Other than having snorp@snorp.net and this blog, I’m not sure why I really want my own domain. I guess I’ll use the webspace for posting screenshots and stuff, like I did with the IU webspace. Of course, I haven’t updated my blog on Advogato forever so I’ll probably end up ignoring this one too.

+

So, what’s been happening? I graduated in May. I’m pretty happy about that. I even found a job (!). I was able to land a 10 week contract job the week after I graduated. After I accepted it, though, another job fell in my lap. And not just any job, one at Ximian! I accepted it just when I was planning on leaving for California (the location of the contract job). Luckily, the guy who hired me for the contract was really cool about it (he had told me before that if I found a permanent job, he would have no problem with me bailing). I start at Ximian in a week or so, working on Red Carpet. Red Carpet is very cool, and I’m sure I’ll love working on it. WOO!!

+

In other news, I have been working on some code which lets users migrate windows to other screens or displays. So, for instance, if you have an app running on another machine you could simply pull it to your current display. A simpler usage might be to move a window between two screens on a non-xinerama multihead machine. I think it’s pretty cool. Nobody has said anything on xdg-list or wm-spec-list when I’ve posted about it, though, so maybe it’s not that cool? Anyway. I need to talk to Owen about it. Maybe he’ll let me put it in gtk+.

+

I finished the file monitoring system for the school project. It works ok for polling, but the dnotify backend is pretty buggy. If I can find some time to work on it, I think it may be ready in the GNOME 2.6 time frame.

+

Haven’t done much GNOME hacking generally, which pretty much sucks. I have been totally useless in the 2.4 cycle. I did fix a couple nautilus bugs (Yes Dave, 2 bugs!), so that’s good I guess. Still plenty of bug-fixing time to go, so maybe I’ll get my ass in gear one of these days.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/ok-ok-stop-hounding-me.html b/snorp.net/content/blog/2003/ok-ok-stop-hounding-me.html new file mode 100644 index 00000000..24466edf --- /dev/null +++ b/snorp.net/content/blog/2003/ok-ok-stop-hounding-me.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "ok, ok, stop hounding me" + created: 2003-07-03 02:22:59 +%} + +{% block article %} +{%article%} + +

Tomorrow will end my first week as a Ximian employee. So far, I’m feeling pretty useless, having done exactly one productive thing the whole week. I’m sure I’ll get the hang of things eventually, but, you know, I’m impatient.

+

4th of July is on Friday. My family sort of has standing plans with some friends of ours who live on a lake. So, the girlfriend and I are heading up there tomorrow night. It’s always a great time, and I’m looking forward to it again this year.

+

Talked with a guy over email about the recent-files spec (he wondered why I was using XML), and then today with Federico on IRC (OO.o stuff). It has been a long time since I touched that code. I need to get it in shape so I don’t miss yet another opportunity to get it in the platform (2.6). Actually, by then maybe I’ll be able to redo it as a set of D-Bus messages instead of specifying the storage mechanism. Seems like it would be far better.

+

Everyone and their dog is hacking on dashboard. It sounds pretty freaking cool.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/take-my-word-for-it.html b/snorp.net/content/blog/2003/take-my-word-for-it.html new file mode 100644 index 00000000..6ba11a79 --- /dev/null +++ b/snorp.net/content/blog/2003/take-my-word-for-it.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "take my word for it" + created: 2003-09-19 21:16:52 +%} + +{% block article %} +{%article%} + +

Do not, under any circumstances, see the movie “Cabin Fever”. Worst. Movie. Ever.

+

If you’re feeling brave, you should totally check out the latest red carpet client. It supports apt rpm repositories and multiple servers now. It’s very sweet. There is a fairly recent one in the ‘rcd-snaps’ channel if you want to try it.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/titles-suck.html b/snorp.net/content/blog/2003/titles-suck.html new file mode 100644 index 00000000..87444b35 --- /dev/null +++ b/snorp.net/content/blog/2003/titles-suck.html @@ -0,0 +1,21 @@ + +{% extends "_post.html" %} +{%hyde + title: "titles suck" + created: 2003-09-05 20:07:40 +%} + +{% block article %} +{%article%} + +

Yeah, so, it took me a while to get the metaWeblog support working with Seth’s blogger applet (as some of the planet gnome readers may have noticed). Seems to be working well now though.

+


+GNOME 2.4 is coming out soon. Best GNOME Ever. I don’t think I did anything, though, except fix a couple bugs. School really had me busy during the end…

+


+PyGTK 2.0 was released! This is great news! I have some libwnck bindings laying around that I’m going to try to get in now. I originally made them so I could easily test the window migration extensions to gtk/wnck I was working on. That kind of flopped though (I am lazy, plus the community did not seem very interested in it).

+


+Someone filed this totally sweet bug report against the recent-files stuff the other day.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/untitled-1.html b/snorp.net/content/blog/2003/untitled-1.html new file mode 100644 index 00000000..7e3fd7ed --- /dev/null +++ b/snorp.net/content/blog/2003/untitled-1.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "Untitled 1" + created: 2003-12-16 11:34:31 +%} + +{% block article %} +{%article%} + +

I’ve been hacking on the wireless applet a bit the last couple days. This is the result:
+

+

Right now it just lists the access points and the quality. It would be sweet if you could switch to another essid from there, etc.

+

Paolo has been working on recent-files stuff. I feel guilty for not helping him yet, so maybe I’ll work on that tonight.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/update.html b/snorp.net/content/blog/2003/update.html new file mode 100644 index 00000000..7aec26fc --- /dev/null +++ b/snorp.net/content/blog/2003/update.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "Update" + created: 2003-08-01 21:41:55 +%} + +{% block article %} +{%article%} + +

I would like to retract the “except Ian” remark made on July 20th. That is all.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/whats-that-on-your-face.html b/snorp.net/content/blog/2003/whats-that-on-your-face.html new file mode 100644 index 00000000..e5df4635 --- /dev/null +++ b/snorp.net/content/blog/2003/whats-that-on-your-face.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "what's that on your face?" + created: 2003-09-19 20:58:37 +%} + +{% block article %} +{%article%} + +

See, I knew I wouldn’t keep this thing updated.

+

Going to Boston again on Sunday. Mucho trabajo to do. Things have been going pretty well, though.


+A week or so ago I was messing around with creating panel applets in C#. Originally, I tried to wrap PanelApplet, but that didn’t go so well. I ended up writing some super hacky C glue to do the job. It worked pretty well. I think if I get a chance I might look at trying to wrap PanelApplet again. It really shouldn’t be that hard. After all, PanelApplet is pretty much a standard GObject.


+I started on a tutorial on writing nautilus context menus and property pages in python. I’ve wanted to do this for a while now. These components are super easy to write in python. Plus you can do hella cool stuff with them, so I think it may be attractive to newbies.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2003/wow-im-lame.html b/snorp.net/content/blog/2003/wow-im-lame.html new file mode 100644 index 00000000..164d62a8 --- /dev/null +++ b/snorp.net/content/blog/2003/wow-im-lame.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "Wow, I'm lame" + created: 2003-08-16 00:36:45 +%} + +{% block article %} +{%article%} + +

A heckuvalot has happened since my last post, I suppose. Notably, Novell acquired Ximian. As Dave said, some people were able to access the intranet today (I was not one of them). However, the org chart on the site shows me as reporting directly to the Vice Chairman of Novell. I don’t think Dave will be calling me a dorkwad anymore (I demoted him once already today).

+

I haven’t done any gnome hacking lately, and unfortunately I don’t see any happening in the near future. Work is keeping me pretty busy. Hopefully I’ll have some time after this development cycle ends.

+

Had a great time in Boston while I was there. In the conference call(s) today, I realized that I was able to put a face to nearly every voice I heard, which was a nice change.

+

The Rio portable ogg player which I’ve been salivating over for a while was finally anounced. I ordered one immediately. It’s going to kick so much ass. I’m a little worried about linux integration, though. Supposedly, the little bugger runs a webserver and has a java applet which you can use to transfer files to it. While this would probably work, what I would really like is a way to mount the drive or use a gnome-vfs module. The developers of the Karma are the same ones who made the empeg linux-based car mp3 player, so maybe they’ll be nice and release some code and/or specs.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/down-with-the-man.html b/snorp.net/content/blog/2004/down-with-the-man.html new file mode 100644 index 00000000..32959383 --- /dev/null +++ b/snorp.net/content/blog/2004/down-with-the-man.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Down with The Man" + created: 2004-05-17 03:35:22 +%} + +{% block article %} +{%article%} + +

So, I’ve ditched MovableType in favor of WordPress. I have to say I’m fairly impressed with it. The installation (including the MT import process) was incredibly simple and painless, save one problem with the dates for the imported entries which was hosing the RSS feed.

+

Hooray for Free Software.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/fud.html b/snorp.net/content/blog/2004/fud.html new file mode 100644 index 00000000..d43eacbb --- /dev/null +++ b/snorp.net/content/blog/2004/fud.html @@ -0,0 +1,21 @@ + +{% extends "_post.html" %} +{%hyde + title: "FUD" + created: 2004-05-21 17:45:52 +%} + +{% block article %} +{%article%} + +

Apparently Red Hat’s FUD campaign extends to more than just mono. From a computing.co.uk interview with Matthew Szulik:
+
+Why Red Hat versus Novell-SuSE or Sun JDS on the desktop?

+

They’re all proprietary except us. They all have proprietary technology inside, not 100 per cent open source software. They continue to lock customers in to limit choice.

+

If you buy the Sun desktop, you’re going to buy into the proprietary Sun architecture. With SuSE there’s Red Carpet, integrated with other Novell technologies, still proprietary.
+

+

I’m sorry, did I miss something? Was RHN recently freed unto the world? Sigh.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/guadec.html b/snorp.net/content/blog/2004/guadec.html new file mode 100644 index 00000000..8d99fe4a --- /dev/null +++ b/snorp.net/content/blog/2004/guadec.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "GUADEC" + created: 2004-06-29 22:10:59 +%} + +{% block article %} +{%article%} + +

I totally wanted to go to GUADEC, but couldn’t for various reasons (time and money, mostly). It was almost like being there, though, when I was able to make fun of Dave during his talk by proxy. Seriously. I think next year each room should have a projector showing a moderated IRC channel where people can ask questions.

+

Work has been going well lately, finally getting back into the groove after being pulled in different directions for a while.

+

Wedding plans are progressing, invitations went out last week. Really starting to hit home now :)

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/hacking.html b/snorp.net/content/blog/2004/hacking.html new file mode 100644 index 00000000..2cc3ba43 --- /dev/null +++ b/snorp.net/content/blog/2004/hacking.html @@ -0,0 +1,21 @@ + +{% extends "_post.html" %} +{%hyde + title: "Hacking" + created: 2004-05-15 14:47:46 +%} + +{% block article %} +{%article%} + +

Nautilus

+

I actually did some nautilus hacking recently. It had been a while, and it was pretty fun. I added desktop item editing to the property window.

+
+

It’s pretty simple. Way better than the UI in gnome-desktop-item-edit, IMO. Anyway, Dave branched nautilus the other day and that’s now in CVS. Now someone needs to fix the UI for adding new launchers/links :)

+

Work

+

I also added yum support to the Red Carpet daemon this week. This should be particularly interesting to Fedora users, since there seem to be a lot of apt and yum repositories for that (and rcd supports both now). I haven’t committed the patch yet due to some other stuff going on, but if anyone wants to try it just mail me.

+

Also, Petreley sucks. That is all.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/hmmm.html b/snorp.net/content/blog/2004/hmmm.html new file mode 100644 index 00000000..45e64143 --- /dev/null +++ b/snorp.net/content/blog/2004/hmmm.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "hmmm" + created: 2004-08-31 17:47:17 +%} + +{% block article %} +{%article%} + +

A month or two ago I was watching a (bogus) documentary about M. Night Shyamalan. This was the first time I’d ever seen what he looked like, and immediately, I realized something. I think maybe my boss is making blockbuster movies on the side. Here is my proof:

+
+

shymalan not shyamalan

+
+

Tell me those are different people. Seriously.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/lightning-sucks.html b/snorp.net/content/blog/2004/lightning-sucks.html new file mode 100644 index 00000000..61091448 --- /dev/null +++ b/snorp.net/content/blog/2004/lightning-sucks.html @@ -0,0 +1,22 @@ + +{% extends "_post.html" %} +{%hyde + title: "Lightning sucks." + created: 2004-06-16 22:10:02 +%} + +{% block article %} +{%article%} + +

Yesterday a thunderstorm passed through. There was lightning. Apparently some of it got close enough to fry some of my electrical things. Specifically, the stereo, gamecube, and wifi AP. Also, the laptop’s AC adapter was destroyed (but I have a new one thanks to IBM and all is well).

+

So today at lunch I went and bought a new wifi switch. I picked up a Linksys WRT54G, the one that has open firmware. So far it rules. No, I mean it totally rules. You should run, not walk, to the nearest Best Buy (or whatever). The open firmware lets you do local dns stuff. So you can have actual dns names for the machines on your LAN. This is the feature I have wanted most in these sort of boxes, and none of them appear to have it (except for this alternative firmware, from sveasoft). Also supposedly the sveasoft firmware has other neat stuff like a VPN server.

+

Red Carpet

+

Also I’ve worked the rest of the bugs out of the yum support for rcd. I think it’s pretty solid now. It even avoids downloading the headers you already have (much like yum itself does). I should commit it soon. I know Shaver and Vlad have been using rcd on FC2, so it might be nice to get some of the yum repos that don’t also have apt ones.

+

I’ve been thinking about writing a tool that creates an open carpet repo out of a yum or apt one. That way apt/yum repo maintainers can just run this magical script without having to do the (relatively painless) process of setting up an open-carpet repo ‘from scratch’. I wonder if something like this would help open-carpet adoption?

+

Oh, I forgot to blog about this earlier, but anyone that has seen the “rcd eats 99% of my cpu” bug will be happy to know that the latest release fixes it. If you use rcd and see this bug in the latest release please report it. I promise I will hunt it down and kill it dead. Either that or I will get smart people like Tambet or Dan Winship to do it for me (who fixed it in the first place).

+

GNOME

+

A long time ago (like a year or more) I worked on some code that allowed you to migrate windows from one display/screen to another. Basically it was just some X message passing stuff that ended up calling gtk_window_set_screen(). I wrote a spec for it and posted it to wm-spec-list, but nobody really seemed that interested. I’ve started working on it again, and I think I’ll give the spec/patch another go. There is lots of badness in gtk+ with closing displays, and that sucks. Also there doesn’t seem to be an async way of opening a display, so if you try to migrate a window to someplace that’s not listening on the right port or whatever, it blocks the GUI.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/lots-of-stuff.html b/snorp.net/content/blog/2004/lots-of-stuff.html new file mode 100644 index 00000000..4dc88db9 --- /dev/null +++ b/snorp.net/content/blog/2004/lots-of-stuff.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Lots of Stuff" + created: 2004-03-26 13:48:46 +%} + +{% block article %} +{%article%} + +

So, haven’t blogged in a pretty long time. Lots has happened. Most notably, I got engaged to my long-time girlfriend, Amy! We set the date for July. I’m really happy! :)

+

Just got back from BrainShare. It was pretty cool. I took some pics, but I don’t have them pulled off the camera yet. I think we were all kind of in awe about the number of Large Monkey Buttons plastered all over :)

+

the floor near the mono booth
+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/more-wireless-stuff.html b/snorp.net/content/blog/2004/more-wireless-stuff.html new file mode 100644 index 00000000..c27e80e8 --- /dev/null +++ b/snorp.net/content/blog/2004/more-wireless-stuff.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "more wireless stuff" + created: 2004-01-24 18:56:39 +%} + +{% block article %} +{%article%} + +

Spent some time today and yesterday hacking on the wireless applet some more. Now you can double-click a network to switch to it (sets the essid and gets a dhcp lease). I was thinking of what the applet should do while its in the process of switching. Getting a IP can take some time. For now, at least, I just made it insensitive with a “switching…” message.

+
+

Anyway, I finally sent a patch to Jean today (the wireless-tools maintainer) for the scanning stuff. Hopefully it’ll make it into the next wireless-tools release. It’s a shame I didn’t get this stuff done before the feature freeze….

+

I also spent a few minutes on recent-files stuff this week. I reverted the (unstable and unfinished) changes I made last year, and applied a patch from bugzilla that fixed some NFS file locking issues. I think by gnome 2.8, dbus will have matured and maybe we’ll be able to rewrite recent-files as a dbus service instead of the file spec we have now….

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/new-place.html b/snorp.net/content/blog/2004/new-place.html new file mode 100644 index 00000000..09fb653f --- /dev/null +++ b/snorp.net/content/blog/2004/new-place.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "new place" + created: 2004-07-16 04:52:02 +%} + +{% block article %} +{%article%} + +

I moved today. It went pretty well. Amy and her parents helped out a lot so that was good. Rupert is back online so that made people happy.

+

A guy (Darren Brierton) has been mailing me lately about rcd on fc2, and eventually he told me he wanted to get rcd into fedora. He totally rules.

+

9 days till wedding :)

+

Update: I committed mono bindings for libredcarpet to CVS this week. Ph33r.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/this-just-in.html b/snorp.net/content/blog/2004/this-just-in.html new file mode 100644 index 00000000..b499c53e --- /dev/null +++ b/snorp.net/content/blog/2004/this-just-in.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "THIS JUST IN" + created: 2004-08-10 20:19:43 +%} + +{% block article %} +{%article%} + +

I hate Todd.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/updated-wireless-stuff.html b/snorp.net/content/blog/2004/updated-wireless-stuff.html new file mode 100644 index 00000000..8349bde8 --- /dev/null +++ b/snorp.net/content/blog/2004/updated-wireless-stuff.html @@ -0,0 +1,23 @@ + +{% extends "_post.html" %} +{%hyde + title: "Updated Wireless Stuff" + created: 2004-05-17 19:48:26 +%} + +{% block article %} +{%article%} + +

I took some time today to update my wireless patches to latest GNOME CVS, and the latest development release of wireless-tools. If you would like to try it, here are instructions:

+
    +
  1. Get the latest wireless-applet and wireless-tools patches
  2. +
  3. Get the latest wireless-tools (version 27pre22) and apply iwlib_jwillcox_scanning_27pre22.diff to it, install it, etc.
  4. +
  5. Unpack the wireless-applets patch tarball thingy in the root of the gnome-applets tree. Apply the gnome_applets_jwillcox_wireless_v4.diff patch. Run autogen, make, etc.
  6. +
  7. Enjoy.
+

This still only works on Red Hat systems due to the usage of consolehelper. You can fix that by putting appropriate root-getting-and-essid-setting-and-renew-dhcp-lease bits for your distro in the wireless-applet-helper script.

+

Update:

+

Screenshot for those of you who haven’t seen it.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/wedding-photos.html b/snorp.net/content/blog/2004/wedding-photos.html new file mode 100644 index 00000000..db12513e --- /dev/null +++ b/snorp.net/content/blog/2004/wedding-photos.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "wedding photos" + created: 2004-08-27 02:11:12 +%} + +{% block article %} +{%article%} + +

So, we finally got some wedding photos back. I think this one is nice :)

+
amy in wedding gown
+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/whirl2il.html b/snorp.net/content/blog/2004/whirl2il.html new file mode 100644 index 00000000..d23b1d82 --- /dev/null +++ b/snorp.net/content/blog/2004/whirl2il.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "whirl2il" + created: 2004-07-20 22:25:56 +%} + +{% block article %} +{%article%} + +

Kris “released” his whir2il code today. I haven’t tried it yet, but the results he has had with it so far are very promising. PInvoke is great and all, but being able to actually compile existing C/C++ code to bytecode is just so freaking sweet.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/whoa-deja-vu.html b/snorp.net/content/blog/2004/whoa-deja-vu.html new file mode 100644 index 00000000..f886561e --- /dev/null +++ b/snorp.net/content/blog/2004/whoa-deja-vu.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "whoa, deja vu" + created: 2004-08-22 14:27:36 +%} + +{% block article %} +{%article%} + +

I really wish they would stop saying things like this:

+

Red Hat’s software technology is 100 percent open source. If you want to put us in a box, then the most obvious box is that Red Hat only, exclusively sells and supports open source — GPL’d software.

+

+

Quite frankly, if tomorrow we decided to release a closed piece of software, literally half the engineers would quit on principal that day.

+

That’s Paul Salazar, European Marketing Director for Red Hat. Can someone please mail me an URL to the source code for Satellite?

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/yet-more-wireless-applet-stuff.html b/snorp.net/content/blog/2004/yet-more-wireless-applet-stuff.html new file mode 100644 index 00000000..3ccd17e5 --- /dev/null +++ b/snorp.net/content/blog/2004/yet-more-wireless-applet-stuff.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Yet More Wireless Applet Stuff" + created: 2004-02-16 17:42:41 +%} + +{% block article %} +{%article%} + +

So, more wireless applet hacking today. This time I fixed the build so it installs all the nifty pam magic. Patches for all. Apply this patch to the latest wireless-tools development version, and unpack/apply this one to gnome-applets CVS. You’ll want to pass something like —sysconfdir=/etc to configure to get the pam stuff working.

+

[Disclaimer] I have only tested this on my own machine, which is running Fedora Core 1. Other RedHat systems will probably work. Basically, “ESSID=<essid> /sbin/ifup <interface>” needs to work, and you need consolehelper.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/you-want-me-to-give-you-what.html b/snorp.net/content/blog/2004/you-want-me-to-give-you-what.html new file mode 100644 index 00000000..29d89dae --- /dev/null +++ b/snorp.net/content/blog/2004/you-want-me-to-give-you-what.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "You want me to give you what?!" + created: 2004-06-18 03:56:40 +%} + +{% block article %} +{%article%} + +

There is this intersection near my apartment where quite frequently there will be someone begging for money or something. Usually they have a cardboard sign with stuff like “hungry need food” on it. Now, I’m all in favor of helping people if they really need it, but today there was a guy LISTENING TO A GOD DAMN IPOD WHILE BEGGING FOR CASH. Sigh.

+

So, I committed my yum support to rcd today. It rules. Snapshot builds will have them soon (tomorrow morning I guess). You can simply subscribe to the ‘rcd-snaps’ channel and do a ‘rug up’ to get it. The yum support works a lot like the apt support, so you need to use something like open-carpet.org to get access to yum repos. If people have some they would like to see in open-carpet.org, mail me and I’ll add it.

+

I also committed some performance improvements that will help a lot if you’re using a large package repository like open-carpet.org. Yay.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2004/zmd.html b/snorp.net/content/blog/2004/zmd.html new file mode 100644 index 00000000..738def27 --- /dev/null +++ b/snorp.net/content/blog/2004/zmd.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "zmd" + created: 2004-09-02 17:03:41 +%} + +{% block article %} +{%article%} + +

In the last few months, Tambet and I have been working on the successor to rcd called zmd (ZENworks Management Daemon). It is written in C#. I did not choose the name.

+

Things are going pretty well, and a lot of stuff is starting to work. We have an xmlrpc compatability layer for implementing the old rcd methods (just enough to make the old GUI work), and a remoting layer for the new clients. The xmlrpc stuff is pretty much complete, and you can use the old rug/red-carpet apps with it to install packages, etc. Also we’re writing a new ‘rug’ in C#. You can see it in action here. In that shot, it’s installing some stuff from the ‘funktronics’ aptrpm repository.

+

I don’t have any code to share yet, but hopefully that will happen soon.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/avahi-sharp.html b/snorp.net/content/blog/2005/avahi-sharp.html new file mode 100644 index 00000000..6d0bc69d --- /dev/null +++ b/snorp.net/content/blog/2005/avahi-sharp.html @@ -0,0 +1,48 @@ + +{% extends "_post.html" %} +{%hyde + title: "avahi-sharp" + created: 2005-09-08 15:57:08 +%} + +{% block article %} +{%article%} + +

I spent some time last night hacking up some Avahi bindings for C#, using the DBus API. The wrapper is here, and a little test app is here. The test app registers a ‘foobar’ service, and then lists all services in the default (I think?) domain. Obligatory ‘screenshot’ below. Unfortunately, I ran into some dbus-sharp bugs while doing this. You’ll need this patch to dbus-sharp in order to use it. You can also grab a dll here.

+

+snorp@sackbut misc/avahi/avahi-sharp % mono test.exe +Service 'Living Room' at DVR-8477.local:80 +Txt: TSN=54000000000000 +Resolved DVR-8477.local to 192.168.1.105 +Reverse resolved 192.168.1.105 to DVR-8477.local +Service 'Now Playing on Living Room' at DVR-8477.local:443 +Txt: TSN=54000000000000 +Resolved DVR-8477.local to 192.168.1.105 +Reverse resolved 192.168.1.105 to DVR-8477.local +Service 'Now Playing on Living Room' at DVR-8477.local:443 +Txt: TSN=54000000000000 +Resolved DVR-8477.local to 192.168.1.105 +Reverse resolved 192.168.1.105 to DVR-8477.local +Service 'iTunes_Ctrl_60AA03D0FEE58A7F' at homer.local:3689 +Txt: DbId=10000 +Resolved homer.local to 192.168.1.103 +Reverse resolved 192.168.1.103 to homer.local +Service 'snorp’s Music' at homer.local:3689 +Txt: Password=false +Resolved homer.local to 192.168.1.103 +Reverse resolved 192.168.1.103 to homer.local +Service 'Remote Terminal on sackbut' at sackbut.local:22 +Resolved sackbut.local to 192.168.1.101 +Reverse resolved 192.168.1.101 to sackbut.local +Service 'sackbut [00:0d:60:36:95:4d]' at sackbut.local:9 +Resolved sackbut.local to 192.168.1.101 +Reverse resolved 192.168.1.101 to sackbut.local +Service 'foobar' at sackbut.local:8080 +Txt: +Resolved sackbut.local to 192.168.1.101 +Reverse resolved 192.168.1.101 to sackbut.local +

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/cd-burning-for-muine.html b/snorp.net/content/blog/2005/cd-burning-for-muine.html new file mode 100644 index 00000000..86a40e35 --- /dev/null +++ b/snorp.net/content/blog/2005/cd-burning-for-muine.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "CD burning for Muine" + created: 2005-06-08 22:01:16 +%} + +{% block article %} +{%article%} + +

I fixed up fer‘s muine-burn plugin recently. It now works with the latest muine, and recent libnautilus-burn. Surprisingly little was needed to get it into working condition again. AFAIK, the plugin wasn’t checked into version control anywhere, so I committed the changes into my baz archive. I also put up a tarball here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/firefox-rules.html b/snorp.net/content/blog/2005/firefox-rules.html new file mode 100644 index 00000000..cd9174f4 --- /dev/null +++ b/snorp.net/content/blog/2005/firefox-rules.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Firefox Rules" + created: 2005-12-01 20:47:40 +%} + +{% block article %} +{%article%} + +

So Firefox 1.5 is out, sporting a new canvas tag. Hopefully we will see all kinds of sweet innovative stuff using it. Here is my contribution:

+

+

Update: I also changed my little bugzilla greasemonkey script to work with Firefox 1.5. You can get that at the usual place, here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/grumpy-sneezy-sleepy.html b/snorp.net/content/blog/2005/grumpy-sneezy-sleepy.html new file mode 100644 index 00000000..ac373474 --- /dev/null +++ b/snorp.net/content/blog/2005/grumpy-sneezy-sleepy.html @@ -0,0 +1,20 @@ + +{% extends "_post.html" %} +{%hyde + title: "Grumpy, Sneezy, Sleepy, ..." + created: 2005-06-21 20:41:33 +%} + +{% block article %} +{%article%} + +

Several people have asked for a little standalone app that uses ipod-sharp, since not everyone uses Muine and keeps all of their music on their PC. So this weekend I wrote Dopi.
+


+

+Dopi, after adding the album ‘Mezmerize’

+
+

You simply DnD folders/files onto the song list there to add stuff. The delete key deletes (surprise!). To try it, you’ll need the latest ipod-sharp. It has been moved into Mono’s SVN, so if you have the stuff from baz, that is out-of-date. Also, ipod-sharp depends on libipoddevice in GNOME CVS, so you’ll need that too. And lastly, Dopi itself is in my baz archive, here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/hula-hula-hula.html b/snorp.net/content/blog/2005/hula-hula-hula.html new file mode 100644 index 00000000..67478cb4 --- /dev/null +++ b/snorp.net/content/blog/2005/hula-hula-hula.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Hula Hula Hula" + created: 2005-02-15 19:39:01 +%} + +{% block article %} +{%article%} + +

+

I didn’t want to be left out.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/ipod-sharp.html b/snorp.net/content/blog/2005/ipod-sharp.html new file mode 100644 index 00000000..6cb813c8 --- /dev/null +++ b/snorp.net/content/blog/2005/ipod-sharp.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "ipod-sharp" + created: 2005-01-25 07:15:22 +%} + +{% block article %} +{%article%} + +

I just committed ipod-sharp to CVS, a library for manipulating iTunesDBs. It’s written entirely in C#. Currently you can use it to add/remove songs, and manipulate any existing playlists. There is also a cheesy little tool that lists the songs/playlists in a given iTunesDB.

+

I mostly wrote it to add iPod syncing to Muine. I have a patch which does this, but it needs a lot of work still. I haven’t yet totally worked out how the HAL integration will happen, for instance. For starters, I patched gnome-volume-manager to mount the appropriate partition when it sees an iPod and invoke ‘muine —ipod-sync ’. But that doesn’t cover unmounting. Also apparently you need to ‘eject’ the device for the iPod to put up the happy “ok to disconnect” screen.

+

You can get the code from the ‘ipod-sharp’ module in gnome CVS, or here. Hopefully I’ll get the Muine patch in a useable state soon.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/ipod-syncing-for-muine.html b/snorp.net/content/blog/2005/ipod-syncing-for-muine.html new file mode 100644 index 00000000..08071770 --- /dev/null +++ b/snorp.net/content/blog/2005/ipod-syncing-for-muine.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "iPod syncing for Muine" + created: 2005-05-11 15:02:03 +%} + +{% block article %} +{%article%} + +

A while ago I started working on a plugin for Muine that syncs your library with an iPod. I worked on it a bit more lately and it seems to be coming along, so I’m trying to get people to test it. You need this and this, to start.

+

Right now there is no HAL integration, as I’m having an incredibly difficult time figuring out the correct way to integrate with that stuff. What it will do, however, is mount/umount your iPod assuming it is setup correctly in fstab (correct device, ‘user’ option, etc). It defaults to /media/ipod for the mount point, but that is configurable through a gconf key (/apps/muine/ipod/mount_path).

+

I’ve been using it for the last few days with no serious problems. I do suggest you backup your iTunesDB file before giving it a shot, though, as corrupting that is the worst thing that can happen. You can find it at /media/ipod/iPod_Control/iTunes/iTunesDB. If you encounter problems, feel free to email me.

+

Update: You will need muine 0.8.3 or greater to use this plugin, as previous versions lack the necessary interface.

+

Another Update: I’ve checked ipod-sharp and muine-ipod into arch at http://www.snorp.net/bazaar.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/more-muine-burning.html b/snorp.net/content/blog/2005/more-muine-burning.html new file mode 100644 index 00000000..0a8ed78f --- /dev/null +++ b/snorp.net/content/blog/2005/more-muine-burning.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "More muine burning" + created: 2005-06-10 22:38:08 +%} + +{% block article %} +{%article%} + +

I worked some more on the muine-burn plugin. It should be far more stable now. So if you tried the last one and had problems, give this a shot. Tarballs are here and here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/more-muineipod.html b/snorp.net/content/blog/2005/more-muineipod.html new file mode 100644 index 00000000..33d1f647 --- /dev/null +++ b/snorp.net/content/blog/2005/more-muineipod.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "more muine/iPod" + created: 2005-05-20 14:13:35 +%} + +{% block article %} +{%article%} + +

Martin Palma has gone to the work of packaging muine-ipod and ipod-sharp for Ubuntu hoary. You can get them here.

+

Work progresses. I’ve still been getting used to bazaar, which I’m using for version control. Overall I really like it. I want to start learning some of the more advanced features, though.

+

The latest muine-ipod snapshot has support for optionally syncing only the current playlist to the iPod. I’ve been told this works pretty well for shuffle users. I still think we need some way to individually mark songs in the library for syncing (similar to what iTunes does I think?). iPod Mini users would especially benefit from this, since they probably can’t sync their entire library — and composing a playlist would just be a bit ridiculous (6gb of music in a playlist!).

+

Also, I sent a patch to muine-list this week that added a plugin for inotify support. It simply monitors the directories you’ve added to muine, and if something gets added/removed/changed it takes the appropriate action on the song library. I’ve wanted this kind of behavior in a music player for years, and now that inotify has come along it’s finally possible. I’ve been using rml’s inotify kernel for SuSE 9.3, and it’s working quite well. You can get the patch + plugin here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/nokiamaemo.html b/snorp.net/content/blog/2005/nokiamaemo.html new file mode 100644 index 00000000..3f4385da --- /dev/null +++ b/snorp.net/content/blog/2005/nokiamaemo.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Nokia/Maemo" + created: 2005-05-25 19:42:51 +%} + +{% block article %} +{%article%} + +

Like everyone else, I am totally stoked about the new Nokia 770. The hardware looks sweet, but the fact that it is coupled with a sane open development platform just makes it ridiculously attractive. Especially considering it’s based on a platform we all know relatively well :)

+

The screenshots look really great. One thing I wonder about, though, is how well the included apps deal with an unreliable network. I mean, if I stroll out of range from my wifi, will all kinds of errors and stuff show up, or will it deal gracefully? The development site mentions some things about getting events like “low battery” over dbus. Hopefully “network is down” is the same kind of thing, and apps can just go into offline mode or whatever.

+

It would be really fabulous to get mono running on this thing. I wonder if something like a “compact edition” of it would be needed.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/rcd-for-fedora-core-3.html b/snorp.net/content/blog/2005/rcd-for-fedora-core-3.html new file mode 100644 index 00000000..9817eff0 --- /dev/null +++ b/snorp.net/content/blog/2005/rcd-for-fedora-core-3.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "rcd for fedora core 3" + created: 2005-03-30 01:59:54 +%} + +{% block article %} +{%article%} + +

A volunteer has packaged rcd/rug for fc3, and I have put the rpms here. Enjoy.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/utah.html b/snorp.net/content/blog/2005/utah.html new file mode 100644 index 00000000..9bee2e59 --- /dev/null +++ b/snorp.net/content/blog/2005/utah.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "Utah" + created: 2005-06-03 23:38:10 +%} + +{% block article %} +{%article%} + +

I was in Utah last week for work. It was good to see people, put some faces with names, etc. Working from home has benefits, but it’s nice to do things in person sometimes. I was in the SuperLab, testing ZENworks (of course). It was pretty cool, and I’m quite sure I’ve never seen more computers in a single place before.
+


+
Novell SuperLab: one of a bajillion rows

+Haven’t done any hacking on Muine lately. Partially, because it won’t even run for me currently (some kind of lame gtk-sharp exception). One thing I was thinking of doing, is resurrecting fer’s CD burning plugin. I don’t usually take the iPod with me in the car on short trips, and I’ve wanted an easy way to burn stuff on a CD to use there.

+

Enjoyed reading GUADEC-goers blogs. Maybe one of these days I’ll be able to go. If there’s another Boston summit, I should definitely try to make it to that.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2005/vengeance-is-mine.html b/snorp.net/content/blog/2005/vengeance-is-mine.html new file mode 100644 index 00000000..fb6c732a --- /dev/null +++ b/snorp.net/content/blog/2005/vengeance-is-mine.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "vengeance is mine" + created: 2005-10-14 03:29:11 +%} + +{% block article %} +{%article%} + +

If you have ever used the Novell Bugzilla you no doubt noticed that it likes to log you out after a short while. Usually for me it’s at least two additional clicks after clicking on a bug link before I can actually see the bug. It annoyed me enough tonight that I wrote a greasemonkey script to ease the pain. You can get it here. Just log in once manually after loading the script so it can store your user/pass, and it should do it for you after that.

+

Update: I just put a newer version of the script up. It will log you in even if the page you’re trying to view is not locked out to anonymous users. Also added some lame feedback so you know what it’s doing.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/allegedly-very-tasty.html b/snorp.net/content/blog/2006/allegedly-very-tasty.html new file mode 100644 index 00000000..b691ff49 --- /dev/null +++ b/snorp.net/content/blog/2006/allegedly-very-tasty.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "allegedly very tasty" + created: 2006-05-05 15:03:44 +%} + +{% block article %} +{%article%} + +

Last night I did a lot of work on Tangerine, a standalone DAAP server of mine. I think it’s probably good enough for general use now, as I added a little control panel for enabling/configuring it.

+
+

The ‘automatic’ mode uses Beagle, which was really fun to do. Aside from some kind of metadata extraction issue, it works really well.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/apple-sucks.html b/snorp.net/content/blog/2006/apple-sucks.html new file mode 100644 index 00000000..d7ffe80c --- /dev/null +++ b/snorp.net/content/blog/2006/apple-sucks.html @@ -0,0 +1,24 @@ + +{% extends "_post.html" %} +{%hyde + title: "Apple Sucks" + created: 2006-06-18 17:38:36 +%} + +{% block article %} +{%article%} + +

A couple weeks ago, I spent some time making Tangerine work on Windows. After I got it working, I started looking into creating an installer and all that stuff. Aaron recommended Inno Setup, so I got that and went to work. I pretty quickly had a basic package working, but it needed to handle installing the various dependencies still.

+

Microsoft has a neat little bootstrapping utility which you can include in your installer to make sure various components (such as .NET 2.0) are installed, so that bit was easy.

+

The other dependency it needed to install was Apple’s Bonjour, so I start poking around on Apple’s site to see if they have some kind of cute installer for it. I discover that they do, but they require you to get some kind of license from Apple. Ok, how bad could it be? I start to look through it. There is the normal legal crap, blah blah blah, then I hit the real requirements. I am not a lawyer, but my understanding is that I would need to do the following things in order to distribute my Bonjour-using application (not just their installer):

+
    +
  • Provide Apple with 2 samples of the application, on physical media, delivered to them 4 weeks before each release.
  • +
  • Provide quarterly reports on the number of Bonjour copies distributed in the previous quarter.
  • +
  • Use the Bonjour logo on any manuals included with the application
  • +
+

I am sure a more astute reader could probably find more nastiness. No wonder there aren’t any Windows apps out there using Bonjour. If Apple really doesn’t want people to use it, why don’t they just come out and say it? We need to port Avahi to Windows and crush them into obsolescence.

+

Update: Luis has added that they can also change the software or license at any point without warning and force me to use that. Nasty.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/blowing-up-pluto.html b/snorp.net/content/blog/2006/blowing-up-pluto.html new file mode 100644 index 00000000..f93b4377 --- /dev/null +++ b/snorp.net/content/blog/2006/blowing-up-pluto.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "blowing up Pluto" + created: 2006-08-25 20:08:27 +%} + +{% block article %} +{%article%} + +

hpj: I am in favor of this idea. We could even use a few of the ~10k nuclear weapons!

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/daap-sharp-on-windows.html b/snorp.net/content/blog/2006/daap-sharp-on-windows.html new file mode 100644 index 00000000..dae30073 --- /dev/null +++ b/snorp.net/content/blog/2006/daap-sharp-on-windows.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "daap-sharp on Windows" + created: 2006-04-08 02:17:48 +%} + +{% block article %} +{%article%} + +

I rolled a new release of daap-sharp tonight, with the main new benefit being that it now runs on Windows. After Aaron added Bonjour bindings, there wasn’t much stopping this from happening, so I fixed up the last couple of bugs. I also made an ultra-cheesy test application using Windows Forms:

+

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/flaming-skulls.html b/snorp.net/content/blog/2006/flaming-skulls.html new file mode 100644 index 00000000..85aaddb7 --- /dev/null +++ b/snorp.net/content/blog/2006/flaming-skulls.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Flaming Skulls!" + created: 2006-10-20 14:04:35 +%} + +{% block article %} +{%article%} + +


+

+

I did a jack-o-lantern this week for the first time in probably 15 years. I am happy with the result.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/fruity.html b/snorp.net/content/blog/2006/fruity.html new file mode 100644 index 00000000..36b9527d --- /dev/null +++ b/snorp.net/content/blog/2006/fruity.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "Fruity" + created: 2006-07-21 15:43:23 +%} + +{% block article %} +{%article%} + +

I released Tangerine 0.2.6, which includes some minor bug fixes. Also now that SLED 10 is out, I can do a screencast showing Tangerine running under it. So I did, and you can see it here.

+

Update: It was suggested to me that a screenshot might be needed too, so here you go :)
+


+

+The control panel for Tangerine

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/gnome-1-slashdot-0.html b/snorp.net/content/blog/2006/gnome-1-slashdot-0.html new file mode 100644 index 00000000..4aaced26 --- /dev/null +++ b/snorp.net/content/blog/2006/gnome-1-slashdot-0.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "GNOME: 1, Slashdot: 0" + created: 2006-03-28 23:33:32 +%} + +{% block article %} +{%article%} + +

Recently, Jeff asked the Slashdot guys to stop using GNOME’s 700 year old logo in favor of the new one. They have not changed it. I have a solution.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/im-in-ur-virtual-machines-managing-them.html b/snorp.net/content/blog/2006/im-in-ur-virtual-machines-managing-them.html new file mode 100644 index 00000000..277415f8 --- /dev/null +++ b/snorp.net/content/blog/2006/im-in-ur-virtual-machines-managing-them.html @@ -0,0 +1,22 @@ + +{% extends "_post.html" %} +{%hyde + title: "I'm in ur virtual machines, managing them" + created: 2006-12-09 00:37:43 +%} + +{% block article %} +{%article%} + +

I’ve been working on an application recently which lets you create, configure, and run VMware virtual machines (it just forks out to VMware Player for the running part). It’s nearing usefulness now, so I thought I’d post some screenshots and stuff.

+
+


+The main window

+


+The configuration window

+
+

You need either qemu or vmware-vdiskmanager installed in order to create new hard disks, and it probably fails pretty badly currently if you don’t. If I get a chance, I may write my own stuff to create the hard disks (the format is a public spec, woo!). Anyway, you can get it from the vmx-manager module in gnome CVS.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/itunes-7.html b/snorp.net/content/blog/2006/itunes-7.html new file mode 100644 index 00000000..2cbb00bf --- /dev/null +++ b/snorp.net/content/blog/2006/itunes-7.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "iTunes 7" + created: 2006-09-12 23:11:19 +%} + +{% block article %} +{%article%} + +

iTunes 7 was announced today, which means more ipod-sharp breakage. It doesn’t look like anything really changed except the database version, so I’ve bumped that and released 0.6.1. The music sharing stuff in iTunes hasn’t changed for ages, so Tangerine still works fine.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/maybe-theyre-not-as-bad-as-i-thought.html b/snorp.net/content/blog/2006/maybe-theyre-not-as-bad-as-i-thought.html new file mode 100644 index 00000000..1d1ec6f3 --- /dev/null +++ b/snorp.net/content/blog/2006/maybe-theyre-not-as-bad-as-i-thought.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Maybe they're not as bad as I thought" + created: 2006-09-18 16:03:02 +%} + +{% block article %} +{%article%} + +

The iPod situation is now mostly fixed. I was able to figure out how iTunes gets the iPod serial number (and other info) with the help of a USB monitoring tool and Matt Dharm (the usb-storage guy). After that, Aaron used the metric ton of SysInfo samples you guys sent in to get a serial number → model number mapping.

+

To get info about (recent) device, iTunes requests an xml document from it over USB. I saw this in the USB trace I had, but the data was nowhere on the disk exposed by USB Mass Storage. Puzzling. Not really knowing much about USB MC, I enlisted the help of Matt, who obviously knows a lot more. He informed me that it was requesting the data using a special SCSI INQUIRY command, and I could probably use SG_IO to get it out. I ran sg_inq on my iPod with the right parameters, and out came the xml. Success! After that I wrote a hal method for libipoddevice to pull the info out (since you need to be root), and the rest was just parsing the xml and doing the serial → model mapping.

+

Even though it was a fairly painful couple of days trying to fix this, I’m pretty happy with the result. The xml from the device includes some really tasty stuff, including information on the image formats for cover art and photos. We are using this data now in ipod-sharp instead of the static table we had before, which will be really great for maintenance. New iPods with previously unknown image formats will Just Work! There is also data on the video formats, which I will soon use to add video support to Dopi

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/more-vmx-manager-stuff.html b/snorp.net/content/blog/2006/more-vmx-manager-stuff.html new file mode 100644 index 00000000..04fc6c22 --- /dev/null +++ b/snorp.net/content/blog/2006/more-vmx-manager-stuff.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "more vmx-manager stuff" + created: 2006-12-18 17:56:42 +%} + +{% block article %} +{%article%} + +

I’m still working hard on vmx-manager, and I think it’s coming along pretty well. I spent a good chunk of last week writing my own code for creating VMware Virtual Disks. The VMDK specification is pretty straightforward, but implementing it proved to be more tedious than I expected (“the devil is in the details”). The end result seems to work pretty well, so now vmx-manager can create disks without relying on qemu or vmware-vdiskmanager. Soon I might try to add a couple more features in this area, such as the ability to grow an existing disk (which in theory should be easy — just add extents). I’ve made a screencast of the app as I put it through its paces, and you can find up-to-date screenshots here.

+

When I was writing the flat extent support (used for pre-allocated disks), I wanted to do something different than just writing a bunch of zeros out to a file (which is slow). It seemed to me that it should be possible to ask the filesystem to quickly give me a file of a specific size. I didn’t care what was in it, so it should be able to just find a bunch of unused sectors (or whatever) and mark them as mine, right? I was able to find no such feature in ext3 or Linux in general, and I guess the reason is probably due to security concerns. You obviously don’t want to give people a way to read deleted data. It would be nice, though, if the fs could mark the data in such a way that it would be zeros until you write to it. Maybe that’s just too expensive, I don’t know. Anyway, if anyone knows how I could accomplish such a thing, please let me know.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/muthafuckin-tangerines-on-a-muthafuckin-planewwindows.html b/snorp.net/content/blog/2006/muthafuckin-tangerines-on-a-muthafuckin-planewwindows.html new file mode 100644 index 00000000..96ddd340 --- /dev/null +++ b/snorp.net/content/blog/2006/muthafuckin-tangerines-on-a-muthafuckin-planewwindows.html @@ -0,0 +1,21 @@ + +{% extends "_post.html" %} +{%hyde + title: "muthafuckin' tangerines on a muthafuckin' plane^Wwindows" + created: 2006-08-20 19:04:24 +%} + +{% block article %} +{%article%} + +

This weekend I spent some more time making Tangerine work on Windows. I built Bonjour from scratch so I could distribute the binaries, and polished up the Tangerine installer as well. I gave it a good deal of testing, but there might be something I missed still. The Windows version includes a plugin for finding songs automatically through Google Desktop.

+
+



+The preferences window for Tangerine (under Windows)


+

Download Now!

+

Update: I just put up a new version which fixes a bunch of issues I found. It also adds the missing features to the config window and hopefully makes it look a little nicer. Updated screenshot above.

+

Update 2: Another updated version up with more bug fixes. I don’t have a bug tracker set up, but if you guys hit any problems just email me (snorp@snorp.net).

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/photos-and-cover-art-in-ipod-sharp.html b/snorp.net/content/blog/2006/photos-and-cover-art-in-ipod-sharp.html new file mode 100644 index 00000000..6ceccfa5 --- /dev/null +++ b/snorp.net/content/blog/2006/photos-and-cover-art-in-ipod-sharp.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "photos and cover art in ipod-sharp" + created: 2006-06-29 20:17:17 +%} + +{% block article %} +{%article%} + +

I’ve been working on ipod-sharp recently to add cover art and photo support. It’s nearly complete now, and I can add and remove art and photos on my video iPod. Another person has tried it successfully on his Nano, so all that’s left to test is a regular iPod Photo. There might be some issues to work out yet with the camera adaptor too. A big thanks to Larry who did the initial work and provided the scary image conversion code :)

+

I’ve added cover art support to Dopi in svn. It uses Cover.jpg (or cover.jpg or folder.png, etc) if one is present next to the files when you add them. I suppose one of these days I should add a crappy “track properties” window so you can view/change the cover art there.

+

I’ve also created a patch for Banshee to make it use the new ipod-sharp and sync cover art. To try it you’ll need ipod-sharp and libipoddevice from svn/cvs.

+

Oh I also converted ipod-sharp to use gmcs. Hooray for generics.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/sleep-at-last.html b/snorp.net/content/blog/2006/sleep-at-last.html new file mode 100644 index 00000000..fed3269e --- /dev/null +++ b/snorp.net/content/blog/2006/sleep-at-last.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "sleep at last" + created: 2006-05-02 04:55:26 +%} + +{% block article %} +{%article%} + +

For the first time ever, I was able to successfully suspend my laptop (IBM T40p) to RAM. For the longest time I was having a problem where the disk would go completely insane upon resuming. Very annoying.

+

I found out recently that it was probably due to the BIOS re-enabling HPA (Host Protected Area) when coming out of sleep. Unfortunately, there didn’t appear to be any kind of workaround for it, but tonight I discovered that you can use the Hitachi Feature Tool to disable it for good. It totally fixed my problem. Woo.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/tangerine-muine.html b/snorp.net/content/blog/2006/tangerine-muine.html new file mode 100644 index 00000000..f2a50fb2 --- /dev/null +++ b/snorp.net/content/blog/2006/tangerine-muine.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "Tangerine + Muine" + created: 2006-09-12 21:54:12 +%} + +{% block article %} +{%article%} + +

Hot on the heels of the last release comes Tangerine 0.2.8, with the only major change being a new plugin for Muine.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/tangerine-with-cocoa.html b/snorp.net/content/blog/2006/tangerine-with-cocoa.html new file mode 100644 index 00000000..59c77c2a --- /dev/null +++ b/snorp.net/content/blog/2006/tangerine-with-cocoa.html @@ -0,0 +1,20 @@ + +{% extends "_post.html" %} +{%hyde + title: "Tangerine with Cocoa" + created: 2006-10-13 14:49:03 +%} + +{% block article %} +{%article%} + +

Last weekend I spent some time porting Tangerine to Mac OS X. The actual music sharing daemon worked fine with no changes, except I had to modify the path to the xml database for the iTunes plugin. With that out of the way, I set out to create a native configuration GUI for it. I had never used Obj-C or Cocoa or anything like that before, so I thought it would be fun to learn all of that stuff. The Apple developer tools are pretty nice, and it wasn’t long before I had a semi-working preference pane. The last couple of nights I polished it up to the point where I think it’s releasable, so here we go.

+

Tangerine’s Preference Pane on Mac OS X
+

I’m not an expert on Apple interfaces, so I’d welcome input from someone who has experience with this. It looks ok to me, though. The “automatic” selection uses Spotlight to find all of your music and share it. This is the same thing it does on Linux and Windows with Beagle and Google Desktop, respectively.

+

Download Now!

+

My next post will be about my experience porting this app to Windows and Mac OS X, what they have that’s better/worse than Linux, etc.

+

Update: To use Tangerine you’ll need the Mono framework installed. Get it here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/up-to-10-times-more-sharing-or-more.html b/snorp.net/content/blog/2006/up-to-10-times-more-sharing-or-more.html new file mode 100644 index 00000000..2e091a45 --- /dev/null +++ b/snorp.net/content/blog/2006/up-to-10-times-more-sharing-or-more.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Up to 10 times more sharing or more!" + created: 2006-09-12 00:31:12 +%} + +{% block article %} +{%article%} + +

I released a new version of Tangerine tonight. The biggest change is that you can now share the songs stored in your music player’s collection. So if you add some songs to your library in Banshee, they will automatically be shared by Tangerine. Rhythmbox and Amarok are also supported.
+



+The config dialog allows you to select a music player

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/why-i-hate-apple-still.html b/snorp.net/content/blog/2006/why-i-hate-apple-still.html new file mode 100644 index 00000000..5d2726e8 --- /dev/null +++ b/snorp.net/content/blog/2006/why-i-hate-apple-still.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "Why I hate Apple (still)" + created: 2006-09-14 14:53:12 +%} + +{% block article %} +{%article%} + +

For a while now I’ve been getting reports of people having a strange problem using their iPod with Banshee or Dopi. There is a file on the device that we use to get things like the model number, which tells us what sort of iPod we’re dealing with. That file has been removed or set to 0 length on newer firmwares, and I was just yesterday able to reproduce it after upgrading and restoring my iPod Video. Since it wasn’t stored on the filesystem anymore, I started poking around the firmware parition, and found several copies of it there. Unfortunately it seems to appear in different places depending on the device, so I haven’t yet found a reliable way of getting that stuff out. We’ll eventually figure it out, though, and at that point things should start working again. I have a totally gross solution which runs strings(1) on the partition, but I don’t think it will come to that :)

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2006/yumier-than-bacardi.html b/snorp.net/content/blog/2006/yumier-than-bacardi.html new file mode 100644 index 00000000..a1c2f460 --- /dev/null +++ b/snorp.net/content/blog/2006/yumier-than-bacardi.html @@ -0,0 +1,23 @@ + +{% extends "_post.html" %} +{%hyde + title: "YUMier Than Bacardi" + created: 2006-09-06 15:25:03 +%} + +{% block article %} +{%article%} + +

When I was in Boston last week, my cohorts and I whipped up something wonderful: a rug interface for yum. We call it ‘rum’, and it has nearly all the features of the original (used with rcd) rug. “zOMG!!! WHY?!!11”, you ask? Well, after using and working on rug/rcd/zmd for the past 3 or 4 years, we simply can’t use anything else. It’s a sickness. So what’s in it for you, the average yum user?
+


    +
  • The world-renowned, award-winning, ‘search’ command

  • +
  • “tilde” support (rum in foo bar ~baz), which allows you to add and remove packages at the same time

  • +
  • Easily add/remove/enable/disable repositories. Never edit a config file again!

  • +
  • Persistent package locks (known as ‘excludes’ in the yum world)

  • +
  • New commands are easily added. Just drop a python file in the appropriate place, and it is automatically added to the rum interface.
  • +
+

You can download RPMs for FC5-i386, FC6-i386, and SuSE 10.x-i586. For you adventurous build-from-source types, a tarball is here. Enjoy!

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2007/banshee-and-awn.html b/snorp.net/content/blog/2007/banshee-and-awn.html new file mode 100644 index 00000000..a7f513e7 --- /dev/null +++ b/snorp.net/content/blog/2007/banshee-and-awn.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Banshee and AWN" + created: 2007-02-28 18:51:20 +%} + +{% block article %} +{%article%} + +

I just tried out Avant Window Navigator for the first time, after seeing Neil’s latest blog entry. It’s pretty slick, and worth trying if you have xgl/aiglx/whatever. I also wrote a Banshee plugin which makes the current track cover appear in the dock.
+


Banshee playing “Stadium Arcadium”

+

You can get the plugin here. Just drop the dll into ~/.gnome2/banshee/plugins.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2007/listing.html b/snorp.net/content/blog/2007/listing.html new file mode 100644 index 00000000..a9ea5200 --- /dev/null +++ b/snorp.net/content/blog/2007/listing.html @@ -0,0 +1,5 @@ +{% extends "skeleton/_listing.html" %} + +{%hyde + title: Archives +%} diff --git a/snorp.net/content/blog/2007/monster-truck-lloyd.html b/snorp.net/content/blog/2007/monster-truck-lloyd.html new file mode 100644 index 00000000..a5efa070 --- /dev/null +++ b/snorp.net/content/blog/2007/monster-truck-lloyd.html @@ -0,0 +1,22 @@ + +{% extends "_post.html" %} +{%hyde + title: "Monster Truck Lloyd" + created: 2007-02-10 18:40:55 +%} + +{% block article %} +{%article%} + +


+

+Monster Truck Lloyd
+

+Originally uploaded by snorp.
+

+

+

Saw this while getting some food this morning. Only in Kansas…

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2007/opensuse-livecd-installer.html b/snorp.net/content/blog/2007/opensuse-livecd-installer.html new file mode 100644 index 00000000..29bb5e25 --- /dev/null +++ b/snorp.net/content/blog/2007/opensuse-livecd-installer.html @@ -0,0 +1,21 @@ + +{% extends "_post.html" %} +{%hyde + title: "openSUSE LiveCD Installer" + created: 2007-07-11 20:21:20 +%} + +{% block article %} +{%article%} + +

I was out of town for part of hack week, so I didn’t participate fully like my colleagues. I did get a couple evenings to mess around with something, though. I wrote an installer for KIWI-generated LiveCDs.

+
+

First page of live installer

+First page of live installer

+

It’s still in early development and has lots of hacks to make things work, but it does manage to install a working system onto your machine. The installation itself is really pretty simple. The LiveCD data is in a compressed squashfs image on the CD, and the installer just copies all of that to the disk. Then it just writes out an fstab, installs grub, etc.

+

As usual, however, the devil is in the details. Things like sound and video card detection are normally done by the YaST installer, so other methods must be used. It might be possible to invoke the relevant bits of YaST from the LiveCD installer to achieve the same effect, but I haven’t looked into it yet. I have everything checked into svn (svn://snorp.net/trunk/opensuse-live), so if you want to try and build a CD everything is there. I will also upload an ISO soonish.

+

Update: You can download a full ISO based on openSUSE Alpha 5 here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2007/return-of-the-carpet.html b/snorp.net/content/blog/2007/return-of-the-carpet.html new file mode 100644 index 00000000..7c390b7b --- /dev/null +++ b/snorp.net/content/blog/2007/return-of-the-carpet.html @@ -0,0 +1,55 @@ + +{% extends "_post.html" %} +{%hyde + title: "Return of The Carpet" + created: 2007-04-26 23:42:24 +%} + +{% block article %} +{%article%} + +

Red Carpet, that is. Yes, that Red Carpet. I’ve taken some time lately to give some love to our old friends rcd and rug (the original rug, not the rewritten one). First I got everything building on a modern distro (openSUSE 10.2). This took more effort than I thought it would, but eventually things worked well. After that, I set out to make rcd more usable with yum services. Here is a list of the main changes:
+


    +
  • Add native yum support. I removed the ‘helix’ service support and replaced it with something that understands yum metadata. This means you can just do ‘rug sa repo_url name’ for any yum service. I used the excellent yum parser that Tambet wrote for the libredcarpet backend of zmd to accomplish this.

  • +
  • Remove channel subscriptions. Since yum services don’t provide multiple channels, subscriptions aren’t really necessary. They have been replaced with the ability to disable a service.

  • +
  • Add sleep ability. One of the main complaints against rcd was that it used too much memory. This was mostly because over time the heap would become fragmented. The ‘sleep’ feature avoids this by running the main rcd daemon only when necessary. After a period of inactivity (3 minutes by default), the main daemon replaces itself with a smaller daemon. This smaller daemon simply waits until a request comes in and launches the full daemon to respond.
  • +
+

With the above changes, rcd is once again a joy to use. I would like to get the GUI working again, but there is some kind of threading problem preventing it from running. I would also like to add ftp support, but that is not a top priority.

+

I know there are probably SUSE users reading this asking “Ok, sounds fine, but is it fast?”. While it may not be the fastest thing out there, I think you will be surprised at the results (I was). Here are a few simple benchmarks from normal usage scenarios:

+

First, lets look at the number of services I currently have added:
+


+% rug sl

+
    +
  1. | Service URI | Name
    +—+———————————————————————————+——————
    +1 | http://go-mono.com/download-stable/suse-102-i586?r… | mono
    +2 | http://software.opensuse.org/download/FATE/openSUS… | fate
    +3 | http://ftp.suse.com/pub/suse/update/10.2/?name=upd… | updates
    +4 | http://download.opensuse.org/distribution/10.2/rep… | suse-nonoss
    +5 | http://download.opensuse.org/distribution/10.2/rep… | suse-oss
    +6 | http://packman.inode.at/suse/10.2?remote_only=1;na… | packman
    +7 | http://software.opensuse.org/download/home:/cybero… | cyborg
    +8 | http://software.opensuse.org/download/X11:/XGL/ope… | xgl
    +9 | http://software.opensuse.org/download/Beagle/openS… | beagle
    +10 | http://software.opensuse.org/download/games:/actio… | games
    +11 | http://software.opensuse.org/download/Virtualizati… | virt
    +12 | http://software.opensuse.org/download/home:/kraxel… | kvm
    +

+So 12 services, and the package count is almost 21000. 22500 if you also count the ones in the rpm database. How long does it take to load all of those? + +

Cold filesystem cache, daemon is sleeping:
+

% time rug ping > /dev/null
+rug ping 0.17s user 0.02s system 1% cpu 13.735 total

+


+14 seconds to respond isn’t terrible, considering the cold filesystem cache. Now that the kernel has it cached, though, how long does it take?

+

Warm filesystem cache, daemon is sleeping:
+<blockquote

% time rug ping >/dev/null
+rug ping > /dev/null 0.14s user 0.02s system 3% cpu 4.465 total
+

+4.5, not bad. Definitely in the tolerable range, I’d say. Of course after the daemon is awake, commands respond immediately. That is maybe the only good thing about rcd being a daemon — subsequent commands are instant, where other tools (yum, smart, etc) have to load the package metadata again. Memory usage after rcd wakes up is about 28MB, so that is not too bad either (it is a little over 1MB when sleeping).

+

Packages for recent SUSE distros are available in the build service. It has had a hard time keeping up recently, though, so you may run into a problem or two with rug. Sources can be found in gnome svn in the yummy branch of the various modules (rcd, rug, libredcarpet).

+

Also, yes, I am sick sick person.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2007/rpm-transaction-enhancements.html b/snorp.net/content/blog/2007/rpm-transaction-enhancements.html new file mode 100644 index 00000000..06593634 --- /dev/null +++ b/snorp.net/content/blog/2007/rpm-transaction-enhancements.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "RPM Transaction Enhancements" + created: 2007-05-04 14:51:28 +%} + +{% block article %} +{%article%} + +

One of the reasons I wanted to revive rcd was so that I could use it to play with some package management ideas I’ve been kicking around. One of these ideas is a way to reliably rollback changes made during an RPM transaction. That is, actually make RPM transactions transactional.

+

Recently, a colleague introduced me to device-mapper, a kernel system used for block device redirection. There is a really cool thing that uses it called dm-snapshot, which allows you to redirect all writes to a device into a separate device. What I would like to do is use this to store all of the changes made during an rpm transaction. I think it would just need a bit of patching so that it only stores the changes made by the rcd/rpm process (and children). If anything goes wrong, you can just trash the snapshot data and things are exactly as they were in the beginning. Of course, if it succeeds without problelms, you need to merge the snapshot changes into the original device. This is where things get fuzzy, as dm-snapshot does not have this ability. However, Mark McLoughlin has created a set of patches that add this feature as part of the Stateless Linux project. Sadly, the patches do not appear to be a high priority for the kernel guys right now, so I guess this approach will have to be put on hold.

+

In any case, a system for performing this rollback stuff would be ridiculously useful in general — not just for package management. It looks like it will be a little more than I can do by myself in a weekend hack, though, so hopefully someone else will carry the torch? :)

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2007/thoughts-on-sincerity.html b/snorp.net/content/blog/2007/thoughts-on-sincerity.html new file mode 100644 index 00000000..e660f3c6 --- /dev/null +++ b/snorp.net/content/blog/2007/thoughts-on-sincerity.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Thoughts on Sincerity" + created: 2007-02-07 04:20:52 +%} + +{% block article %} +{%article%} + +

Hi Steve,

+

Earlier today I read your “Thoughts on Music” post. Afterwards, my initial reaction was “That’s great! You get ’em Steve!”. It’s no secret that DRM is broken by design, but it’s nice to see one of the biggest users of it say so. However, I was quickly reminded by a colleague that Apple hardly seems interested in the “everything works with everything” utopia you describe. One specific example is the iTunes music sharing feature. Soon after it was released, some smart people figured out how it worked and developed software to be compatible with it. This let people access their iTunes-shared music from devices or operating systems that didn’t have iTunes. Soon after, Apple implemented a mechanism which prevented non-iTunes clients from doing this. Why? It wasn’t because of the record companies. The music purchased on the iTunes Music Store was still protected by FairPlay, so they had no reason to complain. The only conceivable reason you’d have for doing this is to force consumers into vendor lock in. Well, it didn’t work. Some more smart people defeated your mechanism and once again people were playing their iTunes-shared music using whatever software they liked best (be it iTunes or otherwise). But that didn’t stop Apple from re-implementing a similar protection scheme again in iTunes 7. This time you even knifed some business partners in the process. This kind of behavior isn’t at all congruent with what you’re saying in your post. Have you changed your mind now? Will the next iTunes release remove this restriction? If your “Thoughts on Music” was sincere, I’d expect so.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/2-1.html b/snorp.net/content/blog/2008/2-1.html new file mode 100644 index 00000000..30a40842 --- /dev/null +++ b/snorp.net/content/blog/2008/2-1.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "2 + 1" + created: 2008-06-12 15:31:53 +%} + +{% block article %} +{%article%} + +
Alexander, 15 minutes old
+Alexander James, 30 minutes old
+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/2008.html b/snorp.net/content/blog/2008/2008.html new file mode 100644 index 00000000..a9ea5200 --- /dev/null +++ b/snorp.net/content/blog/2008/2008.html @@ -0,0 +1,5 @@ +{% extends "skeleton/_listing.html" %} + +{%hyde + title: Archives +%} diff --git a/snorp.net/content/blog/2008/application-usage-monitoring.html b/snorp.net/content/blog/2008/application-usage-monitoring.html new file mode 100644 index 00000000..8f69f146 --- /dev/null +++ b/snorp.net/content/blog/2008/application-usage-monitoring.html @@ -0,0 +1,18 @@ + +{% extends "_post.html" %} +{%hyde + title: "Application Usage Monitoring" + created: 2008-01-18 16:47:16 +%} + +{% block article %} +{%article%} + +

Recently I’ve had a couple of ideas for a project (like I need another one of those). The goal would be to make a library which allows applications to easily track their user’s interactions and log them in a central location. Project maintainers/contributors could then look at the collected data to help them make decisions about what they should be spending time on. For instance, a media player might log what types of files are played or if it was synced to an iPod-like device.

+

As far as technical hurdles go, doing something like this is pretty easy. The main questions I have are around the kind of policies that should exist for such a thing. Obviously, participation should be opt-in. But should it be on a per-app basis, or per-user? Or both? If it is per-app, you would likely get bombarded with a prompt on the first run of every app that uses this system. If that is a small number it might be ok, but hopefully that wouldn’t be the case :). On the other hand, maybe you don’t want certain sensitive applications (email client?) ever sending info.

+

Then there’s the question of who should have access to the data. My feeling is that the user should always be able to see everything that he has sent. But should he also be able to see everyone else’s individual data? What about the aggregated data? That leads me to the next question. Should there be a cookie that identifies a single user throughout all applications? Or even a cookie per-application? I think having a cookie across all applications would definitely make the data more useful, but I’m not sure if people would be opposed to such a thing. Of course, this leads to yet another question. How do we keep personal information out? I don’t believe there is a technical solution to keep things like this from making its way in. Developers will need to be very careful, and that kind of bothers me. If all of the data on the server is available to everyone then maybe public scrutiny will help keep things in check, but who knows.

+

These are just a few of the questions I have come up with, and I am sure others can think of plenty more. Is it possible to come up with something that benefits the development community without infringing on user’s privacy? Even so, would users participate? Comments are open.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/mango-lassi.html b/snorp.net/content/blog/2008/mango-lassi.html new file mode 100644 index 00000000..e0fdbbc9 --- /dev/null +++ b/snorp.net/content/blog/2008/mango-lassi.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Mango Lassi" + created: 2008-02-26 17:17:23 +%} + +{% block article %} +{%article%} + +

The other day I looked into switching away from synergy to Mango Lassi. I only use it between two machines, so I figured my use case was simple enough that it should work at this stage. I was not disappointed. I was getting some very strange behavior with synergy and vmware, and Mango Lassi has none of that. Plus it gives an OSD telling you which machine the mouse/keyboard are being used on, which is a nice perk.

+

Anyway, I was so happy with it that I made packages for openSUSE. You can get it from my build service repository.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/more-mango.html b/snorp.net/content/blog/2008/more-mango.html new file mode 100644 index 00000000..1d034b24 --- /dev/null +++ b/snorp.net/content/blog/2008/more-mango.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "More Mango" + created: 2008-07-11 13:39:36 +%} + +{% block article %} +{%article%} + +

A while back I posted about Mango Lassi and how awesome it was compared to synergy. I still think it is awesome, but every now and then something would freak it out and cause the association between machines to drop. I’ve fixed at least one cause of that problem now and published it in a git repo here: http://www.snorp.net/git/mango-lassi.git/

+

BTW, thanks for all of the great comments on the NAS situation. I’m looking at several of the options mentioned there along with a couple others. I think I’ve ruled out rolling my own, though, as I don’t want yet another linux box to maintain.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/nas-for-home.html b/snorp.net/content/blog/2008/nas-for-home.html new file mode 100644 index 00000000..865ec4c1 --- /dev/null +++ b/snorp.net/content/blog/2008/nas-for-home.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "NAS for Home" + created: 2008-07-04 17:11:06 +%} + +{% block article %} +{%article%} + +

Dear Lazyweb,

+

Does anyone have suggestions on what to use for centralized storage at home? I have a lot of music/photos here piling up and would like to put them on some energy-efficient NAS box. Ideally it would have some sort of of built-in backup solution as well. A lot of the NAS-in-a-box solutions seem to have RAID 1, but that really only helps for HA. I am more concerned with never ever losing this stuff than having it available 24/7.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/new-job.html b/snorp.net/content/blog/2008/new-job.html new file mode 100644 index 00000000..ad12b16e --- /dev/null +++ b/snorp.net/content/blog/2008/new-job.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "New Job" + created: 2008-12-15 18:47:34 +%} + +{% block article %} +{%article%} + +

For quite a while I had been working on SUSE Linux Enterprise Thin Client, which is Novell’s diskful Thin Client solution. It had a lot of challenging aspects, not least of which was fitting a minimal GNOME environment + apps onto 128MB of flash. That work is mostly wrapping up, though, and I’ve moved to a new team.

+

At the beginning of the month I started working on SUSE Studio, which is a web-based appliance builder for SUSE. I wanted to get out of my comfort zone a little, and it hasn’t disappointed there. We’re using RoR, which I am really enjoying so far. Ruby was ridiculously easy to pick up. Rails confused me at first with the amount of magic that it does behind-the-scenes, but there is a lot of information on how that works so getting up to speed wasn’t too bad. I bought Agile Web Development with Rails which has been very helpful as well.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/novell-bugzillauserjs-updates.html b/snorp.net/content/blog/2008/novell-bugzillauserjs-updates.html new file mode 100644 index 00000000..700a79a0 --- /dev/null +++ b/snorp.net/content/blog/2008/novell-bugzillauserjs-updates.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "novell-bugzilla.user.js updates" + created: 2008-08-25 20:42:02 +%} + +{% block article %} +{%article%} + +

I’ve updated the Novell Bugzilla Autologin greasemonkey script again. Just click here to upgrade your current version or install it for the first time. You of course need greasemonkey installed.

+

I’ve removed the “go to login page” step. It now just logs in directly via AJAX and refreshes your current page. It has also been rewritten to use jQuery (and jQuery.blockUI) which cleaned things up a bit and gives a nicer “please wait” message :)

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/out-weaseling-firefox.html b/snorp.net/content/blog/2008/out-weaseling-firefox.html new file mode 100644 index 00000000..4ba27185 --- /dev/null +++ b/snorp.net/content/blog/2008/out-weaseling-firefox.html @@ -0,0 +1,15 @@ + +{% extends "_post.html" %} +{%hyde + title: "Out-weaseling Firefox" + created: 2008-05-21 15:49:57 +%} + +{% block article %} +{%article%} + +

There has been a lot of buzz lately about the Firefox 3 fsync issue. The work I’m doing these days has me doing a lot of long-running disk-bound activies, so this one hurts me pretty bad. Firefox would stop responding for 30-40s at a time while my job was running in the background, which I think is pretty unacceptable. I have worked around it in the (hopefully) short-term with a LD_PRELOAD hack. I’ve posted it here in case anyone else finds it useful. Just unpack, cd to the directory, and ‘make && make install’ (not as root). A word of warning, though: if it breaks you get to keep both pieces. Kudos to Aaron for adding the ‘make install’ bits to the Makefile :)

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/slashdot-looking-for-open-proxies.html b/snorp.net/content/blog/2008/slashdot-looking-for-open-proxies.html new file mode 100644 index 00000000..258ecfd2 --- /dev/null +++ b/snorp.net/content/blog/2008/slashdot-looking-for-open-proxies.html @@ -0,0 +1,20 @@ + +{% extends "_post.html" %} +{%hyde + title: "Slashdot looking for open proxies?" + created: 2008-08-25 16:24:04 +%} + +{% block article %} +{%article%} + +

I saw the following somewhat-strange line in my web server logs today:

+

+216.34.181.45 - - [25/Aug/2008:10:23:51 -0500] "GET http://tech.slashdot.org/ok.txt HTTP/1.0" 401 523 "-" "libwww-perl/5.812" +

+

That web server is running on the IP of my home router. The requesting IP appears to be a Slashdot machine. My guess is that they are trying to find out who accesses their site through an open proxy. But why? Is there another reason they might send a request like that? Do they ban proxies if they find one?

+

UPDATE: Apparently, they do in fact ban open proxies (according to this). Supposedly a lot of comment spam comes from them. I wonder if it would help blogs at all to do something similar?

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2008/some-things-do-change.html b/snorp.net/content/blog/2008/some-things-do-change.html new file mode 100644 index 00000000..5718940b --- /dev/null +++ b/snorp.net/content/blog/2008/some-things-do-change.html @@ -0,0 +1,16 @@ + +{% extends "_post.html" %} +{%hyde + title: "Some things do change..." + created: 2008-05-16 19:26:43 +%} + +{% block article %} +{%article%} + +

Ever since zypper came along I hated it. It was slow, buggy, and used a ton of resources.

+

Well, I installed openSUSE 11.0b3 yesterday and the zypper/libzypp there is massively improved. I don’t think it’s possible to overstate just how much of an improvement it really is. Normally I just make rcd/rug work on whatever new release comes along and continue using that. Zypper and PackageKit are so good now that I’m giving that up. So congratulations to the zypp team — I know they caught a lot of flack in the past, but I think this release will finally put a lot of that to rest.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2009/2009.html b/snorp.net/content/blog/2009/2009.html new file mode 100644 index 00000000..a9ea5200 --- /dev/null +++ b/snorp.net/content/blog/2009/2009.html @@ -0,0 +1,5 @@ +{% extends "skeleton/_listing.html" %} + +{%hyde + title: Archives +%} diff --git a/snorp.net/content/blog/2009/fun-with-studio.html b/snorp.net/content/blog/2009/fun-with-studio.html new file mode 100644 index 00000000..63519137 --- /dev/null +++ b/snorp.net/content/blog/2009/fun-with-studio.html @@ -0,0 +1,19 @@ + +{% extends "_post.html" %} +{%hyde + title: "Fun with Studio" + created: 2009-02-13 20:17:25 +%} + +{% block article %} +{%article%} + +

I’ve been working on a new project now for a while called SUSE Studio. Essentially it is a web interface which allows you to build your own customized version of SUSE. You can select packages, do some configuration, and even add your own branding.

+

I created a media center appliance to see how hard it would be. The appliance is based on openSUSE 11.1, and boots right into the excellent Elisa Media Center. You can download the image here. The tarball contains one file, which you can ‘dd’ to a USB storage device. We’re working on writing a small application to make this part easier.

+

On the first boot it will do some one-time setup like repartition and resize the disk, install NVIDIA or ATI video drivers (if appropriate), and setup X.

+


+Download Elisa Media Center Appliance

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/2009/yeah-im-a-rails-fanboy-now.html b/snorp.net/content/blog/2009/yeah-im-a-rails-fanboy-now.html new file mode 100644 index 00000000..819d8d7d --- /dev/null +++ b/snorp.net/content/blog/2009/yeah-im-a-rails-fanboy-now.html @@ -0,0 +1,17 @@ + +{% extends "_post.html" %} +{%hyde + title: "Yeah, I'm a Rails fanboy now" + created: 2009-04-27 14:48:04 +%} + +{% block article %} +{%article%} + +

A lot of my day job now involves working with Ruby on Rails. At first I wasn’t sure how much I would like Rails or ruby, given that I had been doing a lot of C#/C/whatever desktop work before. Not surprisingly, though, I’ve become quite addicted. The test-driven nature of development is a welcome change — most desktop apps I worked on didn’t even have tests. The Rails community has done a great job of banging automated testing into people’s heads. Almost every tutorial, book, or random blog post I’ve seen emphasizes the importance of good automated tests. Hopefully it has helped decrease the instances of ‘snorpage’, but perhaps my co-workers would disagree :)

+

Anyway, I love Rails so much that I’ve converted my blog from Wordpress to Enki. Enki is more of a create-your-own-blog construction kit than a turn-key solution like Wordpress. That was one of the main reasons I chose it over Typo or Mephisto — I wanted to be able to easily hack on it.

+

I wrote a quick and dirty script to help me import the Wordpress posts into Enki. Any fellow Enki hackers can grab it here.

+ +{%endarticle%} +{% endblock %} + diff --git a/snorp.net/content/blog/atom.xml b/snorp.net/content/blog/atom.xml new file mode 100644 index 00000000..99956d88 --- /dev/null +++ b/snorp.net/content/blog/atom.xml @@ -0,0 +1 @@ +{%extends "skeleton/_atom.xml"%} \ No newline at end of file diff --git a/snorp.net/content/blog/blog.html b/snorp.net/content/blog/blog.html new file mode 100644 index 00000000..a9ea5200 --- /dev/null +++ b/snorp.net/content/blog/blog.html @@ -0,0 +1,5 @@ +{% extends "skeleton/_listing.html" %} + +{%hyde + title: Archives +%} diff --git a/snorp.net/content/index.html b/snorp.net/content/index.html new file mode 100644 index 00000000..b49b1415 --- /dev/null +++ b/snorp.net/content/index.html @@ -0,0 +1,26 @@ +{% extends "skeleton/_body.html" %} +{%hyde + title: "James Willcox" + created: 2010-06-07 03:17:05 + frontpage: True +%} + +{% block content_header %}{% endblock %} +{% block content_body %} + +{%recent_posts recents 5 %} +{%for node in recents %} +{% ifequal node.module.name "blog" %} +
+
+ {{node.title}} + +
+
+ {% render_article node %} +
+
+{% endifequal %} +{%endfor%} + +{%endblock%} \ No newline at end of file diff --git a/snorp.net/layout/_about.html b/snorp.net/layout/_about.html new file mode 100644 index 00000000..eeefd4bb --- /dev/null +++ b/snorp.net/layout/_about.html @@ -0,0 +1,14 @@ +{% extends "skeleton/_body.html" %} +{% block content_body %} +
+{% filter typogrify %} +{% markdown %} + +{% block article %} + +{% endblock %} + +{% endmarkdown %} +{% endfilter %} +
+{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/_post.html b/snorp.net/layout/_post.html new file mode 100644 index 00000000..dd9a94ec --- /dev/null +++ b/snorp.net/layout/_post.html @@ -0,0 +1,24 @@ +{% extends "skeleton/_body.html" %} +{% block content_body %} +
+{% filter typogrify %} +{% markdown %} + +{% block article %} + +{% endblock %} + +{% endmarkdown %} +{% endfilter %} +
+{% endblock %} + +{% block content_header %} +
+

{{ page.title }}

+ +
+{% endblock %} +{% block context_nav %} +{% include "skeleton/_context_nav.html" %} +{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_atom.xml b/snorp.net/layout/skeleton/_atom.xml new file mode 100644 index 00000000..cf1219fd --- /dev/null +++ b/snorp.net/layout/skeleton/_atom.xml @@ -0,0 +1,30 @@ + +{%spaceless%} + + {% block title %}{{site.name}} {{page.node.name}}{%endblock%} + {%block self_url %} + + {%endblock%} + {%block site_url %} + + {%endblock%}{%block feed_extra%}{%endblock%} + {{now|xmldatetime}} + {{site.full_url}} + {% for node_page in page.node.walk_pages %} + {% if node_page.display_in_list %} + + {{node_page.title}} + {{site.author}} + + {{node_page.updated|default:node_page.created|xmldatetime}} + {{node_page.created|xmldatetime}} + {{node_page.full_url}} + {%block entry_extra%}{%endblock%} + + {%filter force_escape%}{% render_article node_page %}{%endfilter%} + + + {%endif%} + {% endfor %} + +{%endspaceless%} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_base.html b/snorp.net/layout/skeleton/_base.html new file mode 100644 index 00000000..9291c2f5 --- /dev/null +++ b/snorp.net/layout/skeleton/_base.html @@ -0,0 +1,24 @@ + + + + snorp.net -- {{page.title}} + {% block feeds %} + + {% endblock %} + {% block css %} + + + {% block extra_css %}{% endblock %}{% endblock %}{% block js %} + + + {% block extra_js %}{% endblock %}{% endblock %} + + + +
+ {% include "skeleton/_header.html" %} + {% block content %}{% endblock %} + {% include "skeleton/_footer.html" %} +
+ + \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_base.html.orig b/snorp.net/layout/skeleton/_base.html.orig new file mode 100644 index 00000000..526b6617 --- /dev/null +++ b/snorp.net/layout/skeleton/_base.html.orig @@ -0,0 +1,27 @@ +{% extends "skeleton/_root.html" %} +{% block all %} + + + + + + {% block title %}{{site.name}}{% endblock %} + {% block feeds %} + + {% endblock %} + {% block css %} + + {% block extra_css %}{% endblock %}{% endblock %}{% block js %} + + {% block extra_js %}{% endblock %}{% endblock %} + + +
+ {% include "skeleton/_header.html" %} + {% block content %}{% endblock %} +
+{% include "skeleton/_footer.html" %} + + +{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_body.html b/snorp.net/layout/skeleton/_body.html new file mode 100644 index 00000000..4458dc4e --- /dev/null +++ b/snorp.net/layout/skeleton/_body.html @@ -0,0 +1,15 @@ +{% extends "skeleton/_base.html" %} +{% block title %}{{site.name}} : {{page.module.name}} : {{page.title}} {% endblock %} +{% block content %} +
+ +{% block content_header %} +
+

{{ page.title }}

+
+{% endblock %} + +{% block content_body %}{% endblock %} + +
+{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_breadcrumbs.html b/snorp.net/layout/skeleton/_breadcrumbs.html new file mode 100644 index 00000000..211caa98 --- /dev/null +++ b/snorp.net/layout/skeleton/_breadcrumbs.html @@ -0,0 +1,15 @@ +{% extends "skeleton/_root.html" %} +{% block all %} + + +{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_context_nav.html b/snorp.net/layout/skeleton/_context_nav.html new file mode 100644 index 00000000..0a6d35c4 --- /dev/null +++ b/snorp.net/layout/skeleton/_context_nav.html @@ -0,0 +1,10 @@ +{% extends "skeleton/_root.html" %} +{% block all %} +
+ Previous + Next +
+{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_footer.html b/snorp.net/layout/skeleton/_footer.html new file mode 100644 index 00000000..848e847a --- /dev/null +++ b/snorp.net/layout/skeleton/_footer.html @@ -0,0 +1,30 @@ + + diff --git a/snorp.net/layout/skeleton/_frontpage_item.html b/snorp.net/layout/skeleton/_frontpage_item.html new file mode 100644 index 00000000..a20fc32d --- /dev/null +++ b/snorp.net/layout/skeleton/_frontpage_item.html @@ -0,0 +1,16 @@ +{% extends "skeleton/_root.html" %} +{% block all %} +{%spaceless%} + +
+
+ {{node.title}} + +
+
+ {% markdown %} + {% render_excerpt node %} + {% endmarkdown %} +
+
+{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_frontpage_item.html.bak b/snorp.net/layout/skeleton/_frontpage_item.html.bak new file mode 100644 index 00000000..f681b83d --- /dev/null +++ b/snorp.net/layout/skeleton/_frontpage_item.html.bak @@ -0,0 +1,41 @@ +{% extends "skeleton/_root.html" %} +{% block all %} +{%spaceless%} + +
+
+ {{node.title}} + +
+
+ {% markdown %} + {% render_excerpt node %} + {% endmarkdown %} +
+
+ +{% endblock %} + + +{%ifnotequal node page.node %} +{% if node.has_listing %}{% endif %} +

{{node.name|unslugify}}

+{% if node.has_listing %}
{% endif %} +{%endifnotequal%} + +{%endspaceless%} +{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_header.html b/snorp.net/layout/skeleton/_header.html new file mode 100644 index 00000000..d7648da8 --- /dev/null +++ b/snorp.net/layout/skeleton/_header.html @@ -0,0 +1,25 @@ + +
\ No newline at end of file diff --git a/snorp.net/layout/skeleton/_innerlisting.html b/snorp.net/layout/skeleton/_innerlisting.html new file mode 100644 index 00000000..fd7c345e --- /dev/null +++ b/snorp.net/layout/skeleton/_innerlisting.html @@ -0,0 +1,25 @@ +{% extends "skeleton/_root.html" %} +{% block all %} +{%spaceless%} +{%ifnotequal node page.node %} +{% if node.has_listing %}{% endif %} +

{{node.name|unslugify}}

+{% if node.has_listing %}
{% endif %} +{%endifnotequal%} + +{%endspaceless%} +{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_listing.html b/snorp.net/layout/skeleton/_listing.html new file mode 100644 index 00000000..00706802 --- /dev/null +++ b/snorp.net/layout/skeleton/_listing.html @@ -0,0 +1,10 @@ +{% extends "skeleton/_body.html"%} +{% block content_header %}{% endblock %} +{% block content_body %} +
+{% for node in page.node.walk %} +{% include "skeleton/_innerlisting.html" %} +{% endfor %} +
+
+{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_root.html b/snorp.net/layout/skeleton/_root.html new file mode 100644 index 00000000..94ff2a3f --- /dev/null +++ b/snorp.net/layout/skeleton/_root.html @@ -0,0 +1 @@ +{% block all %}{% endblock %} \ No newline at end of file diff --git a/snorp.net/media/css/base.css b/snorp.net/media/css/base.css new file mode 100644 index 00000000..042c1b51 --- /dev/null +++ b/snorp.net/media/css/base.css @@ -0,0 +1,186 @@ + +body { + margin: 0; + padding: 0; + background-color: white; + font-family: Helvetica, 'Droid Sans', Arial, sans-serif; + font-size: 14px; + color: #303030; +} + +a { + color: #820700; + -webkit-transition: color 0.5s ease-in; + transition: color 0.5s ease-in; +} + +a:hover { + color: #303030; + -webkit-transition: color 0.5s ease-in; + transition: color 0.5s ease-in; +} + +#main { + margin-left: auto; + margin-right: auto; + width: 1000px; +} + +#header { + height: 40px; + margin-top: 20px; + margin-bottom: 0px; + padding-bottom: 0px; + font-size: 25px; + border-bottom: 1px solid #D8D8D2; + position: relative; +} + +#header_content { + padding-left: 12px; + position: absolute; + bottom: 0px; +} + +#header_content a { + text-decoration: none; +} + +#header_buttons { + font-size: 14px; + list-style-type: none; + margin: 0px; + padding: 0px; + position: absolute; + bottom: -1px; + right: 0px; +} + +#header_buttons li { + display: block; + float: left; + padding: 6px; + border: solid 1px #D8D8D2; + margin-left: 16px; +} + +#header_buttons a { + color: #303030; + display: block; + float: left; + text-decoration: none; + height: 100%; + padding-left: 6px; + padding-right: 6px; +} + +#header_buttons a:hover { + color: white; +} + +#header_buttons li:hover { + background-color: #820700; + cursor: pointer; + -webkit-transition: all 0.2s ease-in; + transition: all 0.2s ease-in; +} + +#header_subtext { + font-size: 10px; + color: #303030; +} + +#header_spacer { + clear: both; + height: 24px; + width: 100%; +} + +#content { + width: 776px; + float: left; + padding: 12px; +} + +#content-header { + padding: 0px; +} + +#content-header h1 { + padding: 0px; + margin: 0px; +} + +#sidebar { + margin-bottom: 24px; + padding-left: 12px; + padding-right: 12px; + width: 174px; + float: right; + border-left: 1px solid #D8D8D2; +} + +#sidebar ul { + list-style-type: none; + padding: 0px; + margin: 0px; + margin-bottom: 24px; +} + +#sidebar h4 { + margin: 0; + color: #820700; + font-size: 16px; + font-weight: normal; + text-transform: lowercase; +} + +#sidebar h4 > span { + font-weight: bold; +} + +.post_title { + font-size: 18px; + font-weight: bold; + padding-bottom: 4px; + margin-bottom: 16px; + text-transform: lowercase; + border-bottom: 1px solid #D8D8D2; +} + +.post_title > a { + display: block; +} + +.post_date { + font-size: 11px; + margin: 0px; +} + +.post_content { + padding-bottom: 4px; +} + +#footer { + clear: both; + padding-left: 12px; + padding-right: 12px; +} + +.tweet { + padding: 6px; + border-bottom: 1px solid #D8D8D2; + -webkit-transition: background-color 0.3s ease-in; + transition: background-color 0.3s ease-in; +} + +.tweet:hover { + background-color: #D8D8D2; + cursor: pointer; + -webkit-transition: background-color 0.3s ease-in; + transition: background-color 0.3s ease-in; +} + +.message { + font-style: italic; +} \ No newline at end of file diff --git a/snorp.net/media/js/base.js b/snorp.net/media/js/base.js new file mode 100644 index 00000000..82b469df --- /dev/null +++ b/snorp.net/media/js/base.js @@ -0,0 +1,42 @@ + +var userPattern = /@([a-zA-Z0-9]+) /g; +var hashPattern = /\#([a-zA-Z0-9]+)/g; + +function linkifyTweet(text) { + text = text.replace(userPattern, "@$1 "); + text = text.replace(hashPattern, "#$1") + return text; +} + + +function refreshTweets() { + + $.getJSON("http://twitter.com/status/user_timeline/snorp.json?count=10&callback=?", function(data){ + $("#tweet_container").html("
    "); + + $.each(data, function(i,item) { + var tweet = document.createElement('li'); + tweet.innerHTML = linkifyTweet(item.text); + tweet.setAttribute('class', 'tweet'); + tweet.setAttribute('tweet_id', item.id); + $("#tweet_container ul").append(tweet); + }); + + $(".tweet").click(function(event) { + var tweet_id = event.target.getAttribute('tweet_id'); + + if (tweet_id) { + window.open('http://twitter.com/snorp/status/' + event.target.getAttribute('tweet_id')); + } + }); + }); + +} + + +// Load jQuery +google.load("jquery", "1"); + +google.setOnLoadCallback(function() { + refreshTweets(); +}) \ No newline at end of file diff --git a/snorp.net/settings.py b/snorp.net/settings.py new file mode 100644 index 00000000..8273b6af --- /dev/null +++ b/snorp.net/settings.py @@ -0,0 +1,136 @@ +import os + +ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) + +#Directories +LAYOUT_DIR = os.path.join(ROOT_PATH, 'layout') +CONTENT_DIR = os.path.join(ROOT_PATH, 'content') +MEDIA_DIR = os.path.join(ROOT_PATH, 'media') +DEPLOY_DIR = os.path.join(ROOT_PATH, 'deploy') +TMP_DIR = os.path.join(ROOT_PATH, 'deploy_tmp') + +BACKUPS_DIR = os.path.join(ROOT_PATH, 'backups') +BACKUP = False + +SITE_ROOT = "/" +SITE_WWW_URL = "http://snorp.net" +SITE_NAME = "snorp.net" +SITE_AUTHOR = "James Willcox" + +#Url Configuration +GENERATE_ABSOLUTE_FS_URLS = False + +# Clean urls causes Hyde to generate urls without extensions. Examples: +# http://example.com/section/page.html becomes +# http://example.com/section/page/, and the listing for that section becomes +# http://example.com/section/ +# The built-in CherryPy webserver is capable of serving pages with clean urls +# without any additional configuration, but Apache will need to use Mod_Rewrite +# to map the clean urls to the actual html files. The HtaccessGenerator site +# post processor is capable of automatically generating the necessary +# RewriteRules for use with Apache. +GENERATE_CLEAN_URLS = False + +# A list of filenames (without extensions) that will be considered listing +# pages for their enclosing folders. +# LISTING_PAGE_NAMES = ['index'] +LISTING_PAGE_NAMES = ['listing', 'index', 'default'] + +# Determines whether or not to append a trailing slash to generated urls when +# clean urls are enabled. +APPEND_SLASH = False + +# {folder : extension : (processors)} +# The processors are run in the given order and are chained. +# Only a lone * is supported as an indicator for folders. Path +# should be specified. No wildcard card support yet. + +# Starting under the media folder. For example, if you have media/css under +# your site root,you should specify just css. If you have media/css/ie you +# should specify css/ie for the folder name. css/* is not supported (yet). + +# Extensions do not support wildcards. + +MEDIA_PROCESSORS = { + '*':{ + '.css':('hydeengine.media_processors.TemplateProcessor', + 'hydeengine.media_processors.YUICompressor',), + '.ccss':('hydeengine.media_processors.TemplateProcessor', + 'hydeengine.media_processors.CleverCSS', + 'hydeengine.media_processors.YUICompressor',), + '.sass':('hydeengine.media_processors.TemplateProcessor', + 'hydeengine.media_processors.SASS', + 'hydeengine.media_processors.YUICompressor',), + '.less':('hydeengine.media_processors.TemplateProcessor', + 'hydeengine.media_processors.LessCSS', + 'hydeengine.media_processors.YUICompressor',), + '.hss':( + 'hydeengine.media_processors.TemplateProcessor', + 'hydeengine.media_processors.HSS', + 'hydeengine.media_processors.YUICompressor',), + '.js':( + 'hydeengine.media_processors.TemplateProcessor', + 'hydeengine.media_processors.YUICompressor',) + } +} + +CONTENT_PROCESSORS = { + 'prerendered/': { + '*.*' : + ('hydeengine.content_processors.PassthroughProcessor',) + } +} + +SITE_POST_PROCESSORS = { + # 'media/js': { + # 'hydeengine.site_post_processors.FolderFlattener' : { + # 'remove_processed_folders': True, + # 'pattern':"*.js" + # } + # } +} + +CONTEXT = { + 'GENERATE_CLEAN_URLS': GENERATE_CLEAN_URLS +} + +FILTER = { + 'include': (".htaccess",), + 'exclude': (".*","*~") +} + + +#Processor Configuration + +# +# Set this to the output of `which growlnotify`. If `which` returns emtpy, +# install growlnotify from the Extras package that comes with the Growl disk image. +# +# +GROWL = None + +# path for YUICompressor, or None if you don't +# want to compress JS/CSS. Project homepage: +# http://developer.yahoo.com/yui/compressor/ +YUI_COMPRESSOR = "./lib/yuicompressor-2.4.1.jar" +#YUI_COMPRESSOR = None + +# path for Closure Compiler, or None if you don't +# want to compress JS/CSS. Project homepage: +# http://closure-compiler.googlecode.com/ +#CLOSURE_COMPILER = "./lib/compiler.jar" +CLOSURE_COMPRILER = None + +# path for HSS, which is a preprocessor for CSS-like files (*.hss) +# project page at http://ncannasse.fr/projects/hss +#HSS_PATH = "./lib/hss-1.0-osx" +HSS_PATH = None # if you don't want to use HSS + +#Django settings + +TEMPLATE_DIRS = (LAYOUT_DIR, CONTENT_DIR, TMP_DIR, MEDIA_DIR) + +INSTALLED_APPS = ( + 'hydeengine', + 'django.contrib.webdesign', +) From bd0ef3d8b0dbec27b64c8a06e4a44bde3c1445ab Mon Sep 17 00:00:00 2001 From: Steven Harms Date: Wed, 9 Jun 2010 09:53:48 -0400 Subject: [PATCH 22/41] Wrote simple wordpress importer --- importers/wordpress.py | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 importers/wordpress.py diff --git a/importers/wordpress.py b/importers/wordpress.py new file mode 100755 index 00000000..06ce5041 --- /dev/null +++ b/importers/wordpress.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +import sys +import os +import re +import MySQLdb + +# I had a huge mixture of wordpress code tags, ie [sourcecode] [code]
    
    +# setting this to true will change them all to {% syntax %}
    +translate_code_tags = True
    +database_host       = "localhost"
    +database_user       = "wordpress"
    +database_password   = "your_db_password"
    +database_name       = "wordpress"
    +
    +# You can safely ignore below this
    +if translate_code_tags:
    +    open_code_regex         = re.compile(r'\[(source)*code lang(uage)*=[\'"](?P.*)[\'"]\]')
    +    close_code_regex        = re.compile(r'\[\/(source)*code\]')
    +    lower_case_lexer_regex  = re.compile(r'{% syntax C %}')
    +    pre_open_remove_regex   = re.compile(r'
    ')
    +    pre_close_remove_regex  = re.compile(r'
    ') + code_open_remove_regex = re.compile(r'') + code_close_remove_regex = re.compile(r'') + +# Content directory is just whatever directory you have the years in, ie /my_hyde_blog/content/blog +if len(sys.argv) != 2: + print "Usage: %s [path_to_content_directory]" % sys.argv[0] + sys.exit(1) + +output_directory = str(sys.argv[1]) +if output_directory[-1] != '/': + output_directory = output_directory + '/' + +print "Outputting files to " + output_directory + +conn = MySQLdb.connect ( host = "localhost", + user = "wpuser", + passwd = "phpaccess", + db = "wp") + +cursor = conn.cursor() +query = """select post_title, post_name, post_date, post_content, post_excerpt, ID, guid from wp_posts where post_status = 'publish' and post_type = 'post'""" + +cursor.execute(query) + +while True: + row = cursor.fetchone() + + if row == None: + break + + title = row[0] + slug = row[1] + date = row[2] + content = row[3] + name = "%02d-%02d-%02d-%s.html" % (date.year, date.month, date.day, slug) + + if translate_code_tags: + content = open_code_regex.sub(r'{% syntax \g %}', content) + content = lower_case_lexer_regex.sub('{% syntax c %}', content) + content = close_code_regex.sub(r'{% endsyntax %}', content) + content = pre_open_remove_regex.sub('', content) + content = pre_close_remove_regex.sub('', content) + content = code_open_remove_regex.sub(r'{% syntax %}', content) + content = code_close_remove_regex.sub(r'{% endsyntax %}', content) + + try: + os.makedirs(output_directory + str(date.year) + "/") + except OSError: + pass + file_handle = open(output_directory + str(date.year) + "/" + name, 'w') + file_handle.write("{% extends \"_post.html\" %}\n") + file_handle.write("{%load webdesign %}") + file_handle.write("{%hyde\n title: \"" + title + "\"\n created: " + str(date) + "\n%}\n") + file_handle.write("{% block article %}\n") + file_handle.write(content) + file_handle.write("{% endblock %}\n") + file_handle.close() + print "Wrote file " + name + +cursor.close() +conn.close() From 8596521ecf63273e7cc979c7da638f4515146e3e Mon Sep 17 00:00:00 2001 From: James Willcox Date: Wed, 9 Jun 2010 10:08:09 -0400 Subject: [PATCH 23/41] moar stuff --- Makefile | 4 +- snorp.net/content/.htaccess | 15 ---- snorp.net/content/about/about.html | 17 ---- snorp.net/content/contact/contact.html | 18 ++++ snorp.net/layout/{_about.html => _page.html} | 9 ++ snorp.net/layout/skeleton/_atom.xml | 50 +++++------ snorp.net/layout/skeleton/_footer.html | 7 +- snorp.net/layout/skeleton/_header.html | 18 ++-- snorp.net/layout/skeleton/_listing.html | 2 +- snorp.net/media/css/base.css | 89 ++++++++++++++----- snorp.net/media/images/facebook_icon.png | Bin 0 -> 859 bytes snorp.net/media/images/linkedin_icon.png | Bin 0 -> 1405 bytes snorp.net/media/images/subscribe.png | Bin 0 -> 2694 bytes snorp.net/media/images/twitter_icon.png | Bin 0 -> 1176 bytes snorp.net/media/js/base.js | 78 +++++++++++----- 15 files changed, 191 insertions(+), 116 deletions(-) delete mode 100644 snorp.net/content/.htaccess delete mode 100644 snorp.net/content/about/about.html create mode 100644 snorp.net/content/contact/contact.html rename snorp.net/layout/{_about.html => _page.html} (50%) create mode 100644 snorp.net/media/images/facebook_icon.png create mode 100644 snorp.net/media/images/linkedin_icon.png create mode 100644 snorp.net/media/images/subscribe.png create mode 100644 snorp.net/media/images/twitter_icon.png diff --git a/Makefile b/Makefile index 7141cd8d..079626c1 100644 --- a/Makefile +++ b/Makefile @@ -3,5 +3,5 @@ all: refresh refresh: ./hyde.py -g -s ./snorp.net -run: refresh - ./hyde.py -k -w -s ./snorp.net \ No newline at end of file +monitor: + ./hyde.py -k -g -s ./snorp.net diff --git a/snorp.net/content/.htaccess b/snorp.net/content/.htaccess deleted file mode 100644 index c02394b2..00000000 --- a/snorp.net/content/.htaccess +++ /dev/null @@ -1,15 +0,0 @@ -{% if GENERATE_CLEAN_URLS %} -RewriteEngine on -RewriteBase {{ node.site.settings.SITE_ROOT }} - -{% hyde_listing_page_rewrite_rules %} - -# listing pages whose names are the same as their enclosing folder's -RewriteCond %{REQUEST_FILENAME}/$1.html -f -RewriteRule ^([^/]*)/$ %{REQUEST_FILENAME}/$1.html - -# regular pages -RewriteCond %{REQUEST_FILENAME}.html -f -RewriteRule ^.*$ %{REQUEST_FILENAME}.html - -{% endif %} diff --git a/snorp.net/content/about/about.html b/snorp.net/content/about/about.html deleted file mode 100644 index 49c44d6c..00000000 --- a/snorp.net/content/about/about.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "_about.html" %} -{%hyde - title: "About" -%} -{% block article %} - -Hyde is a static website generator using django templates & an "extensible generation engine". - -Read more about Hyde [here:]({{content}}/{{links.introducing_hyde}}) - - -{% endblock %} - - - - - diff --git a/snorp.net/content/contact/contact.html b/snorp.net/content/contact/contact.html new file mode 100644 index 00000000..513c33a1 --- /dev/null +++ b/snorp.net/content/contact/contact.html @@ -0,0 +1,18 @@ +{% extends "_page.html" %} +{%hyde + title: "Contact" +%} +{% block article %} + +
    + +James Willcox + +Email and GTalk: snorp@snorp.net + + + + + + +{% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/_about.html b/snorp.net/layout/_page.html similarity index 50% rename from snorp.net/layout/_about.html rename to snorp.net/layout/_page.html index eeefd4bb..5f2efb31 100644 --- a/snorp.net/layout/_about.html +++ b/snorp.net/layout/_page.html @@ -11,4 +11,13 @@ {% endmarkdown %} {% endfilter %} +{% endblock %} + +{% block content_header %} +
    +

    {{ page.title }}

    +
    +{% endblock %} +{% block context_nav %} +{% include "skeleton/_context_nav.html" %} {% endblock %} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_atom.xml b/snorp.net/layout/skeleton/_atom.xml index cf1219fd..d393622c 100644 --- a/snorp.net/layout/skeleton/_atom.xml +++ b/snorp.net/layout/skeleton/_atom.xml @@ -1,30 +1,30 @@ {%spaceless%} - {% block title %}{{site.name}} {{page.node.name}}{%endblock%} - {%block self_url %} - - {%endblock%} - {%block site_url %} - - {%endblock%}{%block feed_extra%}{%endblock%} - {{now|xmldatetime}} - {{site.full_url}} - {% for node_page in page.node.walk_pages %} - {% if node_page.display_in_list %} - - {{node_page.title}} - {{site.author}} - - {{node_page.updated|default:node_page.created|xmldatetime}} - {{node_page.created|xmldatetime}} - {{node_page.full_url}} - {%block entry_extra%}{%endblock%} - - {%filter force_escape%}{% render_article node_page %}{%endfilter%} - - - {%endif%} - {% endfor %} + {% block title %}{{site.name}} {{page.node.name}}{%endblock%} + {%block self_url %} + + {%endblock%} + {%block site_url %} + + {%endblock%}{%block feed_extra%}{%endblock%} + {{now|xmldatetime}} + {{site.full_url}} + {% for node_page in page.node.walk_pages %} + {% if node_page.display_in_list %} + + {{node_page.title}} + {{site.author}} + + {{node_page.updated|default:node_page.created|xmldatetime}} + {{node_page.created|xmldatetime}} + {{node_page.full_url}} + {%block entry_extra%}{%endblock%} + + {%filter force_escape%}{% render_article node_page %}{%endfilter%} + + + {%endif%} + {% endfor %} {%endspaceless%} \ No newline at end of file diff --git a/snorp.net/layout/skeleton/_footer.html b/snorp.net/layout/skeleton/_footer.html index 848e847a..9c03f873 100644 --- a/snorp.net/layout/skeleton/_footer.html +++ b/snorp.net/layout/skeleton/_footer.html @@ -1,8 +1,9 @@