Commits

Virgil Dupras  committed e0aa584

Modernized codebase to adapt to the latest changes in hscommon. Fixed imports (hsutil --> hscommon, hscommon.job --> jobprogress), converted changelog format.

  • Participants
  • Parent commits 880959e

Comments (0)

Files changed (46)

 0de9e6033a1fb339a293923241ebc3f28ec5853f cocoalib
-5ec95a9289aeb0a84655b3e45118727f32727090 hscommon
-9239f1a8f011c36883a72f38e9e2ffeed54b8425 qtlib
+d2cd0d380e01d2c68814e11eee1baa6204d757d7 hscommon
+9fa20059180e555e009c8fe28a33b28aa4a66473 qtlib
 There are also other sub-folder that comes from external repositories (automatically checked out
 with svn:externals):
 
-- hsutil: A collection of helpers used across HS applications.
 - hscommon: Code common to HS apps, yet not common enough to be called "hsutil"
 - cocoalib: A collection of helpers used across Cocoa UI codebases of HS applications.
 - qtlib: A collection of helpers used across Qt UI codebases of HS applications.
 General dependencies
 -----
 
-- Python 2.6 (http://www.python.org)
-- hsutil 1.0.2 (http://hg.hardcoded.net/hsutil)
+- Python 3.1 (http://www.python.org)
+- hsaudiotag3k 1.1.1 (http://hg.hardcoded.net/hsaudiotag)
+- jobprogress (http://hg.hardcoded.net/jobprogress)
 - PyYaml, for help files and the build system. (http://pyyaml.org/)
 - Markdown, for help files. (http://www.freewisdom.org/projects/python-markdown/)
-- py.test, to run unit tests. (http://codespeak.net/py/dist/test/)
+- py.test 2.0, to run unit tests. (http://pytest.org)
 
 OS X prerequisites
 -----
-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-12-31
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 from setuptools import setup
 import yaml
 
-from hscommon import helpgen
-from hscommon.build import add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages
+import helpgen
+from hscommon.build import (add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages,
+    build_all_qt_locs)
+
+def build_cocoa(dev):
+    if not dev:
+        print("Building help index")
+        help_path = op.abspath('help/musicguru_help')
+        os.system('open -a /Developer/Applications/Utilities/Help\\ Indexer.app {0}'.format(help_path))
+    
+    print("Building mg_cocoa.plugin")
+    if op.exists('build'):
+        shutil.rmtree('build')
+    os.mkdir('build')
+    if not dev:
+        copy_packages(['core', 'hsaudiotag', 'hsfs', 'hscommon', 'jobprogress'], 'build')
+    shutil.copy('cocoa/mg_cocoa.py', 'build')
+    os.chdir('build')
+    script_args = ['py2app', '-A'] if dev else ['py2app']
+    setup(
+        script_args = script_args,
+        plugin = ['mg_cocoa.py'],
+        setup_requires = ['py2app'],
+    )
+    os.chdir('..')
+    if op.exists('cocoa/mg_cocoa.plugin'):
+        shutil.rmtree('cocoa/mg_cocoa.plugin')
+    shutil.move('build/dist/mg_cocoa.plugin', 'cocoa/mg_cocoa.plugin')
+    if dev:
+        # In alias mode, the tweakings we do to the pythonpath aren't counted in. We have to
+        # manually put a .pth in the plugin
+        pluginpath = 'cocoa/mg_cocoa.plugin'
+        pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
+        open(pthpath, 'w').write(op.abspath('.'))
+    os.chdir('cocoa')
+    print("Building the XCode project")
+    os.system('xcodebuild')
+    os.chdir('..')
+
+def build_qt():
+    print("Building .ts files")
+    build_all_qt_locs(op.join('qtlib', 'lang'))
+    print("Building Qt stuff")
+    build_all_qt_ui(op.join('qt', 'ui'))
+    os.chdir('qt')
+    print_and_do("pyrcc4 -py3 mg.qrc > mg_rc.py")
+    os.chdir('..')
 
 def main():
     conf = yaml.load(open('conf.yaml'))
     destpath = op.abspath(op.join('help', 'musicguru_help'))
     helpgen.gen(basepath, destpath, profile=profile)
     if ui == 'cocoa':
-        if not dev:
-            print("Building help index")
-            help_path = op.abspath('help/musicguru_help')
-            os.system('open -a /Developer/Applications/Utilities/Help\\ Indexer.app {0}'.format(help_path))
-        
-        print("Building mg_cocoa.plugin")
-        if op.exists('build'):
-            shutil.rmtree('build')
-        os.mkdir('build')
-        if not dev:
-            copy_packages(['core', 'hsutil', 'hsaudiotag', 'hsfs', 'hscommon'], 'build')
-        shutil.copy('cocoa/mg_cocoa.py', 'build')
-        os.chdir('build')
-        script_args = ['py2app', '-A'] if dev else ['py2app']
-        setup(
-            script_args = script_args,
-            plugin = ['mg_cocoa.py'],
-            setup_requires = ['py2app'],
-        )
-        os.chdir('..')
-        if op.exists('cocoa/mg_cocoa.plugin'):
-            shutil.rmtree('cocoa/mg_cocoa.plugin')
-        shutil.move('build/dist/mg_cocoa.plugin', 'cocoa/mg_cocoa.plugin')
-        if dev:
-            # In alias mode, the tweakings we do to the pythonpath aren't counted in. We have to
-            # manually put a .pth in the plugin
-            pluginpath = 'cocoa/mg_cocoa.plugin'
-            pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
-            open(pthpath, 'w').write(op.abspath('.'))
-        os.chdir('cocoa')
-        print("Building the XCode project")
-        os.system('xcodebuild')
-        os.chdir('..')
+        build_cocoa(dev)
     elif ui == 'qt':
-        build_all_qt_ui(op.join('qt', 'ui'))
-        os.chdir('qt')
-        print_and_do("pyrcc4 -py3 mg.qrc > mg_rc.py")
-        os.chdir('..')
+        build_qt()
 
 if __name__ == '__main__':
     main()

File cocoa/mg_cocoa.py

 import sys
 import objc
 
-from hscommon import job
+from jobprogress import job
 from hscommon.cocoa.inter import signature, PyFairware
 from hscommon.cocoa.objcmin import NSObject
 
 
 import os
 import os.path as op
-import shutil
 
 import hsfs as fs
 from hsfs import phys
 from hsfs.stats import StatsList
-from hsutil.conflict import get_unconflicted_name, get_conflicted_name
-from hsutil.files import clean_empty_dirs
-from hsutil.misc import cond, tryint
-from hsutil.str import format_size, format_time, multi_replace, FT_MINUTES, FS_FORBIDDEN
-from hscommon.job import JobCancelled
+from hscommon.conflict import get_unconflicted_name, get_conflicted_name
+from hscommon.util import tryint, format_size, format_time, multi_replace
+from jobprogress.job import JobCancelled
 from hscommon.reg import RegistrableApplication
 
 from . import design
-from .fs_utils import BatchOperation
+from .fs_utils import BatchOperation, FS_FORBIDDEN
+from .util import clean_empty_dirs
 from .sqlfs.music import Root, VOLTYPE_CDROM, VOLTYPE_FIXED, MODE_PHYSICAL, MODE_NORMAL
 
 class MusicGuru(RegistrableApplication):
         self.board = design.Board()
     
     def AddLocation(self, path, name, removeable, job):
-        vol_type = cond(removeable, VOLTYPE_CDROM, VOLTYPE_FIXED)
+        vol_type = VOLTYPE_CDROM if removeable else VOLTYPE_FIXED
         ref = phys.music.Directory(None, path)
         self.collection.add_volume(ref, name, vol_type, job)
     
     def GetSelectionInfo(self, item):
         def output_stats(info, item):
             info.append(('Size',format_size(item.get_stat('size'),2)))
-            info.append(('Time',format_time(item.get_stat('duration'),FT_MINUTES)))
+            info.append(('Time',format_time(item.get_stat('duration'), with_hours=False)))
             info.append(('Extensions',','.join(item.get_stat('extension',[]))))
             info.append(('# Artists',len(item.get_stat('artist',[]))))
             info.append(('# Albums',len(item.get_stat('album',[]))))
             new_info.append(('Year',item.year))
             new_info.append(('Track',"%02d" % item.track))
             new_info.append(('Size',format_size(item.size,2)))
-            new_info.append(('Time',format_time(item.duration,FT_MINUTES)))
+            new_info.append(('Time',format_time(item.duration, with_hours=False)))
             new_info.append(('Bitrate',item.bitrate))
             new_info.append(('Comment',item.comment))
         return new_info

File core/app_cocoa.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-import os
-import os.path as op
-from threading import Thread
-import tempfile
-
-import hsfs as fs
-from hsutil.conflict import is_conflicted
-from hsutil.misc import cond, dedupe
-from hsutil.path import Path
-from hsutil.str import format_size, format_time, FT_MINUTES
+from hscommon.conflict import is_conflicted
+from hscommon.util import dedupe, format_size, format_time
+from hscommon.path import Path
 from hscommon import cocoa
 
 from . import app, sqlfs as sql
     #---Data
     def GetNodeData(self, node):
         if node.is_container:
-            img_name = cond(node.allconflicts,'folder_conflict_16','folder_16')
+            img_name = 'folder_conflict_16' if node.allconflicts else 'folder_16'
             parent_volumes = dedupe(song.original.parent_volume for song in node.iterallfiles())
             return [
                 node.name,
                 img_name,
             ]
         else:
-            img_name = cond(is_conflicted(node.name),'song_conflict_16','song_16')
+            img_name = 'song_conflict_16' if is_conflicted(node.name) else 'song_16'
             return [
                 node.name,
                 node.original.parent_volume.name,
                 0,
                 format_size(node.size,2,2,False),
-                format_time(node.duration,FT_MINUTES),
+                format_time(node.duration, with_hours=False),
                 img_name,
             ]
     

File core/app_test.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from hsutil.path import Path
-from hsutil.testcase import TestCase
+from hscommon.path import Path
+from hscommon.testcase import TestCase
 
 from .app_cocoa import MusicGuru
 from .sqlfs.music import Root, VOLTYPE_CDROM

File core/design.py

 
 import random
 
-from hscommon import job
-from hsutil import conflict
-from hsutil.str import pluralize, format_size, format_time, FT_DECIMAL
+from jobprogress import job
+from hscommon import conflict
+from hscommon.util import pluralize, format_size, format_time_decimal
 
 from . import fs_utils, manualfs
 
             return "%s (%d conflicts), %s, %s" % (
                 pluralize(self.get_stat('filecount'), 'song'),
                 len(self.allconflicts),
-                format_time(self.get_stat('duration'), FT_DECIMAL),
+                format_time_decimal(self.get_stat('duration')),
                 format_size(self.get_stat('size'), 2))
         else:
             return "%s, %s, %s" % (
                 pluralize(self.get_stat('filecount'), 'song'),
-                format_time(self.get_stat('duration'), FT_DECIMAL),
+                format_time_decimal(self.get_stat('duration')),
                 format_size(self.get_stat('size'), 2))
 
     #---Public

File core/design_test.py

 import os
 import random
 
-from hsutil.testutil import eq_
+from hscommon.testutil import eq_
 
 from . import fs_utils, manualfs, design
 from .sqlfs.music import Root, VOLTYPE_CDROM, VOLTYPE_FIXED

File core/fs_utils.py

 import tempfile
 
 import hsfs as fs
-from hsutil import conflict, io
-from hsutil.misc import tryint, dedupe
-from hsutil.path import Path
-from hsutil.str import multi_replace, FS_FORBIDDEN, rem_file_ext, process_tokens
-from hscommon.job import nulljob, JobCancelled
+from hscommon import conflict, io
+from hscommon.util import tryint, dedupe, multi_replace, rem_file_ext
+from hscommon.path import Path
+from jobprogress.job import nulljob, JobCancelled
 
+from .util import process_tokens
 from .sqlfs.music import VOLTYPE_CDROM, MODE_TOKEN, MODE_NORMAL
 from .manualfs import AutoResolve
 
 (WS_DONT_TOUCH,
  WS_SPACES_TO_UNDERSCORES,
  WS_UNDERSCORES_TO_SPACES) = list(range(3))
+
+FS_FORBIDDEN = '/\\:*?"<>|'
  
 def smart_move(items, dest, allow_merge=True):
     """move items into dest by taking care of name conflicts.

File core/fs_utils_test.py

 import gc
 import shutil
 
-from hsutil.testutil import eq_
-
+from hscommon.testutil import eq_
 import hsfs.music
 from hsfs import phys
 from hsfs.tests.phys_test import create_fake_fs, create_unicode_test_dir
-from hsutil.path import Path
-from hscommon.job import Job
+from hscommon.path import Path
+from jobprogress.job import Job
 
 from . import manualfs
 from .testcase import TestCase

File core/manualfs.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2004-12-27
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 # http://www.hardcoded.net/licenses/bsd_license
 
 import hsfs as fs
-from hscommon import job
-from hsutil.misc import nonone
-from hsutil.conflict import get_conflicted_name, is_conflicted
+from jobprogress import job
+from hscommon.util import nonone
+from hscommon.conflict import get_conflicted_name, is_conflicted
 
-class _CopyOf(object):
+class _CopyOf:
     #--- Public
     
     def copy(self, refnode):

File core/manualfs_test.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2005-01-14
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 import weakref
 import gc
 
-from hsutil.testutil import eq_, assert_raises
-
-from hscommon import job
-from hsutil.decorators import log_calls
-from hsutil.path import Path
+from pytest import raises
+from hscommon.testutil import eq_, log_calls
+from jobprogress import job
+from hscommon.path import Path
 
 import hsfs as fs
 from .manualfs import *
     source_file = source.AddFile('foobar')
     dest = TestDir(None,'dest')
     dest_dir = dest.AddDir('foobar')
-    assert_raises(fs.AlreadyExistsError, source_file.move, dest)
+    with raises(fs.AlreadyExistsError):
+        source_file.move(dest)
     assert source_file in source
 
 def test_already_exists():
     ref = TestDir(None,'')
     ref.AddFile('foobar')
-    assert_raises(fs.AlreadyExistsError, ref.AddFile, 'foobar')
-    assert_raises(fs.AlreadyExistsError, ref.AddDir, 'foobar')
+    with raises(fs.AlreadyExistsError):
+        ref.AddFile('foobar')
+    with raises(fs.AlreadyExistsError):
+        ref.AddDir('foobar')
 
 def test_move_dir_conflict():
     dir1 = TestDir(None,'')
     dir2 = TestDir(None,'')
     subdir = dir1.AddDir('foobar')
     dir2.AddFile('foobar')
-    assert_raises(fs.AlreadyExistsError, subdir.move, dir2)
+    with raises(fs.AlreadyExistsError):
+        subdir.move(dir2)
     assert 'foobar' in dir1
     assert 'foobar' in dir2
 
     eq_(1, len(test))
     eq_('dir1', test[0].name)
     mainjob = job.Job(1,lambda progress:progress < 30)
-    assert_raises(job.JobCancelled, test.add_dir_copy, root, 'dir2', mainjob)
+    with raises(job.JobCancelled):
+        test.add_dir_copy(root, 'dir2', mainjob)
     eq_(1, len(test))
     eq_('dir1', test[0].name)
 
     ref = TestDir(None,'test')
     root = Directory(None,'')
     root.add_dir_copy(ref,'foobar')
-    assert_raises(fs.AlreadyExistsError, root.add_dir_copy, ref, 'foobar')
+    with raises(fs.AlreadyExistsError):
+        root.add_dir_copy(ref, 'foobar')
 
 def test_original():
     root1 = TestDir(None,'')

File core/sqlfs/_sql.py

 # http://www.hardcoded.net/licenses/bsd_license
 
 import sqlite3 as sqlite
-import time
 from weakref import WeakValueDictionary
 
 import hsfs as fs
-from hscommon.job import nulljob, JobCancelled
-from hsutil.misc import tryint
-from hsutil.str import multi_replace
+from jobprogress.job import nulljob, JobCancelled
+from hscommon.util import tryint, multi_replace
 import hscommon.sqlite
 
 (NODE_TYPE_DIR,

File core/sqlfs/music.py

 import hsfs as fs
 import hsfs.music
 from hsfs.phys import music
-from hsutil import io
-from hsutil.path import Path
-from hscommon.job import nulljob, JobCancelled
+from hscommon import io
+from hscommon.path import Path
+from jobprogress.job import nulljob, JobCancelled
 
-class Node(object):
+class Node:
     @property
     def parent_volume(self):
         if self.parent is not None:

File core/sqlfs/music_test.py

 import shutil
 
 from hsfs.phys import music
-from hsutil.path import Path
-from hscommon.job import Job, JobCancelled
+from hscommon.path import Path
+from jobprogress.job import Job, JobCancelled
 
 from ..testcase import TestCase
 from .. import manualfs
         self.v = self.root.new_directory('vol')
     
     def test_true(self):
-        self.v.initial_path = TestCase.filepath('') #it always exists
+        self.v.initial_path = self.filepath('') #it always exists
         self.assertTrue(self.v.is_available)
     
     def test_false(self):

File core/sqlfs/sql_test.py

 import time
 import weakref
 
-from hsutil.testutil import eq_
+from hscommon.testutil import eq_
 
 import hsfs as fs
 from .. import manualfs
-from hscommon.job import Job, JobCancelled
+from jobprogress.job import Job, JobCancelled
 
 from ..testcase import TestCase
 from ._sql import *

File core/sqlfs/utils.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from hsutil.str import sqlite_escape
+def escape(s, to_escape, escape_with='\\'):
+    return ''.join((escape_with+c if c in to_escape else c) for c in s)
 
-from ._sql import Node
+def sqlite_escape(s):
+    return escape(s, "'", "'")
 
-class DBBuffer(object):
+class DBBuffer:
     """Progressively buffers a SQL SELECT request, using LIMIT
     """
     def __init__(self,con,sql,lookahead=10):

File core/testcase.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from hsutil.testcase import TestCase as TestCaseBase
-from hsutil.path import Path
+import py.path
+from hscommon.testcase import TestCase as TestCaseBase
+from hscommon.path import Path
 
 class TestCase(TestCaseBase):
     def tearDown(self):
         if hasattr(self, '_created_directories'):
             self.global_teardown()
     
-    @classmethod
-    def datadirpath(cls):
-        return Path(__file__)[:-1] + 'testdata'
-    
     def tmpdir(self, *args, **kwargs):
         if not hasattr(self, '_created_directories'):
             self.global_setup()
         if not hasattr(self, '_created_directories'):
             self.global_setup()
         return TestCaseBase.mock(self, *args, **kwargs)
+    
+    @property
+    def datadirpath(self):
+        return py.path.local(str(Path(__file__)[:-1] + 'testdata'))

File core/util.py

+# Created By: Virgil Dupras
+# Created On: 2011-02-09
+# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
+# 
+# This software is licensed under the "BSD" License as described in the "LICENSE" file, 
+# which should be included with this package. The terms are also available at 
+# http://www.hardcoded.net/licenses/bsd_license
+
+# Stuff that used to be in hsutil and that is not generic enough to end up in hscommon
+
+import re
+
+from hscommon import io
+from hscommon.util import delete_if_empty
+
+def clean_empty_dirs(path, deleteself=False, files_to_delete=[]):
+    """Recursively delete empty dirs in directory. 'directory' is deleted only
+    if empty and 'deleteself' is True.
+    Returns the list of delete paths.
+    files_to_delete: The name is clear enough. However, files in
+        this list will ONLY be deleted if it makes the directory deletable
+        thereafter (In other words, if the directory contains files not in the
+        list, NO file will be deleted)
+    """
+    result = []
+    subdirs = [name for name in io.listdir(path) if io.isdir(path + name)]
+    for subdir in subdirs:
+        result.extend(clean_empty_dirs(path + subdir, True, files_to_delete))
+    if deleteself and delete_if_empty(path, files_to_delete):
+        result.append(str(path))
+    return result
+
+re_process_tokens = re.compile('\%[\w:\s]*\%',re.IGNORECASE)
+
+def process_tokens(s, handlers, data=None):
+    """Process a token filled (%token%) string using handlers.
+    
+    s is a string containing tokens. Tokens are words between two
+    percent (%) signs. They can optionally contain parameters, which are
+    defined with :, like %token:param:other_param%.
+    
+    handlers is a dictionnary of strings mapped to callable. the string
+    represent a supported token name, and the callable must return a string
+    that will replace the token. If the callabale returns None, or doesn't
+    have the number of parameters matching with the number of params
+    present in the token, the token will be substitued by '(none)'
+    
+    if handlers is a callable instead of a dictionnary, it means that
+    the user wants only a single handler. in this case, the token name will be
+    passed as the first parameter. if there is a data, data will be the second
+    param, and then will follow the sub params.
+    
+    if data is not None, every handler will receive it as their first
+    parameter. Don't forget to think about it when writing your handlers!
+    """
+    def replace(match):
+        result = None
+        expression = match.string[match.start()+1:match.end()-1].lower()
+        token = expression.split(':')[0]
+        params = expression.split(':')[1:]
+        if data is not None:
+            params.insert(0,data)
+        if hasattr(handlers, '__call__'):
+            params.insert(0,token)
+            handler = handlers
+        else:
+            handler = handlers.get(token,None)
+        if hasattr(handler, '__call__'):
+            try:
+                result = handler(*params)
+            except TypeError:
+                pass
+        if result is None:
+            result = ''
+        result = result.replace('\n', ' ').replace('\0', ' ').strip()
+        if result == '':
+            result = '(none)'
+        return result
+    
+    return re_process_tokens.sub(replace, s)

File help/changelog

+=== 1.4.3 (2010-11-01)
+
+* Fixed various little glitches related to the Fairware pop up.
+
+=== 1.4.2 (2010-09-30)
+
+* Re-licensed musicGuru to BSD and made it [Fairware](http://open.hardcoded.net/about/)
+
+=== 1.4.1 (2010-07-11)
+
+* Fixed a crash on adding locations with buggy encodings. (#10)
+
+=== 1.4.0 (2010-04-09)
+
+* Now 64-bit under Mac OS X.
+* Added Linux support.
+* Improved the materialize process.
+* Dropped OS X Tiger (10.4) support.
+
+=== 1.3.6 (2010-01-02)
+
+* Fixed a crash on node rename in the Design Board. [OS X] (#8)
+* Fixed a crash on Copy and Move materialize actions. [Windows]
+
+=== 1.3.5 (2009-10-17)
+
+* Windows version ported to Qt.
+
+=== 1.3.5 (2008-11-08)
+
+* **Added** support for AIFF files.
+* **Improved** MPEG files duration detection.
+
+=== 1.3.4 (2008-01-16)
+
+* **Improved** the Mass Rename panel. The custom model is now remembered between runs.
+* **Fixed** an issue sometimes preventing the application from starting at all.
+* **Fixed** the collection update being launched on startup. It can now be cancelled.
+
+=== 1.3.3 (2007-10-19)
+
+* **Added** a way to change the path of a location.
+* **Improved** the speed of the Update Collection process.
+* **Fixed** various crashes related to external drives.
+* **Fixed** Unsplit CD/DVD, which sometimes left the design board in a messy state.
+* **Fixed** Split to DVD under Windows (It made musicGuru crash).
+
+=== 1.3.2 (2007-10-07)
+
+* **Improved** UI responsiveness (using threads) under Mac OS X.
+* **Fixed** some user interface annoyances under Windows.
+* **Fixed** a bug with non-latin directory names.
+* **Fixed** a bug on Add Location's cancel operation.
+* **Fixed** a small issue with AAC decoding.
+* **Fixed** a bug with Move operations to external drives.
+
+=== 1.3.1 (2007-03-04)
+
+* **Fixed** a bug with the move operation.
+* **Fixed** a bug where the tool windows would sometimes stop showing up (Windows).
+* **Fixed** keyboard navigation quirks (Windows).
+
+=== 1.3.0 (2006-12-08)
+
+* **Changed** the Windows interface. It is now .NET based.
+* **Added** an auto-update feature to the windows version.
+* **Changed** the way Mass Rename handles empty tags. Instead of putting "(none)" where there is no tag, musicGuru just doesn't rename the file, and put it in the "(not renamed)" directory.
+* **Added** blue coloring for locations based on a removable media.
+
+=== 1.2.0 (2006-10-12)
+
+* **Changed** the music collection format to a SQLite based format. It makes musicGuru generally faster.
+* **Added** an auto-update feature in the Mac OS X version (with Sparkle).
+* **Fixed** numerous small bugs.
+
+=== 1.1.3 (2006-09-04)
+
+* **Fixed** a bug sometimes making the collection updating procedure to fail.
+* **Fixed** a bug in the mp3 decoding unit.
+
+=== 1.1.2 (2006-08-26)
+
+* **Improved** the speed of musicGuru when adding songs.
+* **Replaced** the 2 locations drawers by a single location panel under Mac OS X.
+
+=== 1.1.1 (2006-08-15)
+
+* **Improved** unicode support.
+* **Improved** MP3, WMA and AAC metadata decoding.
+* musicGuru is now a Universal application under Mac OS X.
+
+=== 1.1.0 (2006-03-28)
+
+* **Improved** speed. Especially on Mac OS X.
+* **Improved** the Add Location interface. It went from a wizard to a simple dialog.
+* **Improved** progression feedback for Mass Rename and Split features.
+* **Improved** Design Board interface for the Windows version.
+* **Fixed** bug in the Split dialog.
+* **Fixed** bug in the CD recording process.
+
+=== 1.0.8 (2006-03-11)
+
+* **Fixed** occasional problems when reading unicode in wma and mp3 files.
+* **Fixed** unicode issues for Win98 users.
+* **Fixed** a DLL dependency bug.
+
+=== 1.0.4 (2006-03-04)
+
+* **Fixed** problems when reading some kind of wma files.
+* **Fixed** occasional errors when saving music collection.
+
+=== 1.0.3 (2006-01-28)
+
+* **Added** a "custom capacity" option in the splitting panel.
+* **Fixed** issues with disc recording.
+* **Fixed** occasional inaccuracies in the Info panel.
+
+=== 1.0.2 (2006-01-24)
+
+* **Fixed** issues with the registration system.
+* **Fixed** issues packaging issues in the windows MSI.            
+
+=== 1.0.1 (2006-01-21)
+
+* **Improved** memory usage of the Design Board.
+* **Improved** the Design Board by making it case insensitive.
+* **Fixed** issues with disc recording.
+* **Fixed** issues with very long filenames in Windows.
+* **Fixed** issues with the Splitting Options dialog.
+
+=== 1.0.0 (2006-01-17)
+
+* Initial release.

File help/changelog.yaml

-- date: 2010-11-01
-  version: 1.4.3
-  description: |
-    * Fixed various little glitches related to the Fairware pop up.
-- date: 2010-09-30
-  version: 1.4.2
-  description: |
-    * Re-licensed musicGuru to BSD and made it [Fairware](http://open.hardcoded.net/about/)
-- date: 2010-07-11
-  version: 1.4.1
-  description: |
-    * Fixed a crash on adding locations with buggy encodings. (#10)
-- date: 2010-04-09
-  version: 1.4.0
-  description: |
-    * Now 64-bit under Mac OS X.
-    * Added Linux support.
-    * Improved the materialize process.
-    * Dropped OS X Tiger (10.4) support.
-- date: 2010-01-02
-  version: 1.3.6
-  description: |
-    * Fixed a crash on node rename in the Design Board. [OS X] (#8)
-    * Fixed a crash on Copy and Move materialize actions. [Windows]
-- date: 2009-10-17
-  version: 1.3.5
-  description: "Windows version ported to Qt."
-- date: 2008-11-08
-  version: 1.3.5
-  description: |
-    * **Added** support for AIFF files.
-    * **Improved** MPEG files duration detection.
-- date: 2008-01-16
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> the Mass Rename panel. The custom\
-    \ model is now remembered between runs.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> an\
-    \ issue sometimes preventing the application from starting at all.</li>\n\t\t\t\
-    \t\t\t<li><b>Fixed</b> the collection update being launched on startup. It can\
-    \ now be cancelled.</li>\n\t\t            </ul>"
-  version: 1.3.4
-- date: 2007-10-19
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> a way to change the path of a location.</li>\n\
-    \t\t\t\t\t\t<li><b>Improved</b> the speed of the Update Collection process.</li>\n\
-    \t\t\t\t\t\t<li><b>Fixed</b> various crashes related to external drives.</li>\n\
-    \t\t\t\t\t\t<li><b>Fixed</b> Unsplit CD/DVD, which sometimes left the design board\
-    \ in a messy state.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> Split to DVD under Windows\
-    \ (It made musicGuru crash).</li>\n\t\t            </ul>"
-  version: 1.3.3
-- date: 2007-10-07
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> UI responsiveness (using threads)\
-    \ under Mac OS X.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> some user interface annoyances\
-    \ under Windows</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with non-latin directory\
-    \ names.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug on Add Location's cancel operation.</li>\n\
-    \t\t\t\t\t\t<li><b>Fixed</b> a small issue with AAC decoding.</li>\n\t\t\t\t\t\
-    \t<li><b>Fixed</b> a bug with Move operations to external drives.</li>\n\t\t \
-    \           </ul>"
-  version: 1.3.2
-- date: 2007-03-04
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with the move operation.</li>\n\
-    \t\t\t\t\t\t<li><b>Fixed</b> a bug where the tool windows would sometimes stop\
-    \ showing up (Windows).</li>\n\t\t\t\t\t\t<li><b>Fixed</b> keyboard navigation\
-    \ quirks (Windows).</li>\n\t\t            </ul>"
-  version: 1.3.1
-- date: 2006-12-08
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Changed</b> the Windows interface. It is\
-    \ now .NET based.</li>\n\t\t\t\t\t\t<li><b>Added</b> an auto-update feature to\
-    \ the windows version.</li>\n\t\t\t\t\t\t<li><b>Changed</b> the way Mass Rename\
-    \ handles empty tags. Instead of putting \"(none)\" where there is no tag, musicGuru\
-    \ just doesn't rename the file, and put it in the \"(not renamed)\" directory.</li>\n\
-    \t\t\t\t\t\t<li><b>Added</b> blue coloring for locations based on a removable\
-    \ media.</li>\n\t\t            </ul>"
-  version: 1.3.0
-- date: 2006-10-12
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Changed</b> the music collection format to\
-    \ a SQLite based format. It makes musicGuru generally faster.</li>\n\t\t\t\t\t\
-    \t<li><b>Added</b> an auto-update feature in the Mac OS X version (with Sparkle).</li>\n\
-    \t\t\t\t\t\t<li><b>Fixed</b> numerous small bugs.</li>\n\t\t            </ul>"
-  version: 1.2.0
-- date: 2006-09-04
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug sometimes making the collection\
-    \ updating procedure to fail.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug in the\
-    \ mp3 decoding unit.</li>\n\t\t            </ul>"
-  version: 1.1.3
-- date: 2006-08-26
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> the speed of musicGuru when\
-    \ adding songs.</li>\n\t\t\t\t\t\t<li><b>Replaced</b> the 2 locations drawers\
-    \ by a single location panel under Mac OS X.</li>\n\t\t            </ul>"
-  version: 1.1.2
-- date: 2006-08-15
-  description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> unicode support.</li>\n\t\t\t\
-    \t\t\t<li><b>Improved</b> MP3, WMA and AAC metadata decoding.</li>\n\t\t     \
-    \           <li>musicGuru is now a Universal application under Mac OS X.</li>\n\
-    \t\t            </ul>"
-  version: 1.1.1
-- date: 2006-03-28
-  description: "<ul>\n\t\t                <li><b>Improved</b> speed. Especially on\
-    \ Mac OS X.</li>\n\t\t\t\t\t\t<li><b>Improved</b> the Add Location interface.\
-    \ It went from a wizard to a simple dialog.</li>\n\t\t\t\t\t\t<li><b>Improved</b>\
-    \ progression feedback for Mass Rename and Split features.</li>\n\t\t\t\t\t\t\
-    <li><b>Improved</b> Design Board interface for the Windows version.</li>\n\t\t\
-    \t\t\t\t<li><b>Fixed</b> bug in the Split dialog.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
-    \ bug in the CD recording process.</li>\t\n\t\t            </ul>"
-  version: 1.1.0
-- date: 2006-03-11
-  description: "<ul>\n\t\t                <li><b>Fixed</b> occasional problems when\
-    \ reading unicode in wma and mp3 files.</li>\n\t\t                <li><b>Fixed</b>\
-    \ unicode issues for Win98 users.</li>\n\t\t                <li><b>Fixed</b> a\
-    \ DLL dependency bug.</li>\n\t\t            </ul>"
-  version: 1.0.8
-- date: 2006-03-04
-  description: "<ul>\n\t\t                <li><b>Fixed</b> problems when reading some\
-    \ kind of wma files.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> occasional errors when\
-    \ saving music collection.</li>\n\t\t            </ul>"
-  version: 1.0.4
-- date: 2006-01-28
-  description: "<ul>\n\t\t                <li><b>Added</b> a \"custom capacity\" option\
-    \ in the splitting panel.</li>\n\t\t                <li><b>Fixed</b> issues with\
-    \ disc recording.</li>\n\t\t                <li><b>Fixed</b> occasional innacuracies\
-    \ in the Info panel.</li>\n\t\t            </ul>"
-  version: 1.0.3
-- date: 2006-01-24
-  description: "<ul>\n\t\t            \t<li><b>Fixed</b> issues with the registration\
-    \ system.</li>\n\t\t            \t<li><b>Fixed</b> issues packaging issues in\
-    \ the windows MSI.</li>\n\t\t            </ul>"
-  version: 1.0.2
-- date: 2006-01-21
-  description: "<ul>\n\t\t                <li><b>Improved</b> memory usage of the\
-    \ Design Board.</li>\n\t\t                <li><b>Improved</b> the Design Board\
-    \ by making it case insensitive.</li>\n\t\t            \t<li><b>Fixed</b> issues\
-    \ with disc recording.</li>\n\t\t            \t<li><b>Fixed</b> issues with very\
-    \ long filenames in Windows.</li>\n\t\t            \t<li><b>Fixed</b> issues with\
-    \ the Splitting Options dialog.</li>\n\t\t            </ul>"
-  version: 1.0.1
-- date: 2006-01-17
-  description: "<ul>\n\t\t            \t<li>Initial release.</li>\n\t\t          \
-    \  </ul>"
-  version: 1.0.0

File help/conf.yaml

 base:
     pages: en/pages.yaml
     skeleton: skeleton
-    changelog: changelog.yaml
+    changelog: changelog
     tixurl: "https://hardcoded.lighthouseapp.com/projects/31701/tickets/{0}"
     firstpage_meta: "<meta name=\"AppleTitle\" content=\"musicGuru Help\"></meta>"
+# Created By: Virgil Dupras
+# Created On: 2010-06-28
+# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
+# 
+# This software is licensed under the "BSD" License as described in the "LICENSE" file, 
+# which should be included with this package. The terms are also available at 
+# http://www.hardcoded.net/licenses/bsd_license
+
+import re
+import yaml
+import markdown
+
+from hscommon import io
+from hscommon.path import Path
+from hscommon.build import read_changelog_file
+
+MAIN_CONTENTS = """
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	{meta}
+    <title>{title}</title>
+    <link rel="SHORTCUT ICON" href="/favicon.ico" />
+    <link rel="stylesheet" href="{relpath}hardcoded.css" type="text/css" />
+  </head>
+  <body>
+    <div class="mainlogo">
+      <a href="http://www.hardcoded.net"><img src="{relpath}images/hs_title.png" alt="HS Logo" /></a>
+    </div>
+    <div class="maincontainer">
+      <table>
+      <tr valign="top">
+		<td><h1>{title}</h1>{contents}</td>
+        <td class="menuframe">
+          <div class="menu">
+			  {menu}
+          </div>
+        </td>
+      </tr>
+      </table>
+    </div>
+  </body>
+</html>
+"""
+
+MENU_CONTENTS = """
+<a class="{menuclass}" href="{relpath}{link}">{name}</a>
+<span class="titleline"> </span>
+<span class="titledescrip">{desc}</span>
+"""
+
+CHANGELOG = """
+<table class="hardcoded">
+<tr class="header">
+<td>Version</td>
+<td style="width:56pt;">Date</td>
+<td>Description</td>
+</tr>
+{contents}
+</table>
+"""
+
+CHANGELOG_ITEM = """
+<tr>
+<td>{version}</td>
+<td>{date}</td>
+<td>{description}</td>
+</tr>
+"""
+
+def tixgen(tixurl):
+    """This is a filter *generator*. tixurl is a url pattern for the tix with a {0} placeholder
+    for the tix #
+    """
+    urlpattern = tixurl.format('\\1') # will be replaced buy the content of the first group in re
+    R = re.compile(r'#(\d+)')
+    repl = '[#\\1]({0})'.format(urlpattern)
+    return lambda text: R.sub(repl, text)
+
+class Page:
+    def __init__(self, pagedata, pagespath, fallbackpath):
+        self.name = pagedata['name']
+        self.basename = Path(self.name)[-1]
+        self.basepath = Path(self.name)[:-1]
+        self.path = pagespath + self.basepath + '{}.md'.format(self.basename)
+        if not io.exists(self.path):
+            self.path = fallbackpath + self.basepath + '{}.md'.format(self.basename)
+        self.title = pagedata['title']
+        self.relpath = '../' * len(self.basepath)
+        self.meta = ''
+    
+    def render(self, destpath, menu, env):
+        dest = destpath + self.basepath + '{0}.htm'.format(self.basename)
+        if not io.exists(dest[:-1]):
+            io.makedirs(dest[:-1])
+        mdcontents = io.open(self.path, 'rt', encoding='utf-8').read()
+        mdcontents = mdcontents.format(**env)
+        main_contents = markdown.markdown(mdcontents)
+        rendered = MAIN_CONTENTS.format(meta=self.meta, title=self.title, relpath=self.relpath,
+            menu=menu, contents=main_contents)
+        fp = io.open(dest, 'wt', encoding='utf-8')
+        fp.write(rendered)
+        fp.close()
+    
+
+class MainPage(Page):
+    def __init__(self, pagedata, pagespath, fallbackpath):
+        Page.__init__(self, pagedata, pagespath, fallbackpath)
+        self.menutitle = pagedata['menutitle']
+        self.menudesc = pagedata.get('menudesc', self.title)
+        self.subpages = [Page(data, pagespath, fallbackpath) for data in pagedata.get('subpages', [])]
+    
+    def build_menu(self, pages):
+        menu_items = []
+        for page in pages:
+            menuclass = 'menuitem_selected' if page is self else 'menuitem'
+            link = '{0}.htm'.format(page.name)
+            contents = MENU_CONTENTS.format(menuclass=menuclass, relpath=self.relpath, link=link,
+            name=page.menutitle, desc=page.menudesc)
+            menu_items.append(contents)
+        return ''.join(menu_items)
+    
+    def render(self, destpath, pages, env):
+        menu = self.build_menu(pages)
+        for page in self.subpages:
+            page.render(destpath, menu, env)
+        Page.render(self, destpath, menu, env)
+    
+
+def render_changelog(changelog, tixurl):
+    items = []
+    tix = tixgen(tixurl)
+    for item in changelog:
+        date = item['date']
+        version = item['version']
+        description = markdown.markdown(tix(item['description']))
+        rendered = CHANGELOG_ITEM.format(date=date, version=version, description=description)
+        items.append(rendered)
+    rendered_items = ''.join(items)
+    return CHANGELOG.format(contents=rendered_items)
+
+def gen(basepath, destpath, profile=None):
+    basepath = Path(basepath)
+    destpath = Path(destpath)
+    configpath = basepath + 'conf.yaml'
+    confall = yaml.load(io.open(configpath, 'rt', encoding='utf-8'))
+    conf = confall['base']
+    if profile and profile in confall:
+        conf.update(confall[profile])
+    tixurl = conf['tixurl']
+    changelogdata = read_changelog_file(str(basepath + conf['changelog']))
+    changelog = render_changelog(changelogdata, tixurl)
+    if 'env' in conf:
+        envpath = basepath + conf['env']
+        env = yaml.load(io.open(envpath, 'rt', encoding='utf-8'))
+    else:
+        env = {}
+    env['changelog'] = changelog
+    pagespath = basepath + conf['pages']
+    if 'basepages' in conf:
+        fallbackpath = basepath + conf['basepages']
+    else:
+        fallbackpath = None
+    pagedatas = yaml.load(io.open(pagespath, 'rt', encoding='utf-8'))
+    pages = [MainPage(pagedata, pagespath=pagespath[:-1], fallbackpath=fallbackpath) for pagedata in pagedatas]
+    skelpath = basepath + Path(conf['skeleton'])
+    if not io.exists(destpath):
+        print("Copying skeleton")
+        io.copytree(skelpath, destpath)
+    pages[0].meta = conf.get('firstpage_meta', '')
+    for i, page in enumerate(pages):
+        print("Rendering {0}".format(page.name))
+        page.render(destpath, pages, env)
 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-import sys
 import logging
 
-from hsutil.path import Path
-from hsutil.str import get_file_ext
+from hscommon.path import Path
+from hscommon.util import get_file_ext
 
 from . import tree
 from .stats import Stats

File hsfs/auto.py

 from unicodedata import normalize
 import logging
 
-from hsutil.misc import flatten
+from hscommon.util import flatten
 from . import _fs as fs
 
 def hold_update(method):

File hsfs/phys/_phys.py

 import time
 import logging
 
-from hsutil.misc import nonone
-from hsutil import io
+from hscommon.util import nonone
+from hscommon import io
 from .. import _fs as fs, auto
 
 MTIME_COOLDOWN = 1 # 1 second minimum cooldown between GetMTime() calls

File hsfs/phys/music.py

 from . import _phys as phys
 from .. import music
 from hsaudiotag import mpeg, wma, mp4, ogg, flac, aiff
-from hsutil.str import get_file_ext
+from hscommon.util import get_file_ext
 
 TAG_FIELDS = ['audiosize', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
     'album', 'genre', 'year', 'track', 'comment']
     #---Override
     def _read_info(self, field):
         if field == 'md5partial':
-            fileinfo = mpeg.Mpeg(self.path)
+            fileinfo = mpeg.Mpeg(str(self.path))
             self._md5partial_offset = fileinfo.audio_offset
             self._md5partial_size = fileinfo.audio_size
         super(Mp3File, self)._read_info(field)
         if field in TAG_FIELDS:
-            fileinfo = mpeg.Mpeg(self.path)
+            fileinfo = mpeg.Mpeg(str(self.path))
             self.audiosize = fileinfo.audio_size
             self.bitrate = fileinfo.bitrate
             self.duration = fileinfo.duration
     #---Override
     def _read_info(self, field):
         if field == 'md5partial':
-            dec = wma.WMADecoder(self.path)
+            dec = wma.WMADecoder(str(self.path))
             self._md5partial_offset = dec.audio_offset
             self._md5partial_size = dec.audio_size
         super(WmaFile, self)._read_info(field)
         if field in TAG_FIELDS:
-            dec = wma.WMADecoder(self.path)
+            dec = wma.WMADecoder(str(self.path))
             self.audiosize = dec.audio_size
             self.bitrate = dec.bitrate
             self.duration = dec.duration
     #---Override
     def _read_info(self, field):
         if field == 'md5partial':
-            dec = mp4.File(self.path)
+            dec = mp4.File(str(self.path))
             self._md5partial_offset = dec.audio_offset
             self._md5partial_size = dec.audio_size
             dec.close()
         super(Mp4File, self)._read_info(field)
         if field in TAG_FIELDS:
-            dec = mp4.File(self.path)
+            dec = mp4.File(str(self.path))
             self.audiosize = dec.audio_size
             self.bitrate = dec.bitrate
             self.duration = dec.duration
     #---Override
     def _read_info(self, field):
         if field == 'md5partial':
-            dec = ogg.Vorbis(self.path)
+            dec = ogg.Vorbis(str(self.path))
             self._md5partial_offset = dec.audio_offset
             self._md5partial_size = dec.audio_size
         super(OggFile, self)._read_info(field)
         if field in TAG_FIELDS:
-            dec = ogg.Vorbis(self.path)
+            dec = ogg.Vorbis(str(self.path))
             self.audiosize = dec.audio_size
             self.bitrate = dec.bitrate
             self.duration = dec.duration
     #---Override
     def _read_info(self, field):
         if field == 'md5partial':
-            dec = flac.FLAC(self.path)
+            dec = flac.FLAC(str(self.path))
             self._md5partial_offset = dec.audio_offset
             self._md5partial_size = dec.audio_size
         super(FlacFile, self)._read_info(field)
         if field in TAG_FIELDS:
-            dec = flac.FLAC(self.path)
+            dec = flac.FLAC(str(self.path))
             self.audiosize = dec.audio_size
             self.bitrate = dec.bitrate
             self.duration = dec.duration
     #---Override
     def _read_info(self, field):
         if field == 'md5partial':
-            dec = aiff.File(self.path)
+            dec = aiff.File(str(self.path))
             self._md5partial_offset = dec.audio_offset
             self._md5partial_size = dec.audio_size
         super(AiffFile, self)._read_info(field)
         if field in TAG_FIELDS:
-            dec = aiff.File(self.path)
+            dec = aiff.File(str(self.path))
             self.audiosize = dec.audio_size
             self.bitrate = dec.bitrate
             self.duration = dec.duration

File hsfs/stats.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from hsutil.misc import nonone
+from hscommon.util import nonone
 
 def do_add(x, y):
     if isinstance(x, (int, list, tuple)):
         result = result.copy()
     return result
 
-class Stats(object):
+class Stats:
     """Stats is intended to be a mixin class, and it should be mixed with a
     container class. To use it, call get_stat(arg). This will go through self,
     and look for <arg> attributes among items, and will return stats about it.

File hsfs/tests/auto_test.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from hsutil.testcase import TestCase
+from hscommon.testcase import TestCase
 
 from .. import _fs as fs, auto
 

File hsfs/tests/fs_test.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-import time
 import os
-from random import randrange
 import sys
 from io import StringIO
 import weakref
 import gc
 
-from hsutil.testutil import eq_
-
-from hsutil.path import Path
-from hsutil.testcase import TestCase
+from hscommon.testutil import eq_
+from hscommon.path import Path
+from hscommon.testcase import TestCase
 
 from .._fs import *
 

File hsfs/tests/music_test.py

 import os
 
 from py.test import importorskip
-from hsutil.testutil import eq_
+from hscommon.testutil import eq_
 
 # This test is permanently skipped since there's no easy way to get to hsaudio's testdata now that
 # the dependency is through site-package (in which testdata isn't installed) instead of through
 from hsaudiotag.testcase import TestCase
 from ..phys import music
 
-from hsutil.str import get_file_ext
+from hscommon.util import get_file_ext
 
 from .. import phys, _fs as fs
 from ..music import _File

File hsfs/tests/phys_test.py

 import shutil
 import sys
 
-from hsutil import io
-from hsutil.decorators import log_calls
-from hsutil.path import Path
-from hsutil.testcase import TestCase
+from hscommon import io
+from hscommon.testutil import log_calls, TestData
+from hscommon.path import Path
+from hscommon.testcase import TestCase
 
 from .. import phys
 from .. import _fs as fs
 
+testdata = TestData(op.join(op.dirname(__file__), 'testdata'))
+
 def create_fake_fs(rootpath):
     rootpath = op.join(rootpath, 'fs')
     os.mkdir(rootpath)
         testdata dir. Let's just assume that if it's not zero, it has been
         correctly fetched.
         """
-        root = phys.Directory(None, self.filepath('utils'))
+        root = phys.Directory(None, testdata.filepath('utils'))
         self.assert_(root._get_mtime() > 1)
         self.assert_(root.files[0].mtime > 1)
     
             self.fail()
         except fs.InvalidPath:
             pass
-        root = phys.Directory(None, self.filepath('utils'))
+        root = phys.Directory(None, testdata.filepath('utils'))
         d = phys.Directory(root, 'does_not_exist')
         self.assertEqual([],d.dirs)
         self.assertEqual([],d.files)
     
     def test_that_move_keeps_the_same_instance(self):
         tmpdir = phys.Directory(None, self.tmpdir())
-        refdir = phys.Directory(None, self.filepath('utils'))
+        refdir = phys.Directory(None, testdata.filepath('utils'))
         f = refdir.files[0]
         f.copy(tmpdir)
         f = tmpdir.files[0]
         def fake_copy_move(src,dest):
             raise EnvironmentError
             
-        refdir = phys.Directory(None, self.filepath('utils'))
+        refdir = phys.Directory(None, testdata.filepath('utils'))
         tmpdir = phys.Directory(None, self.tmpdir())
         self.mock(shutil, 'move', fake_copy_move)
         f = refdir.files[0]
         def fake_copy_move(src,dest):
             raise EnvironmentError
         
-        refdir = phys.Directory(None, self.filepath('utils'))
+        refdir = phys.Directory(None, testdata.filepath('utils'))
         tmpdir = phys.Directory(None, self.tmpdir())
         self.mock(shutil, 'move', fake_copy_move)
         f = refdir.files[0]

File hsfs/tests/stats_test.py

 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from hsutil.testutil import eq_
-
-from hscommon import job
+from hscommon.testutil import eq_
 
 from ..stats import *
 
-class BaseObject(object):
+class BaseObject:
     intval = 1
     strval = 'foo'
     lstval = [1,2,3]

File hsfs/tests/testdata/utils/test.txt

+test_data

File hsfs/tests/tree_test.py

 import weakref
 import gc
 
-from hsutil.testutil import eq_
-
-from hsutil.decorators import log_calls
+from hscommon.testutil import eq_
+from hscommon.testutil import log_calls
 
 from ..tree import *
 
     srcpath = op.join(destpath, 'src')
     os.makedirs(destpath)
     shutil.copytree('qt', srcpath)
-    copy_packages(['hsutil', 'hsaudiotag', 'hsfs', 'core', 'qtlib', 'hscommon'], srcpath)
+    copy_packages(['hsaudiotag', 'hsfs', 'core', 'qtlib', 'hscommon', 'jobprogress'], srcpath)
     shutil.copytree('debian', op.join(destpath, 'debian'))
     build_debian_changelog(op.join('help', 'changelog.yaml'), op.join(destpath, 'debian', 'changelog'), 'musicguru', from_version='1.3.6')
     shutil.copytree(op.join('help', 'musicguru_help'), op.join(srcpath, 'help'))

File qt/add_location_dialog.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-13
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-
-
 from PyQt4.QtCore import SIGNAL, QTimer
 from PyQt4.QtGui import QDialog, QGroupBox, QFileDialog
 
-from hsutil.path import Path
+from hscommon.path import Path
 
 import platform
 from ui.add_location_dialog_ui import Ui_AddLocationDialog
-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-11
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 from PyQt4.QtCore import SIGNAL, QUrl
 from PyQt4.QtGui import QDesktopServices, QMessageBox, QApplication, QFileDialog, QDialog
 
-from hscommon import job
+from jobprogress import job
 from qtlib.app import Application as ApplicationBase
 from qtlib.about_box import AboutBox
-from qtlib.progress import Progress
+from jobprogress.qt import Progress
 from qtlib.reg import Registration
 
 from core.app import MusicGuru as MusicGuruBase

File qt/fs_model.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-19
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-
-
 from PyQt4.QtCore import Qt, SIGNAL, QMimeData, QByteArray
 from PyQt4.QtGui import QPixmap
 
-from hsutil.conflict import is_conflicted
-from hsutil.misc import dedupe
-from hsutil.path import Path
-from hsutil.str import format_size, format_time, FT_MINUTES
+from hscommon.conflict import is_conflicted
+from hscommon.util import dedupe, format_size, format_time
+from hscommon.path import Path
 from qtlib.tree_model import TreeNode, TreeModel
 
 from core.fs_utils import smart_move
             song.original.parent_volume.name,
             0,
             format_size(song.size, 2, 2, False),
-            format_time(song.duration, FT_MINUTES),
+            format_time(song.duration, with_hours=False),
         ]
     
     def _getImageName(self):

File qt/locations_model.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-11
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-from PyQt4.QtCore import SIGNAL, Qt, QAbstractTableModel, QModelIndex
+from PyQt4.QtCore import SIGNAL, Qt, QAbstractTableModel
 from PyQt4.QtGui import QBrush
 
-from hsutil.str import format_size
+from hscommon.util import format_size
 
 class LocationsModel(QAbstractTableModel):
     HEADER = ['Name', 'Files', 'GB']

File qt/locations_panel.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-11
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 # which should be included with this package. The terms are also available at 
 # http://www.hardcoded.net/licenses/bsd_license
 
-
-
 from PyQt4.QtCore import SIGNAL, Qt
 from PyQt4.QtGui import QWidget, QHeaderView, QFileDialog
 
-from hsutil.path import Path
+from hscommon.path import Path
 
 import mg_rc
 from ui.locations_panel_ui import Ui_LocationsPanel

File qt/main_window.py

-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-11
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 from PyQt4.QtGui import (QMainWindow, QHeaderView, QMenu, QIcon, QPixmap, QToolButton, QDialog,
     QDesktopServices)
 
-from hsutil.conflict import is_conflicted
+from hscommon.conflict import is_conflicted
 
 import mg_rc
 from board_model import BoardModel
 <!DOCTYPE RCC><RCC version="1.0">
 <qresource>
+    <file alias="en.qm">../qtlib/lang/en.qm</file>
     <file alias="mg_logo">../images/mg_logo.ico</file>
     <file alias="mg_logo_big">../images/mg_logo_big.png</file>
     <file alias="locations">../images/locations_32.png</file>
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-09-11
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
 
 import mg_rc
 
+from hscommon.trans import install_qt_trans
 from app import MusicGuru
 
 if sys.platform == 'win32':
     QCoreApplication.setOrganizationName('Hardcoded Software')
     QCoreApplication.setApplicationName('musicGuru')
     QCoreApplication.setApplicationVersion(MusicGuru.VERSION)
+    install_qt_trans('en')
     mgapp = MusicGuru()
     exec_result = app.exec_()
     del mgapp
-# -*- coding: utf-8 -*-
 # Created By: Virgil Dupras
 # Created On: 2009-12-31
 # Copyright 2010 Hardcoded Software (http://www.hardcoded.net)