From 5f9246f53255bf5ee33a7ffc69ca65cea4e05a33 Mon Sep 17 00:00:00 2001
From: Neil <neil@dip.sun.ac.za>
Date: Sat, 3 Sep 2016 22:29:45 +0200
Subject: [PATCH 1/1] Create Repo. Add Skellington 2.3 created project outline

---
 BUGS.txt                   |   1 +
 CHANGES.txt                |   7 +
 LICENSE.txt                |   1 +
 Makefile                   |  70 ++++++++++
 README.txt                 |  51 +++++++
 TODO.txt                   |   5 +
 android.txt                |   4 +
 configure                  |   4 +
 data/README.txt            |   2 +
 pyweek_upload.py           | 198 ++++++++++++++++++++++++++
 run_game.py                |   3 +
 run_game.pyw               |   3 +
 scripts/tabakrolletjie     |   6 +
 setup.py                   | 277 +++++++++++++++++++++++++++++++++++++
 tabakrolletjie/__init__.py |   3 +
 tabakrolletjie/__main__.py |   4 +
 tabakrolletjie/data.py     |  27 ++++
 17 files changed, 666 insertions(+)
 create mode 100644 BUGS.txt
 create mode 100644 CHANGES.txt
 create mode 100644 LICENSE.txt
 create mode 100644 Makefile
 create mode 100644 README.txt
 create mode 100644 TODO.txt
 create mode 100644 android.txt
 create mode 100755 configure
 create mode 100644 data/README.txt
 create mode 100644 pyweek_upload.py
 create mode 100644 run_game.py
 create mode 100644 run_game.pyw
 create mode 100755 scripts/tabakrolletjie
 create mode 100755 setup.py
 create mode 100644 tabakrolletjie/__init__.py
 create mode 100644 tabakrolletjie/__main__.py
 create mode 100644 tabakrolletjie/data.py

diff --git a/BUGS.txt b/BUGS.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/BUGS.txt
@@ -0,0 +1 @@
+
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..9da9763
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,7 @@
+
+Changes
+=======
+
+
+
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..63d5fc3
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1 @@
+Licence: short_licence
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a12b51d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,70 @@
+#fake Makefile for tabakrolletjie, to support the common
+# ./configure;make;make install
+
+PYTHON = python
+
+#build: Setup setup.py
+build: setup.py
+	$(PYTHON) setup.py build
+
+#install: Setup setup.py
+install: setup.py
+	$(PYTHON) setup.py install
+
+#Setup:
+#	$(PYTHON) configure.py
+
+test check tests:
+	$(PYTHON) run_tests.py
+
+testall:
+	python2.5 setup.py test
+	python2.6 setup.py test
+	python3.1 setup.py test
+	make checkdocs
+
+#docs:	install
+#	cd docs/utils
+#	$(PYTHON) makedocs.py
+
+clean:
+	rm -rf build dist MANIFEST .coverage
+	rm -f tabakrolletjie/*~
+	rm -rf bin develop-eggs eggs parts .installed.cfg tabakrolletjie.egg-info
+	find . -name *.pyc -exec rm {} \;
+	find . -name *.swp -exec rm {} \;
+	$(PYTHON) setup.py clean
+
+# push changes
+push:
+	#bzr push lp:tabakrolletjie
+	svn commit
+
+# commit changes
+commit:
+	#bzr commit
+	svn commit
+
+#upload to pypi
+upload:
+	make clean
+	#if you have your gpg key set up... sign your release.
+	#$(PYTHON) setup.py sdist upload --sign --identity="Your Name <youremail@example.com>" 
+	$(PYTHON) setup.py sdist upload
+
+sdist:
+	make clean
+	make testall
+	$(PYTHON) setup.py sdist
+
+checkdocs:
+	$(PYTHON) setup.py checkdocs -setuptools
+
+showdocs:
+	$(PYTHON) setup.py showdocs -setuptools
+
+coverage:
+	coverage run run_tests.py
+	coverage report -m
+
+
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..0fa18a5
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,51 @@
+Your Game Title
+===============
+
+Entry in PyWeek #22  <http://www.pyweek.org/22/>
+URL: http://ctpug.org.za/gitweb/tabakrolletjie
+Team: YOUR TEAM NAME (leave the "Team: bit")
+Members: YOUR TEAM MEMBERS (leave the "Members: bit")
+License: see LICENSE.txt
+
+
+Running the Game
+----------------
+
+On Windows or Mac OS X, locate the "run_game.pyw" file and double-click it.
+
+Othewise open a terminal / console and "cd" to the game directory and run:
+
+  python run_game.py
+
+
+How to Play the Game
+--------------------
+
+An entry for pyweek 22
+
+Move the cursor around the screen with the mouse.
+
+Press the left mouse button to fire the ducks.
+
+
+Development notes 
+-----------------
+
+Creating a source distribution with::
+
+   python setup.py sdist
+
+You may also generate Windows executables and OS X applications::
+
+   python setup.py py2exe
+   python setup.py py2app
+
+Upload files to PyWeek with::
+
+   python pyweek_upload.py
+
+Upload to the Python Package Index with::
+
+   python setup.py register
+   python setup.py sdist upload
+
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..1bad070
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,5 @@
+
+
+TODO
+====
+
diff --git a/android.txt b/android.txt
new file mode 100644
index 0000000..e4cdc5e
--- /dev/null
+++ b/android.txt
@@ -0,0 +1,4 @@
+title=tabakrolletjie
+author=yourgameauthor
+api=1
+orientation=landscape
diff --git a/configure b/configure
new file mode 100755
index 0000000..546f631
--- /dev/null
+++ b/configure
@@ -0,0 +1,4 @@
+#fake configure for pywebsite, to support the common
+# ./configure;make;make install
+
+#python config.py -auto
diff --git a/data/README.txt b/data/README.txt
new file mode 100644
index 0000000..18bcae8
--- /dev/null
+++ b/data/README.txt
@@ -0,0 +1,2 @@
+Place your game's data files in here (images, fonts, map files, sounds,
+etc).
diff --git a/pyweek_upload.py b/pyweek_upload.py
new file mode 100644
index 0000000..053a0cc
--- /dev/null
+++ b/pyweek_upload.py
@@ -0,0 +1,198 @@
+'''
+Upload script specifically engineered for the PyWeek challenge.
+
+Handles authentication and gives upload progress feedback.
+'''
+import sys, os, httplib, cStringIO, socket, time, getopt
+
+class Upload:
+    def __init__(self, filename):
+        self.filename = filename
+
+boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+sep_boundary = '\n--' + boundary
+end_boundary = sep_boundary + '--'
+
+def mimeEncode(data, sep_boundary=sep_boundary, end_boundary=end_boundary):
+    '''Take the mapping of data and construct the body of a
+    multipart/form-data message with it using the indicated boundaries.
+    '''
+    ret = cStringIO.StringIO()
+    for key, value in data.items():
+        # handle multiple entries for the same name
+        if type(value) != type([]): value = [value]
+        for value in value:
+            ret.write(sep_boundary)
+            if isinstance(value, Upload):
+                ret.write('\nContent-Disposition: form-data; name="%s"'%key)
+                filename = os.path.basename(value.filename)
+                ret.write('; filename="%s"\n\n'%filename)
+                value = open(os.path.join(value.filename), "rb").read()
+            else:
+                ret.write('\nContent-Disposition: form-data; name="%s"'%key)
+                ret.write("\n\n")
+                value = str(value)
+            ret.write(str(value))
+            if value and value[-1] == '\r':
+                ret.write('\n')  # write an extra newline
+    ret.write(end_boundary)
+    return ret.getvalue()
+
+class Progress:
+    def __init__(self, info, data):
+        self.info = info
+        self.tosend = len(data)
+        self.total = self.tosend/1024
+        self.data = cStringIO.StringIO(data)
+        self.start = self.now = time.time()
+        self.sent = 0
+        self.num = 0
+        self.stepsize = self.total / 100 or 1
+        self.steptimes = []
+        self.display()
+
+    def __iter__(self): return self
+
+    def next(self):
+        self.num += 1
+        if self.sent >= self.tosend:
+            print self.info, 'done', ' '*(75-len(self.info)-6)
+            sys.stdout.flush()
+            raise StopIteration
+
+        chunk = self.data.read(1024)
+        self.sent += len(chunk)
+        #print (self.num, self.stepsize, self.total, self.sent, self.tosend)
+
+        if self.num % self.stepsize:
+            return chunk
+        self.display()
+        return chunk
+
+    def display(self):
+        # figure how long we've spent - guess how long to go
+        now = time.time()
+        steptime = now - self.now
+        self.steptimes.insert(0, steptime)
+        if len(self.steptimes) > 5:
+            self.steptimes.pop()
+        steptime = sum(self.steptimes) / len(self.steptimes)
+        self.now = now
+        eta = steptime * ((self.total - self.num)/self.stepsize)
+
+        # tell it like it is (or might be)
+        if now - self.start > 3:
+            M = eta / 60
+            H = M / 60
+            M = M % 60
+            S = eta % 60
+            if self.total:
+                s = '%s %2d%% (ETA %02d:%02d:%02d)'%(self.info,
+                    self.num * 100. / self.total, H, M, S)
+            else:
+                s = '%s 0%% (ETA %02d:%02d:%02d)'%(self.info, H, M, S)
+        elif self.total:
+            s = '%s %2d%%'%(self.info, self.num * 100. / self.total)
+        else:
+            s = '%s %d done'%(self.info, self.num)
+        sys.stdout.write(s + ' '*(75-len(s)) + '\r')
+        sys.stdout.flush()
+
+class progressHTTPConnection(httplib.HTTPConnection):
+    def progress_send(self, str):
+        """Send `str' to the server."""
+        if self.sock is None:
+            self.connect()
+
+        p = Progress('Uploading', str)
+        for chunk in p:
+            sent = 0
+            while sent != len(chunk):
+                try:
+                    sent += self.sock.send(chunk)
+                except socket.error, v:
+                    if v[0] == 32:      # Broken pipe
+                        self.close()
+                    raise
+                p.display()
+
+class progressHTTP(httplib.HTTP):
+    _connection_class = progressHTTPConnection
+    def _setup(self, conn):
+        httplib.HTTP._setup(self, conn)
+        self.progress_send = self._conn.progress_send
+
+def http_request(data, server, port, url):
+    h = progressHTTP(server, port)
+
+    data = mimeEncode(data)
+    h.putrequest('POST', url)
+    h.putheader('Content-type', 'multipart/form-data; boundary=%s'%boundary)
+    h.putheader('Content-length', str(len(data)))
+    h.putheader('Host', server)
+    h.endheaders()
+
+    h.progress_send(data)
+
+    errcode, errmsg, headers = h.getreply()
+
+    f = h.getfile()
+    response = f.read().strip()
+    f.close()
+
+    print '%s %s'%(errcode, errmsg)
+    if response: print response
+
+def usage():
+    print '''This program is to be used to upload files to the PyWeek system.
+You may use it to upload screenshots or code submissions.
+
+REQUIRED ARGUMENTS:
+ -u   username
+ -p   password
+ -d   description of file
+ -c   file to upload
+ -e   entry short name
+
+OPTIONAL ARGUMENTS:
+ -s   file is a screenshot
+ -f   file is FINAL submission
+ -h   override default host name (www.pyweek.org)
+ -P   override default host port (80)
+
+In order to qualify for judging at the end of the challenge, you MUST
+upload your source and check the "Final Submission" checkbox.
+'''
+
+
+if __name__ == '__main__':
+    try:
+        optlist, args = getopt.getopt(sys.argv[1:], 'e:u:p:sfd:h:P:c:')
+    except getopt.GetoptError, message:
+        print message
+        usage()
+        sys.exit(1)
+    host = 'www.pyweek.org'
+    port = 80
+    data = dict(version=2)
+    optional = {}
+    url = None
+    for opt, arg in optlist:
+        if opt == '-u': data['user'] = arg
+        elif opt == '-p': data['password'] = arg
+        elif opt == '-s': optional['is_screenshot'] = 'yes'
+        elif opt == '-f': optional['is_final'] = 'yes'
+        elif opt == '-d': data['description'] = arg
+        elif opt == '-c': data['content_file'] = Upload(arg)
+        elif opt == '-e': url = '/e/%s/oup/'%arg
+        elif opt == '-h': host = arg
+        elif opt == '-P': port = int(arg)
+
+    if len(data) < 4 or url is None:
+        print 'Required argument missing'
+        usage()
+        sys.exit(1)
+
+    data.update(optional)
+    http_request(data, host, port, url)
+
diff --git a/run_game.py b/run_game.py
new file mode 100644
index 0000000..31e7bb0
--- /dev/null
+++ b/run_game.py
@@ -0,0 +1,3 @@
+import tabakrolletjie.__main__
+if __name__ == "__main__":
+    tabakrolletjie.__main__.main()
diff --git a/run_game.pyw b/run_game.pyw
new file mode 100644
index 0000000..1a6f51f
--- /dev/null
+++ b/run_game.pyw
@@ -0,0 +1,3 @@
+import yourgameshortname.main
+if __name__ == "__main__":
+    yourgameshortname.main.main()
diff --git a/scripts/tabakrolletjie b/scripts/tabakrolletjie
new file mode 100755
index 0000000..a6d8097
--- /dev/null
+++ b/scripts/tabakrolletjie
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+import tabakrolletjie.__main__
+if __name__ == "__main__":
+    tabakrolletjie.__main__.main()
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..e6b1a53
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,277 @@
+import os
+
+# usage: python setup.py command
+#
+# sdist - build a source dist
+# py2exe - build an exe
+# py2app - build an app
+# cx_freeze - build a linux binary (not implemented)
+#
+# the goods are placed in the dist dir for you to .zip up or whatever...
+
+
+APP_NAME = 'tabakrolletjie'
+DESCRIPTION = open('README.txt').read()
+CHANGES = open('CHANGES.txt').read()
+TODO = open('TODO.txt').read()
+
+
+
+METADATA = {
+    'name':APP_NAME,
+    'version':          '0.0.1',
+    'license':          'short_licence',
+    'description':      'An entry for pyweek 22',
+    'author':           'Adrianna Pinksa, Simon Cross and Neil Muller',
+    #'author_email':     '',
+    'url':              'http://ctpug.org.za/gitweb/tabakrolletjie',
+    'classifiers':      [
+            'Development Status :: 4 - Beta',
+            'Intended Audience :: End Users/Desktop',
+            'Intended Audience :: Information Technology',
+            'License :: OSI Approved :: BSD License',
+            'Operating System :: OS Independent',
+            'Programming Language :: Python :: 2',
+            'Programming Language :: Python :: 2.5',
+            'Programming Language :: Python :: 2.6',
+            'Programming Language :: Python :: 2.7',
+            'Programming Language :: Python :: 3',
+            'Programming Language :: Python :: 3.0',
+            'Programming Language :: Python :: 3.1',
+            'Programming Language :: Python :: 3.2',
+            'Topic :: Software Development :: Libraries :: pygame',
+            'Topic :: Games/Entertainment :: Real Time Strategy',
+    ],
+
+
+    'py2exe.target':'',
+    #'py2exe.icon':'icon.ico', #64x64
+    'py2exe.binary':APP_NAME, #leave off the .exe, it will be added
+    
+    'py2app.target':APP_NAME,
+    'py2app.icon':'icon.icns', #128x128
+    
+    #'cx_freeze.cmd':'~/src/cx_Freeze-3.0.3/FreezePython',
+    'cx_freeze.cmd':'cxfreeze',
+    'cx_freeze.target':'%s_linux' % APP_NAME,
+    'cx_freeze.binary':APP_NAME,
+    }
+    
+files_to_remove = ['tk84.dll',
+                    '_ssl.pyd',
+                    'tcl84.dll',
+                    os.path.join('numpy','core', '_dotblas.pyd'),
+                    os.path.join('numpy', 'linalg', 'lapack_lite.pyd'),
+]
+
+
+directories_to_remove = [os.path.join('numpy', 'distutils'),
+                         'distutils',
+                         'tcl',
+]
+
+
+cmdclass = {}
+PACKAGEDATA = {
+    'cmdclass':    cmdclass,
+
+    'package_dir': {'tabakrolletjie': 'tabakrolletjie',
+                   },
+    'packages': ['tabakrolletjie',
+                ],
+    'scripts': ['scripts/tabakrolletjie'],
+}
+
+PACKAGEDATA.update(METADATA)
+
+
+from distutils.core import setup, Extension
+try:
+    import py2exe
+except:
+    pass
+
+import sys
+import glob
+import os
+import shutil
+
+try:
+    cmd = sys.argv[1]
+except IndexError:
+    print 'Usage: setup.py install|py2exe|py2app|cx_freeze'
+    raise SystemExit
+
+# utility for adding subdirectories
+def add_files(dest,generator):
+    for dirpath, dirnames, filenames in generator:
+        for name in 'CVS', '.svn':
+            if name in dirnames:
+                dirnames.remove(name)
+
+        for name in filenames:
+            if '~' in name: continue
+            suffix = os.path.splitext(name)[1]
+            if suffix in ('.pyc', '.pyo'): continue
+            if name[0] == '.': continue
+            filename = os.path.join(dirpath, name)
+            dest.append(filename)
+
+# define what is our data
+_DATA_DIR = os.path.join('tabakrolletjie', 'data')
+data = []
+add_files(data,os.walk(_DATA_DIR))
+
+
+
+
+#data_dirs = [os.path.join(f2.replace(_DATA_DIR, 'data'), '*') for f2 in data]
+data_dirs = [os.path.join(f2.replace(_DATA_DIR, 'data')) for f2 in data]
+PACKAGEDATA['package_data'] = {'tabakrolletjie': data_dirs}
+
+
+
+
+
+data.extend(glob.glob('*.txt'))
+#data.append('MANIFEST.in')
+# define what is our source
+src = []
+add_files(src,os.walk('tabakrolletjie'))
+src.extend(glob.glob('*.py'))
+
+
+
+
+# build the sdist target
+if cmd not in "py2exe py2app cx_freeze".split():
+    f = open("MANIFEST.in","w")
+    for l in data: f.write("include "+l+"\n")
+    for l in src: f.write("include "+l+"\n")
+    f.close()
+    
+    setup(**PACKAGEDATA)
+
+# build the py2exe target
+if cmd in ('py2exe',):
+    dist_dir = os.path.join('dist',METADATA['py2exe.target'])
+    data_dir = dist_dir
+    
+    src = 'run_game.py'
+    dest = METADATA['py2exe.binary']+'.py'
+    shutil.copy(src,dest)
+    
+    setup(
+        options={'py2exe':{
+            'dist_dir':dist_dir,
+            'dll_excludes':['_dotblas.pyd','_numpy.pyd', 'numpy.linalg.lapack_lite.pyd', 'numpy.core._dotblas.pyd'] + files_to_remove,
+            'excludes':['matplotlib', 'tcl', 'OpenGL'],
+            'ignores':['matplotlib', 'tcl', 'OpenGL'],
+            'bundle_files':1,
+            }},
+#        windows=[{
+       console=[{
+            'script':dest,
+            #'icon_resources':[(1,METADATA['py2exe.icon'])],
+            }],
+        )
+
+# build the py2app target
+if cmd == 'py2app':
+    dist_dir = os.path.join('dist',METADATA['py2app.target']+'.app')
+    data_dir = os.path.join(dist_dir,'Contents','Resources')
+    from setuptools import setup
+
+    src = 'run_game.py'
+    dest = METADATA['py2app.target']+'.py'
+    shutil.copy(src,dest)
+
+    APP = [dest]
+    DATA_FILES = []
+    OPTIONS = {'argv_emulation': True, 
+               #'iconfile':METADATA['py2app.icon']
+              }
+
+    setup(
+        app=APP,
+        data_files=DATA_FILES,
+        options={'py2app': OPTIONS},
+        setup_requires=['py2app'],
+    )
+
+# make the cx_freeze target
+if cmd == 'cx_freeze':
+    app_dist_dir = METADATA['cx_freeze.target'] + "_" + METADATA['version']
+    dist_dir = os.path.join('dist', app_dist_dir)
+    data_dir = dist_dir
+
+    modules_exclude = "tcl,tk"
+    cmd_args = (METADATA['cx_freeze.cmd'], dist_dir, METADATA['cx_freeze.binary'], modules_exclude)
+    sys_cmd = '%s --install-dir=%s --target-name=%s --exclude-modules=%s run_game.py' % cmd_args
+    print sys_cmd
+    os.system(sys_cmd)
+
+    import shutil
+    if os.path.exists(os.path.join(data_dir, "tcl")): 
+        shutil.rmtree( os.path.join(data_dir, "tcl") )
+    if os.path.exists(os.path.join(data_dir, "tk")): 
+        shutil.rmtree( os.path.join(data_dir, "tk") )
+
+
+
+# recursively make a bunch of folders
+def make_dirs(dname_):
+    parts = list(os.path.split(dname_))
+    dname = None
+    while len(parts):
+        if dname == None:
+            dname = parts.pop(0)
+        else:
+            dname = os.path.join(dname,parts.pop(0))
+        if not os.path.isdir(dname):
+            os.mkdir(dname)
+
+# copy data into the binaries 
+if cmd in ('py2exe','cx_freeze','py2app'):
+    dest = data_dir
+    for fname in data:
+        dname = os.path.join(dest,os.path.dirname(fname))
+        make_dirs(dname)
+        if not os.path.isdir(fname):
+            #print (fname,dname)
+            shutil.copy(fname,dname)
+
+# make a tgz files.
+if cmd == 'cx_freeze':
+    sys_cmd = "cd dist; tar -vczf %s.tgz %s/" % (app_dist_dir,app_dist_dir)  
+    os.system(sys_cmd)
+
+
+# remove files from the zip.
+if 0 and cmd in ('py2exe'):
+    import shutil
+
+    #shutil.rmtree( os.path.join('dist') )
+    #shutil.rmtree( os.path.join('build') )
+
+
+    os.system("unzip dist/library.zip -d dist\library")
+
+    for fn in files_to_remove:
+        os.remove( os.path.join('dist', 'library', fn) )
+
+
+    for d in directories_to_remove:
+        if os.path.exists( os.path.join('dist', 'library', d) ):
+            shutil.rmtree( os.path.join('dist', 'library', d) )
+
+    os.remove( os.path.join('dist', 'library.zip') )
+
+
+    os.chdir("dist")
+    os.chdir("library")
+
+    os.system("zip -r -9 ..\library.zip .")
+
+    os.chdir("..")
+    os.chdir("..")
diff --git a/tabakrolletjie/__init__.py b/tabakrolletjie/__init__.py
new file mode 100644
index 0000000..6cd298b
--- /dev/null
+++ b/tabakrolletjie/__init__.py
@@ -0,0 +1,3 @@
+if __name__ == "__main__":
+    import main
+    main.main()
diff --git a/tabakrolletjie/__main__.py b/tabakrolletjie/__main__.py
new file mode 100644
index 0000000..dbb20cd
--- /dev/null
+++ b/tabakrolletjie/__main__.py
@@ -0,0 +1,4 @@
+
+def main():
+    """ your app starts here
+    """
diff --git a/tabakrolletjie/data.py b/tabakrolletjie/data.py
new file mode 100644
index 0000000..66b66bb
--- /dev/null
+++ b/tabakrolletjie/data.py
@@ -0,0 +1,27 @@
+'''Simple data loader module.
+
+Loads data files from the "data" directory shipped with a game.
+
+Enhancing this to handle caching etc. is left as an exercise for the reader.
+
+Note that pyglet users should probably just add the data directory to the
+pyglet.resource search path.
+'''
+
+import os
+
+data_py = os.path.abspath(os.path.dirname(__file__))
+data_dir = os.path.normpath(os.path.join(data_py, '..', 'data'))
+
+def filepath(filename):
+    '''Determine the path to a file in the data directory.
+    '''
+    return os.path.join(data_dir, filename)
+
+def load(filename, mode='rb'):
+    '''Open a file in the data directory.
+
+    "mode" is passed as the second arg to open().
+    '''
+    return open(os.path.join(data_dir, filename), mode)
+
-- 
2.34.1