| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
|
|---|
| 3 |
# This serves as a catch-all script for common utilities that may be used |
|---|
| 4 |
# in lots of places throughout Dabo. Typically, to use a function 'foo()' in |
|---|
| 5 |
# this file, add the following import statement to your script: |
|---|
| 6 |
# |
|---|
| 7 |
# import dabo.lib.utils as utils |
|---|
| 8 |
# |
|---|
| 9 |
# Then, in your code, simply call: |
|---|
| 10 |
# |
|---|
| 11 |
# utils.foo() |
|---|
| 12 |
|
|---|
| 13 |
import os |
|---|
| 14 |
import sys |
|---|
| 15 |
import dabo |
|---|
| 16 |
try: |
|---|
| 17 |
from win32com.shell import shell, shellcon |
|---|
| 18 |
except ImportError: |
|---|
| 19 |
shell, shellcon = None, None |
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
# can't compare NoneType to some types: sort None lower than anything else: |
|---|
| 23 |
def noneSortKey(vv): |
|---|
| 24 |
vv = vv[0] |
|---|
| 25 |
if vv is None: |
|---|
| 26 |
return (0, None) |
|---|
| 27 |
else: |
|---|
| 28 |
return (1, vv) |
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 |
def caseInsensitiveSortKey(vv): |
|---|
| 32 |
return (vv[0] or "").lower() |
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
def reverseText(tx): |
|---|
| 36 |
"""Takes a string and returns it reversed. Example: |
|---|
| 37 |
|
|---|
| 38 |
utils.reverseText("Wow, this is so cool!") |
|---|
| 39 |
=> returns "!looc os si siht ,woW" |
|---|
| 40 |
""" |
|---|
| 41 |
return tx[::-1] |
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 |
def getUserHomeDirectory(): |
|---|
| 45 |
"""Return the user's home directory in a platform-portable way. |
|---|
| 46 |
|
|---|
| 47 |
If the home directory cannot be determined, return None. |
|---|
| 48 |
""" |
|---|
| 49 |
hd = None |
|---|
| 50 |
|
|---|
| 51 |
# If we are on Windows and win32com is available, get the user home |
|---|
| 52 |
# directory using the Windows API: |
|---|
| 53 |
if shell and shellcon: |
|---|
| 54 |
return shell.SHGetFolderPath(0, shellcon.CSIDL_PROFILE, 0, 0) |
|---|
| 55 |
|
|---|
| 56 |
# os.path.expanduser should work on all posix systems (*nix, Mac, and some |
|---|
| 57 |
# Windows NT setups): |
|---|
| 58 |
hd = os.path.expanduser("~") |
|---|
| 59 |
|
|---|
| 60 |
# If for some reason the posix function above didn't work, most Linux setups |
|---|
| 61 |
# define the environmental variable $HOME, and perhaps this is done sometimes |
|---|
| 62 |
# on Mac and Win as well: |
|---|
| 63 |
if hd is None: |
|---|
| 64 |
hd = os.environ.get("HOME") |
|---|
| 65 |
|
|---|
| 66 |
# If we still haven't found a value, Windows tends to define a $USERPROFILE |
|---|
| 67 |
# directory, which usually expands to something like |
|---|
| 68 |
# c:\Documents and Settings\maryjane |
|---|
| 69 |
if hd is None: |
|---|
| 70 |
hd = os.environ.get("USERPROFILE") |
|---|
| 71 |
|
|---|
| 72 |
return hd |
|---|
| 73 |
|
|---|
| 74 |
|
|---|
| 75 |
def getUserAppDataDirectory(appName="Dabo"): |
|---|
| 76 |
"""Return the directory where Dabo can save user preference and setting information. |
|---|
| 77 |
|
|---|
| 78 |
On *nix, this will be something like /home/pmcnett/.dabo |
|---|
| 79 |
On Windows, it will be more like c:\Documents and Settings\pmcnett\Application Data\Dabo |
|---|
| 80 |
|
|---|
| 81 |
This function relies on platform conventions to determine this information. If it |
|---|
| 82 |
cannot be determined (because platform conventions were circumvented), the return |
|---|
| 83 |
value will be None. |
|---|
| 84 |
|
|---|
| 85 |
if appName is passed, the directory will be named accordingly. |
|---|
| 86 |
|
|---|
| 87 |
This function will try to create the directory if it doesn't already exist, but if the |
|---|
| 88 |
creation of the directory fails, the return value will revert to None. |
|---|
| 89 |
""" |
|---|
| 90 |
dd = None |
|---|
| 91 |
|
|---|
| 92 |
if sys.platform not in ("win32",): |
|---|
| 93 |
# On Unix, change appname to lower, don't allow spaces, and prepend a ".": |
|---|
| 94 |
appName = ".%s" % appName.lower().replace(" ", "_") |
|---|
| 95 |
|
|---|
| 96 |
# First, on Windows, try the Windows API function: |
|---|
| 97 |
if shell and shellcon: |
|---|
| 98 |
dd = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) |
|---|
| 99 |
|
|---|
| 100 |
if dd is None and sys.platform == "win32": |
|---|
| 101 |
# We are on Windows, but win32com wasn't installed. Look for the APPDATA |
|---|
| 102 |
# environmental variable: |
|---|
| 103 |
dd = os.environ.get("APPDATA") |
|---|
| 104 |
|
|---|
| 105 |
if dd is None: |
|---|
| 106 |
# We are either not on Windows, or we couldn't locate the directory for |
|---|
| 107 |
# whatever reason. Try going off the home directory: |
|---|
| 108 |
dd = getUserHomeDirectory() |
|---|
| 109 |
|
|---|
| 110 |
if dd is not None: |
|---|
| 111 |
dd = os.path.join(dd, appName) |
|---|
| 112 |
if not os.path.exists(dd): |
|---|
| 113 |
# try to create the dabo directory: |
|---|
| 114 |
try: |
|---|
| 115 |
os.makedirs(dd) |
|---|
| 116 |
except OSError: |
|---|
| 117 |
sys.stderr.write("Couldn't create the user setting directory (%s)." % dd) |
|---|
| 118 |
dd = None |
|---|
| 119 |
return dd |
|---|
| 120 |
|
|---|
| 121 |
|
|---|
| 122 |
def dictStringify(dct): |
|---|
| 123 |
"""The ability to pass a properties dict to an object relies on |
|---|
| 124 |
the practice of passing '**properties' to a function. Seems that |
|---|
| 125 |
Python requires that the keys in any dict being expanded like |
|---|
| 126 |
this be strings, not unicode. This method returns a dict with all |
|---|
| 127 |
unicode keys changed to strings. |
|---|
| 128 |
""" |
|---|
| 129 |
ret = {} |
|---|
| 130 |
for kk, vv in dct.items(): |
|---|
| 131 |
if isinstance(kk, unicode): |
|---|
| 132 |
try: |
|---|
| 133 |
ret[str(kk)] = vv |
|---|
| 134 |
except UnicodeEncodeError: |
|---|
| 135 |
kk = kk.encode(dabo.defaultEncoding) |
|---|
| 136 |
ret[kk] = vv |
|---|
| 137 |
else: |
|---|
| 138 |
ret[kk] = vv |
|---|
| 139 |
return ret |
|---|
| 140 |
|
|---|
| 141 |
|
|---|
| 142 |
def relativePathList(toLoc, fromLoc=None): |
|---|
| 143 |
"""Given two paths, returns a list that, when joined with |
|---|
| 144 |
os.path.sep, gives the relative path from 'fromLoc' to |
|---|
| 145 |
"toLoc'. If 'fromLoc' is not specified, the current directory |
|---|
| 146 |
is assumed. |
|---|
| 147 |
""" |
|---|
| 148 |
if fromLoc is None: |
|---|
| 149 |
fromLoc = os.getcwd() |
|---|
| 150 |
if toLoc.startswith(".."): |
|---|
| 151 |
if os.path.isdir(fromLoc): |
|---|
| 152 |
toLoc = os.path.join(fromLoc, toLoc) |
|---|
| 153 |
else: |
|---|
| 154 |
toLoc = os.path.join(os.path.split(fromLoc)[0], toLoc) |
|---|
| 155 |
toLoc = os.path.abspath(toLoc) |
|---|
| 156 |
if os.path.isfile(toLoc): |
|---|
| 157 |
toDir, toFile = os.path.split(toLoc) |
|---|
| 158 |
else: |
|---|
| 159 |
toDir = toLoc |
|---|
| 160 |
toFile = "" |
|---|
| 161 |
fromLoc = os.path.abspath(fromLoc) |
|---|
| 162 |
if os.path.isfile(fromLoc): |
|---|
| 163 |
fromLoc = os.path.split(fromLoc)[0] |
|---|
| 164 |
fromList = fromLoc.split(os.path.sep) |
|---|
| 165 |
toList = toDir.split(os.path.sep) |
|---|
| 166 |
# There can be empty strings from the split |
|---|
| 167 |
while len(fromList) > 0 and not fromList[0]: |
|---|
| 168 |
fromList.pop(0) |
|---|
| 169 |
while len(toList) > 0 and not toList[0]: |
|---|
| 170 |
toList.pop(0) |
|---|
| 171 |
lev = 0 |
|---|
| 172 |
while (len(fromList) > lev) and (len(toList) > lev) and \ |
|---|
| 173 |
(fromList[lev] == toList[lev]): |
|---|
| 174 |
lev += 1 |
|---|
| 175 |
|
|---|
| 176 |
# 'lev' now contains the first level where they differ |
|---|
| 177 |
fromDiff = fromList[lev:] |
|---|
| 178 |
toDiff = toList[lev:] |
|---|
| 179 |
ret = [".."] * len(fromDiff) + toDiff |
|---|
| 180 |
if toFile: |
|---|
| 181 |
ret += [toFile] |
|---|
| 182 |
return ret |
|---|
| 183 |
|
|---|
| 184 |
|
|---|
| 185 |
def relativePath(toLoc, fromLoc=None): |
|---|
| 186 |
"""Given two paths, returns a relative path from fromLoc to toLoc.""" |
|---|
| 187 |
return os.path.sep.join(relativePathList(toLoc, fromLoc)) |
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
def getPathAttributePrefix(): |
|---|
| 191 |
return "path://" |
|---|
| 192 |
|
|---|
| 193 |
|
|---|
| 194 |
def resolveAttributePathing(atts, pth=None): |
|---|
| 195 |
"""Dabo design files store their information in XML, which means |
|---|
| 196 |
when they are 'read' the values come back in a dictionary of |
|---|
| 197 |
attributes, which are then used to restore the designed object to its |
|---|
| 198 |
intended state. Path values will be stored in a relative path format, |
|---|
| 199 |
with the value preceeded by the string returned by |
|---|
| 200 |
getPathAttributePrefix(); i.e., 'path://'. |
|---|
| 201 |
|
|---|
| 202 |
This method finds all values that begin with the 'path://' label, |
|---|
| 203 |
strips off that label, converts the paths back to values that |
|---|
| 204 |
can be used by the object, and then updates the attribute dict with |
|---|
| 205 |
those new values. |
|---|
| 206 |
""" |
|---|
| 207 |
prfx = getPathAttributePrefix() |
|---|
| 208 |
pathsToConvert = [(kk, vv) for kk, vv in atts.items() |
|---|
| 209 |
if isinstance(vv, basestring) and vv.startswith(prfx)] |
|---|
| 210 |
for convKey, convVal in pathsToConvert: |
|---|
| 211 |
# Strip the path designator |
|---|
| 212 |
convVal = convVal.replace(prfx, "") |
|---|
| 213 |
# Convert to relative path |
|---|
| 214 |
relPath = relativePath(convVal, pth) |
|---|
| 215 |
# Update the atts |
|---|
| 216 |
atts[convKey] = relPath |
|---|
| 217 |
|
|---|
| 218 |
|
|---|
| 219 |
def resolvePath(val, pth=None, abspath=False): |
|---|
| 220 |
"""Takes a single string value in the format Dabo uses to store pathing |
|---|
| 221 |
in XML, and returns the original path relative to the specified path (or the |
|---|
| 222 |
current directory, if no pth is specified). If 'abspath' is True, returns an |
|---|
| 223 |
absolute path instead of the default relative path. |
|---|
| 224 |
""" |
|---|
| 225 |
prfx = getPathAttributePrefix() |
|---|
| 226 |
# Strip the path designator |
|---|
| 227 |
val = val.replace(prfx, "") |
|---|
| 228 |
# Convert to relative path |
|---|
| 229 |
ret = relativePath(val, pth) |
|---|
| 230 |
if abspath: |
|---|
| 231 |
ret = os.path.abspath(ret) |
|---|
| 232 |
return ret |
|---|
| 233 |
|
|---|
| 234 |
|
|---|
| 235 |
def cleanMenuCaption(cap, bad=None): |
|---|
| 236 |
"""Menu captions can contain several special characters that make them |
|---|
| 237 |
unsuitable for things such as preference settings. This method provides |
|---|
| 238 |
a simple way of getting the 'clean' version of these captions. By default it |
|---|
| 239 |
strips ampersands, spaces and periods; you can change that by passing |
|---|
| 240 |
the characters you want stripped in the 'bad' parameter. |
|---|
| 241 |
""" |
|---|
| 242 |
if bad is None: |
|---|
| 243 |
bad = "&. " |
|---|
| 244 |
ret = cap |
|---|
| 245 |
for ch in bad: |
|---|
| 246 |
ret = ret.replace(ch, "") |
|---|
| 247 |
return ret |
|---|