appy.bin: updated publish.py, that is now able to generate a DistUtils tar.gz for Appy; publish.py can now be called with option '-s' (silent): in this mode no question is asked to the user, default values are used; updated new.py that generates a better Plone4-ready simple Zope instance; appy: moved FileWrapper from appy.gen.utils to appy.shared.utils to avoid circular package dependencies; appy.gen: use of .pyt extensions for template Python classes in appy.gen.templates in order to avoid byte-compilation errors when distutils installs the package; appy.pod: when using function 'document' in 'from' statements, first arg can now be a appy.shared.utils.FileWrapper instance.
This commit is contained in:
parent
e78cf62694
commit
6ece750d9a
56
bin/new.py
56
bin/new.py
|
@ -109,8 +109,9 @@ class NewScript:
|
||||||
f.write(self.patchRex.sub('<!--Del. includePlugins-->',fileContent))
|
f.write(self.patchRex.sub('<!--Del. includePlugins-->',fileContent))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
filesToPatch2 = ('profiles/default/skins.xml')
|
missingIncludes = ('plone.app.upgrade', 'plonetheme.sunburst',
|
||||||
def patchPlone4(self):
|
'plonetheme.classic')
|
||||||
|
def patchPlone4(self, versions):
|
||||||
'''Patches Plone 4 that can't live without buildout as-is.'''
|
'''Patches Plone 4 that can't live without buildout as-is.'''
|
||||||
self.patchPlone3x() # We still need this for Plone 4 as well.
|
self.patchPlone3x() # We still need this for Plone 4 as well.
|
||||||
# bin/zopectl
|
# bin/zopectl
|
||||||
|
@ -124,42 +125,44 @@ class NewScript:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.close()
|
f.close()
|
||||||
j = os.path.join
|
j = os.path.join
|
||||||
themeFolder = '%s/plonetheme' % self.libFolder
|
# As eggs have been deleted, versions of components are lost. Reify
|
||||||
for theme in os.listdir(themeFolder):
|
# them from p_versions.
|
||||||
# Create a simlink to every theme in self.productsFolder
|
dVersions = ['"%s":"%s"' % (n, v) for n, v in versions.iteritems()]
|
||||||
tFolder = j(themeFolder, theme)
|
sVersions = 'appyVersions = {' + ','.join(dVersions) + '}'
|
||||||
if not os.path.isdir(tFolder): continue
|
|
||||||
os.system('ln -s %s %s/%s' % (tFolder, self.productsFolder, theme))
|
|
||||||
# Patch skins.xml
|
|
||||||
fileName = '%s/profiles/default/skins.xml' % tFolder
|
|
||||||
f = file(fileName)
|
|
||||||
content = f.read()
|
|
||||||
f.close()
|
|
||||||
f = file(fileName, 'w')
|
|
||||||
f.write(content.replace('plonetheme.%s:' % theme, '%s/' % theme))
|
|
||||||
f.close()
|
|
||||||
# As eggs have been deleted, Plone can't tell which version of Zope and
|
|
||||||
# Plone are there. So we patch the code that tries to get Plone and Zope
|
|
||||||
# versions.
|
|
||||||
codeFile = "%s/pkg_resources.py" % self.libFolder
|
codeFile = "%s/pkg_resources.py" % self.libFolder
|
||||||
f = file(codeFile)
|
f = file(codeFile)
|
||||||
content = f.read()
|
content = sVersions + '\n' + f.read()
|
||||||
f.close()
|
f.close()
|
||||||
content = content.replace("raise DistributionNotFound(req)",
|
content = content.replace("raise DistributionNotFound(req)",
|
||||||
"dist = Distribution(project_name=req.project_name, " \
|
"dist = Distribution(project_name=req.project_name, " \
|
||||||
"version='1.1.1', platform='linux2', location='%s')" % \
|
"version=appyVersions[req.project_name], platform='linux2', " \
|
||||||
self.instancePath)
|
"location='%s')" % self.instancePath)
|
||||||
f = file(codeFile, 'w')
|
f = file(codeFile, 'w')
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.close()
|
f.close()
|
||||||
|
# Some 'include' directives must be added with our install.
|
||||||
|
configPlone = j(self.productsFolder, 'CMFPlone', 'configure.zcml')
|
||||||
|
f = file(configPlone)
|
||||||
|
content = f.read()
|
||||||
|
f.close()
|
||||||
|
missing = ''
|
||||||
|
for missingInclude in self.missingIncludes:
|
||||||
|
missing += ' <include package="%s"/>\n' % missingInclude
|
||||||
|
content = content.replace('</configure>', '%s\n</configure>' % missing)
|
||||||
|
f = file(configPlone, 'w')
|
||||||
|
f.write(content)
|
||||||
|
f.close()
|
||||||
|
|
||||||
def copyEggs(self):
|
def copyEggs(self):
|
||||||
'''Copy content of eggs into the Zope instance.'''
|
'''Copy content of eggs into the Zope instance. This method also
|
||||||
|
retrieves every egg version and returns a dict {s_egg:s_version}.'''
|
||||||
j = os.path.join
|
j = os.path.join
|
||||||
eggsFolder = j(self.plonePath, 'buildout-cache/eggs')
|
eggsFolder = j(self.plonePath, 'buildout-cache/eggs')
|
||||||
self.ploneThemes = []
|
res = {}
|
||||||
for name in os.listdir(eggsFolder):
|
for name in os.listdir(eggsFolder):
|
||||||
if name == 'EGG-INFO': continue
|
if name == 'EGG-INFO': continue
|
||||||
|
splittedName = name.split('-')
|
||||||
|
res[splittedName[0]] = splittedName[1]
|
||||||
absName = j(eggsFolder, name)
|
absName = j(eggsFolder, name)
|
||||||
# Copy every file or sub-folder into self.libFolder or
|
# Copy every file or sub-folder into self.libFolder or
|
||||||
# self.productsFolder.
|
# self.productsFolder.
|
||||||
|
@ -175,6 +178,7 @@ class NewScript:
|
||||||
copyFolder(absFileName, j(self.libFolder, fileName))
|
copyFolder(absFileName, j(self.libFolder, fileName))
|
||||||
else:
|
else:
|
||||||
shutil.copy(absFileName, self.libFolder)
|
shutil.copy(absFileName, self.libFolder)
|
||||||
|
return res
|
||||||
|
|
||||||
def createInstance(self, linksForProducts):
|
def createInstance(self, linksForProducts):
|
||||||
'''Calls the Zope script that allows to create a Zope instance and copy
|
'''Calls the Zope script that allows to create a Zope instance and copy
|
||||||
|
@ -224,7 +228,7 @@ class NewScript:
|
||||||
if self.ploneVersion in ('plone25', 'plone30'):
|
if self.ploneVersion in ('plone25', 'plone30'):
|
||||||
self.installPlone25or30Stuff(linksForProducts)
|
self.installPlone25or30Stuff(linksForProducts)
|
||||||
elif self.ploneVersion in ('plone3x', 'plone4'):
|
elif self.ploneVersion in ('plone3x', 'plone4'):
|
||||||
self.copyEggs()
|
versions = self.copyEggs()
|
||||||
if self.ploneVersion == 'plone3x':
|
if self.ploneVersion == 'plone3x':
|
||||||
self.patchPlone3x()
|
self.patchPlone3x()
|
||||||
elif self.ploneVersion == 'plone4':
|
elif self.ploneVersion == 'plone4':
|
||||||
|
@ -234,7 +238,7 @@ class NewScript:
|
||||||
j(self.instancePath, 'lib/python'))
|
j(self.instancePath, 'lib/python'))
|
||||||
print cmd
|
print cmd
|
||||||
os.system(cmd)
|
os.system(cmd)
|
||||||
self.patchPlone4()
|
self.patchPlone4(versions)
|
||||||
# Remove .bat files under Linux
|
# Remove .bat files under Linux
|
||||||
if os.name == 'posix':
|
if os.name == 'posix':
|
||||||
cleanFolder(j(self.instancePath, 'bin'), exts=('.bat',))
|
cleanFolder(j(self.instancePath, 'bin'), exts=('.bat',))
|
||||||
|
|
181
bin/publish.py
Executable file → Normal file
181
bin/publish.py
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python2.4.4
|
#!/usr/bin/python
|
||||||
# Imports ----------------------------------------------------------------------
|
# Imports ----------------------------------------------------------------------
|
||||||
import os, os.path, shutil, re, zipfile, sys, ftplib, time
|
import os, os.path, sys, shutil, re, zipfile, sys, ftplib, time
|
||||||
import appy
|
import appy
|
||||||
from appy.shared import appyPath
|
from appy.shared import appyPath
|
||||||
from appy.shared.utils import FolderDeleter, LinesCounter
|
from appy.shared.utils import FolderDeleter, LinesCounter
|
||||||
|
@ -9,17 +9,23 @@ from appy.gen.utils import produceNiceMessage
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
versionRex = re.compile('(\d+\.\d+\.\d+)')
|
versionRex = re.compile('(\d+\.\d+\.\d+)')
|
||||||
eggInfo = '''import os, setuptools
|
distInfo = '''from distutils.core import setup
|
||||||
setuptools.setup(
|
setup(name = "appy", version = "%s",
|
||||||
name = "appy", version = "%s", description = "The Appy framework",
|
description = "The Appy framework",
|
||||||
long_description = "See http://appyframework.org",
|
long_description = "Appy builds simple but complex web Python apps.",
|
||||||
author = "Gaetan Delannay", author_email = "gaetan.delannay AT gmail.com",
|
author = "Gaetan Delannay",
|
||||||
license = "GPL", keywords = "plone, pod, pdf, odt, document",
|
author_email = "gaetan.delannay AT geezteem.com",
|
||||||
url = 'http://appyframework.org',
|
license = "GPL", platforms="all",
|
||||||
classifiers = ['Development Status :: 4 - Beta', "License :: OSI Approved"],
|
url = 'http://appyframework.org',
|
||||||
packages = setuptools.find_packages('src'), include_package_data = True,
|
packages = [%s],
|
||||||
package_dir = {'':'src'}, data_files = [('.', [])],
|
package_data = {'':["*.*"]})
|
||||||
namespace_packages = ['appy'], zip_safe = False)'''
|
'''
|
||||||
|
manifestInfo = '''
|
||||||
|
recursive-include appy/bin *
|
||||||
|
recursive-include appy/gen *
|
||||||
|
recursive-include appy/pod *
|
||||||
|
recursive-include appy/shared *
|
||||||
|
'''
|
||||||
|
|
||||||
def askLogin():
|
def askLogin():
|
||||||
print 'Login: ',
|
print 'Login: ',
|
||||||
|
@ -28,29 +34,6 @@ def askLogin():
|
||||||
passwd = sys.stdin.readline().strip()
|
passwd = sys.stdin.readline().strip()
|
||||||
return (login, passwd)
|
return (login, passwd)
|
||||||
|
|
||||||
def askQuestion(question, default='yes'):
|
|
||||||
'''Asks a question to the user (yes/no) and returns True if the user
|
|
||||||
answered "yes".'''
|
|
||||||
defaultIsYes = (default.lower() in ('y', 'yes'))
|
|
||||||
if defaultIsYes:
|
|
||||||
yesNo = '[Y/n]'
|
|
||||||
else:
|
|
||||||
yesNo = '[y/N]'
|
|
||||||
print question + ' ' + yesNo + ' ',
|
|
||||||
response = sys.stdin.readline().strip().lower()
|
|
||||||
res = False
|
|
||||||
if response in ('y', 'yes'):
|
|
||||||
res = True
|
|
||||||
elif response in ('n', 'no'):
|
|
||||||
res = False
|
|
||||||
elif not response:
|
|
||||||
# It depends on default value
|
|
||||||
if defaultIsYes:
|
|
||||||
res = True
|
|
||||||
else:
|
|
||||||
res = False
|
|
||||||
return res
|
|
||||||
|
|
||||||
class FtpFolder:
|
class FtpFolder:
|
||||||
'''Represents a folder on a FTP site.'''
|
'''Represents a folder on a FTP site.'''
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
@ -247,52 +230,95 @@ class Publisher:
|
||||||
self.versionLong = '%s (%s)' % (self.versionShort,
|
self.versionLong = '%s (%s)' % (self.versionShort,
|
||||||
time.strftime('%Y/%m/%d %H:%M'))
|
time.strftime('%Y/%m/%d %H:%M'))
|
||||||
f.close()
|
f.close()
|
||||||
|
# In silent mode (option -s), no question is asked, default answers are
|
||||||
|
# automatically given.
|
||||||
|
if (len(sys.argv) > 1) and (sys.argv[1] == '-s'):
|
||||||
|
self.silent = True
|
||||||
|
else:
|
||||||
|
self.silent = False
|
||||||
|
|
||||||
|
def askQuestion(self, question, default='yes'):
|
||||||
|
'''Asks a question to the user (yes/no) and returns True if the user
|
||||||
|
answered "yes".'''
|
||||||
|
if self.silent: return (default == 'yes')
|
||||||
|
defaultIsYes = (default.lower() in ('y', 'yes'))
|
||||||
|
if defaultIsYes:
|
||||||
|
yesNo = '[Y/n]'
|
||||||
|
else:
|
||||||
|
yesNo = '[y/N]'
|
||||||
|
print question + ' ' + yesNo + ' ',
|
||||||
|
response = sys.stdin.readline().strip().lower()
|
||||||
|
res = False
|
||||||
|
if response in ('y', 'yes'):
|
||||||
|
res = True
|
||||||
|
elif response in ('n', 'no'):
|
||||||
|
res = False
|
||||||
|
elif not response:
|
||||||
|
# It depends on default value
|
||||||
|
if defaultIsYes:
|
||||||
|
res = True
|
||||||
|
else:
|
||||||
|
res = False
|
||||||
|
return res
|
||||||
|
|
||||||
def executeCommand(self, cmd):
|
def executeCommand(self, cmd):
|
||||||
'''Executes the system command p_cmd.'''
|
'''Executes the system command p_cmd.'''
|
||||||
print 'Executing %s...' % cmd
|
print 'Executing %s...' % cmd
|
||||||
os.system(cmd)
|
os.system(cmd)
|
||||||
|
|
||||||
def createCodeAndEggReleases(self):
|
distExcluded = ('appy/doc', 'appy/temp', 'appy/versions', 'appy/gen/test')
|
||||||
'''Publishes the egg on pypi.python.org.'''
|
def isDistExcluded(self, name):
|
||||||
|
'''Returns True if folder named p_name must be included in the
|
||||||
|
distribution.'''
|
||||||
|
if '.bzr' in name: return True
|
||||||
|
for prefix in self.distExcluded:
|
||||||
|
if name.startswith(prefix): return True
|
||||||
|
|
||||||
|
def createDistRelease(self):
|
||||||
|
'''Create the distutils package.'''
|
||||||
curdir = os.getcwd()
|
curdir = os.getcwd()
|
||||||
if askQuestion('Upload eggs on PyPI?', default='no'):
|
distFolder = '%s/dist' % self.genFolder
|
||||||
# Create egg structure
|
# Create setup.py
|
||||||
eggFolder = '%s/egg' % self.genFolder
|
os.mkdir(distFolder)
|
||||||
os.mkdir(eggFolder)
|
f = file('%s/setup.py' % distFolder, 'w')
|
||||||
f = file('%s/setup.py' % eggFolder, 'w')
|
# List all packages to include
|
||||||
f.write(eggInfo % self.versionShort)
|
packages = []
|
||||||
f.close()
|
os.chdir(os.path.dirname(appyPath))
|
||||||
os.mkdir('%s/docs' % eggFolder)
|
for dir, dirnames, filenames in os.walk('appy'):
|
||||||
os.mkdir('%s/src' % eggFolder)
|
if self.isDistExcluded(dir): continue
|
||||||
os.mkdir('%s/src/appy' % eggFolder)
|
packageName = dir.replace('/', '.')
|
||||||
shutil.copy('%s/doc/version.txt' % appyPath,
|
packages.append('"%s"' % packageName)
|
||||||
'%s/docs/HISTORY.txt' % eggFolder)
|
f.write(distInfo % (self.versionShort, ','.join(packages)))
|
||||||
shutil.copy('%s/doc/license.txt' % appyPath,
|
f.close()
|
||||||
'%s/docs/LICENSE.txt' % eggFolder)
|
# Create MANIFEST.in
|
||||||
# Move appy sources within the egg
|
f = file('%s/MANIFEST.in' % distFolder, 'w')
|
||||||
os.rename('%s/appy' % self.genFolder, '%s/src/appy' % eggFolder)
|
f.write(manifestInfo)
|
||||||
# Create eggs and publish them on pypi
|
f.close()
|
||||||
os.chdir(eggFolder)
|
# Move appy sources within the dist folder
|
||||||
print 'Uploading appy%s source egg on PyPI...' % self.versionShort
|
os.rename('%s/appy' % self.genFolder, '%s/appy' % distFolder)
|
||||||
#self.executeCommand('python setup.py sdist upload')
|
# Create the source distribution
|
||||||
self.executeCommand('python setup.py sdist')
|
os.chdir(distFolder)
|
||||||
for pythonTarget in self.pythonTargets:
|
self.executeCommand('python setup.py sdist')
|
||||||
print 'Uploading appy%s binary egg for python%s...' % \
|
# DistUtils has created the .tar.gz file. Copy it into folder "versions"
|
||||||
(self.versionShort, pythonTarget)
|
name = 'appy-%s.tar.gz' % self.versionShort
|
||||||
#self.executeCommand('python%s setup.py bdist_egg upload' % \
|
os.rename('%s/dist/%s' % (distFolder, name),
|
||||||
# pythonTarget)
|
'%s/versions/%s' % (appyPath, name))
|
||||||
self.executeCommand('python%s setup.py bdist_egg' % \
|
# Clean temp files
|
||||||
pythonTarget)
|
|
||||||
os.chdir(curdir)
|
os.chdir(curdir)
|
||||||
|
FolderDeleter.delete(os.path.join(self.genFolder, 'dist'))
|
||||||
|
return name
|
||||||
|
|
||||||
|
def uploadOnPypi(self, name):
|
||||||
|
print 'Uploading %s on PyPI...' % name
|
||||||
|
#self.executeCommand('python setup.py sdist upload')
|
||||||
|
|
||||||
def createZipRelease(self):
|
def createZipRelease(self):
|
||||||
'''Creates a zip file with the appy sources.'''
|
'''Creates a zip file with the appy sources.'''
|
||||||
newZipRelease = '%s/versions/appy%s.zip' % (appyPath, self.versionShort)
|
newZipRelease = '%s/versions/appy%s.zip' % (appyPath, self.versionShort)
|
||||||
if os.path.exists(newZipRelease):
|
if os.path.exists(newZipRelease):
|
||||||
if not askQuestion('"%s" already exists. Replace it?' % \
|
if not self.askQuestion('"%s" already exists. Replace it?' % \
|
||||||
newZipRelease, default='yes'):
|
newZipRelease, default='yes'):
|
||||||
print 'Publication cancelled.'
|
print 'Publication canceled.'
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print 'Removing obsolete %s...' % newZipRelease
|
print 'Removing obsolete %s...' % newZipRelease
|
||||||
os.remove(newZipRelease)
|
os.remove(newZipRelease)
|
||||||
|
@ -306,8 +332,6 @@ class Publisher:
|
||||||
# [2:] is there to avoid havin './' in the path in the zip file.
|
# [2:] is there to avoid havin './' in the path in the zip file.
|
||||||
zipFile.close()
|
zipFile.close()
|
||||||
os.chdir(curdir)
|
os.chdir(curdir)
|
||||||
# Remove the "appy" folder within the gen folder.
|
|
||||||
FolderDeleter.delete(os.path.join(self.genFolder, 'appy'))
|
|
||||||
|
|
||||||
def applyTemplate(self):
|
def applyTemplate(self):
|
||||||
'''Decorates each page with the template.'''
|
'''Decorates each page with the template.'''
|
||||||
|
@ -405,7 +429,7 @@ class Publisher:
|
||||||
# Create a temp clean copy of appy sources (without .svn folders, etc)
|
# Create a temp clean copy of appy sources (without .svn folders, etc)
|
||||||
genSrcFolder = '%s/appy' % self.genFolder
|
genSrcFolder = '%s/appy' % self.genFolder
|
||||||
os.mkdir(genSrcFolder)
|
os.mkdir(genSrcFolder)
|
||||||
for aFile in ('__init__.py', 'install.txt'):
|
for aFile in ('__init__.py',):
|
||||||
shutil.copy('%s/%s' % (appyPath, aFile), genSrcFolder)
|
shutil.copy('%s/%s' % (appyPath, aFile), genSrcFolder)
|
||||||
for aFolder in ('gen', 'pod', 'shared', 'bin'):
|
for aFolder in ('gen', 'pod', 'shared', 'bin'):
|
||||||
shutil.copytree('%s/%s' % (appyPath, aFolder),
|
shutil.copytree('%s/%s' % (appyPath, aFolder),
|
||||||
|
@ -438,17 +462,18 @@ class Publisher:
|
||||||
# Perform a small analysis on the Appy code
|
# Perform a small analysis on the Appy code
|
||||||
LinesCounter(appy).run()
|
LinesCounter(appy).run()
|
||||||
print 'Generating site in %s...' % self.genFolder
|
print 'Generating site in %s...' % self.genFolder
|
||||||
minimalist = askQuestion('Minimalist (shipped without tests)?',
|
minimalist = self.askQuestion('Minimalist (shipped without tests)?',
|
||||||
default='no')
|
default='no')
|
||||||
self.prepareGenFolder(minimalist)
|
self.prepareGenFolder(minimalist)
|
||||||
self.createDocToc()
|
self.createDocToc()
|
||||||
self.applyTemplate()
|
self.applyTemplate()
|
||||||
self.createZipRelease()
|
self.createZipRelease()
|
||||||
#self.createCodeAndEggReleases()
|
tarball = self.createDistRelease()
|
||||||
if askQuestion('Do you want to publish the site on ' \
|
if self.askQuestion('Upload %s on PyPI?' % tarball, default='no'):
|
||||||
'appyframework.org?', default='no'):
|
self.uploadOnPypi(tarball)
|
||||||
|
if self.askQuestion('Publish on appyframework.org?', default='no'):
|
||||||
AppySite().publish()
|
AppySite().publish()
|
||||||
if askQuestion('Delete locally generated site ?', default='no'):
|
if self.askQuestion('Delete locally generated site ?', default='yes'):
|
||||||
FolderDeleter.delete(self.genFolder)
|
FolderDeleter.delete(self.genFolder)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -5,12 +5,13 @@ from appy import Object
|
||||||
from appy.gen.layout import Table
|
from appy.gen.layout import Table
|
||||||
from appy.gen.layout import defaultFieldLayouts
|
from appy.gen.layout import defaultFieldLayouts
|
||||||
from appy.gen.po import PoMessage
|
from appy.gen.po import PoMessage
|
||||||
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
|
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, getClassName, \
|
||||||
getClassName, SomeObjects
|
SomeObjects
|
||||||
import appy.pod
|
import appy.pod
|
||||||
from appy.pod.renderer import Renderer
|
from appy.pod.renderer import Renderer
|
||||||
from appy.shared.data import countries
|
from appy.shared.data import countries
|
||||||
from appy.shared.utils import Traceback, getOsTempFolder, formatNumber
|
from appy.shared.utils import Traceback, getOsTempFolder, formatNumber, \
|
||||||
|
FileWrapper
|
||||||
|
|
||||||
# Default Appy permissions -----------------------------------------------------
|
# Default Appy permissions -----------------------------------------------------
|
||||||
r, w, d = ('read', 'write', 'delete')
|
r, w, d = ('read', 'write', 'delete')
|
||||||
|
@ -1505,7 +1506,7 @@ class File(Type):
|
||||||
def getRequestValue(self, request):
|
def getRequestValue(self, request):
|
||||||
return request.get('%s_file' % self.name)
|
return request.get('%s_file' % self.name)
|
||||||
|
|
||||||
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
|
def getDefaultLayouts(self): return {'view':'l-f','edit':'lrv-f'}
|
||||||
|
|
||||||
def isEmptyValue(self, value, obj=None):
|
def isEmptyValue(self, value, obj=None):
|
||||||
'''Must p_value be considered as empty?'''
|
'''Must p_value be considered as empty?'''
|
||||||
|
@ -1536,8 +1537,9 @@ class File(Type):
|
||||||
* an instance of Zope class ZPublisher.HTTPRequest.FileUpload. In
|
* an instance of Zope class ZPublisher.HTTPRequest.FileUpload. In
|
||||||
this case, it is file content coming from a HTTP POST;
|
this case, it is file content coming from a HTTP POST;
|
||||||
* an instance of Zope class OFS.Image.File;
|
* an instance of Zope class OFS.Image.File;
|
||||||
* an instance of appy.gen.utils.FileWrapper, which wraps an instance
|
* an instance of appy.shared.utils.FileWrapper, which wraps an
|
||||||
of OFS.Image.File and adds useful methods for manipulating it;
|
instance of OFS.Image.File and adds useful methods for manipulating
|
||||||
|
it;
|
||||||
* a string. In this case, the string represents the path of a file
|
* a string. In this case, the string represents the path of a file
|
||||||
on disk;
|
on disk;
|
||||||
* a 2-tuple (fileName, fileContent) where:
|
* a 2-tuple (fileName, fileContent) where:
|
||||||
|
|
|
@ -675,7 +675,7 @@ class ZopeGenerator(Generator):
|
||||||
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
|
repls['languages'] = ','.join('"%s"' % l for l in self.config.languages)
|
||||||
repls['languageSelector'] = self.config.languageSelector
|
repls['languageSelector'] = self.config.languageSelector
|
||||||
repls['sourceLanguage'] = self.config.sourceLanguage
|
repls['sourceLanguage'] = self.config.sourceLanguage
|
||||||
self.copyFile('config.py', repls)
|
self.copyFile('config.pyt', repls, destName='config.py')
|
||||||
|
|
||||||
def generateInit(self):
|
def generateInit(self):
|
||||||
# Compute imports
|
# Compute imports
|
||||||
|
@ -690,7 +690,7 @@ class ZopeGenerator(Generator):
|
||||||
repls['imports'] = '\n'.join(imports)
|
repls['imports'] = '\n'.join(imports)
|
||||||
repls['classes'] = ','.join(classNames)
|
repls['classes'] = ','.join(classNames)
|
||||||
repls['totalNumberOfTests'] = self.totalNumberOfTests
|
repls['totalNumberOfTests'] = self.totalNumberOfTests
|
||||||
self.copyFile('__init__.py', repls)
|
self.copyFile('__init__.pyt', repls, destName='__init__.py')
|
||||||
|
|
||||||
def getClassesInOrder(self, allClasses):
|
def getClassesInOrder(self, allClasses):
|
||||||
'''When generating wrappers, classes mut be dumped in order (else, it
|
'''When generating wrappers, classes mut be dumped in order (else, it
|
||||||
|
@ -757,7 +757,7 @@ class ZopeGenerator(Generator):
|
||||||
for klass in self.getClasses(include='predefined'):
|
for klass in self.getClasses(include='predefined'):
|
||||||
modelClass = klass.modelClass
|
modelClass = klass.modelClass
|
||||||
repls['%s' % modelClass.__name__] = modelClass._appy_getBody()
|
repls['%s' % modelClass.__name__] = modelClass._appy_getBody()
|
||||||
self.copyFile('wrappers.py', repls)
|
self.copyFile('wrappers.pyt', repls, destName='wrappers.py')
|
||||||
|
|
||||||
def generateTests(self):
|
def generateTests(self):
|
||||||
'''Generates the file needed for executing tests.'''
|
'''Generates the file needed for executing tests.'''
|
||||||
|
@ -765,7 +765,8 @@ class ZopeGenerator(Generator):
|
||||||
modules = self.modulesWithTests
|
modules = self.modulesWithTests
|
||||||
repls['imports'] = '\n'.join(['import %s' % m for m in modules])
|
repls['imports'] = '\n'.join(['import %s' % m for m in modules])
|
||||||
repls['modulesWithTests'] = ','.join(modules)
|
repls['modulesWithTests'] = ','.join(modules)
|
||||||
self.copyFile('testAll.py', repls, destFolder='tests')
|
self.copyFile('testAll.pyt', repls, destName='testAll.py',
|
||||||
|
destFolder='tests')
|
||||||
|
|
||||||
def generateTool(self):
|
def generateTool(self):
|
||||||
'''Generates the tool that corresponds to this application.'''
|
'''Generates the tool that corresponds to this application.'''
|
||||||
|
@ -790,7 +791,7 @@ class ZopeGenerator(Generator):
|
||||||
repls.update({'methods': klass.methods, 'genClassName': klass.name,
|
repls.update({'methods': klass.methods, 'genClassName': klass.name,
|
||||||
'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem',
|
'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem',
|
||||||
'classDoc': 'Standard Appy class', 'icon':'object.gif'})
|
'classDoc': 'Standard Appy class', 'icon':'object.gif'})
|
||||||
self.copyFile('Class.py', repls, destName='%s.py' % klass.name)
|
self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name)
|
||||||
|
|
||||||
# Before generating the Tool class, finalize it with query result
|
# Before generating the Tool class, finalize it with query result
|
||||||
# columns, with fields to propagate, workflow-related fields.
|
# columns, with fields to propagate, workflow-related fields.
|
||||||
|
@ -817,7 +818,7 @@ class ZopeGenerator(Generator):
|
||||||
'genClassName': self.tool.name, 'baseMixin':'ToolMixin',
|
'genClassName': self.tool.name, 'baseMixin':'ToolMixin',
|
||||||
'parents': 'ToolMixin, Folder', 'icon': 'folder.gif',
|
'parents': 'ToolMixin, Folder', 'icon': 'folder.gif',
|
||||||
'classDoc': 'Tool class for %s' % self.applicationName})
|
'classDoc': 'Tool class for %s' % self.applicationName})
|
||||||
self.copyFile('Class.py', repls, destName='%s.py' % self.tool.name)
|
self.copyFile('Class.pyt', repls, destName='%s.py' % self.tool.name)
|
||||||
|
|
||||||
def generateClass(self, classDescr):
|
def generateClass(self, classDescr):
|
||||||
'''Is called each time an Appy class is found in the application, for
|
'''Is called each time an Appy class is found in the application, for
|
||||||
|
@ -863,7 +864,7 @@ class ZopeGenerator(Generator):
|
||||||
if poMsg not in self.labels:
|
if poMsg not in self.labels:
|
||||||
self.labels.append(poMsg)
|
self.labels.append(poMsg)
|
||||||
# Generate the resulting Zope class.
|
# Generate the resulting Zope class.
|
||||||
self.copyFile('Class.py', repls, destName=fileName)
|
self.copyFile('Class.pyt', repls, destName=fileName)
|
||||||
|
|
||||||
def generateWorkflow(self, wfDescr):
|
def generateWorkflow(self, wfDescr):
|
||||||
'''This method creates the i18n labels related to the workflow described
|
'''This method creates the i18n labels related to the workflow described
|
||||||
|
|
|
@ -781,7 +781,7 @@ class ToolMixin(BaseMixin):
|
||||||
if brain:
|
if brain:
|
||||||
sibling = brain.getObject()
|
sibling = brain.getObject()
|
||||||
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
||||||
page='main')
|
page=self.REQUEST.get('page', 'main'))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def tabularize(self, data, numberOfRows):
|
def tabularize(self, data, numberOfRows):
|
||||||
|
|
81
gen/utils.py
81
gen/utils.py
|
@ -1,7 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re, os, os.path, time
|
import re, os, os.path
|
||||||
import appy.pod
|
import appy.pod
|
||||||
from appy.shared.utils import getOsTempFolder, normalizeString, executeCommand
|
|
||||||
sequenceTypes = (list, tuple)
|
sequenceTypes = (list, tuple)
|
||||||
|
|
||||||
# Function for creating a Zope object ------------------------------------------
|
# Function for creating a Zope object ------------------------------------------
|
||||||
|
@ -242,84 +241,6 @@ class Keywords:
|
||||||
return op.join(self.keywords)+'*'
|
return op.join(self.keywords)+'*'
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
|
|
||||||
class FileWrapper:
|
|
||||||
'''When you get, from an appy object, the value of a File attribute, you
|
|
||||||
get an instance of this class.'''
|
|
||||||
def __init__(self, zopeFile):
|
|
||||||
'''This constructor is only used by Appy to create a nice File instance
|
|
||||||
from a Zope corresponding instance (p_zopeFile). If you need to
|
|
||||||
create a new file and assign it to a File attribute, use the
|
|
||||||
attribute setter, do not create yourself an instance of this
|
|
||||||
class.'''
|
|
||||||
d = self.__dict__
|
|
||||||
d['_zopeFile'] = zopeFile # Not for you!
|
|
||||||
d['name'] = zopeFile.filename
|
|
||||||
d['content'] = zopeFile.data
|
|
||||||
d['mimeType'] = zopeFile.content_type
|
|
||||||
d['size'] = zopeFile.size # In bytes
|
|
||||||
|
|
||||||
def __setattr__(self, name, v):
|
|
||||||
d = self.__dict__
|
|
||||||
if name == 'name':
|
|
||||||
self._zopeFile.filename = v
|
|
||||||
d['name'] = v
|
|
||||||
elif name == 'content':
|
|
||||||
self._zopeFile.update_data(v, self.mimeType, len(v))
|
|
||||||
d['content'] = v
|
|
||||||
d['size'] = len(v)
|
|
||||||
elif name == 'mimeType':
|
|
||||||
self._zopeFile.content_type = self.mimeType = v
|
|
||||||
else:
|
|
||||||
raise 'Impossible to set attribute %s. "Settable" attributes ' \
|
|
||||||
'are "name", "content" and "mimeType".' % name
|
|
||||||
|
|
||||||
def dump(self, filePath=None, format=None, tool=None):
|
|
||||||
'''Writes the file on disk. If p_filePath is specified, it is the
|
|
||||||
path name where the file will be dumped; folders mentioned in it
|
|
||||||
must exist. If not, the file will be dumped in the OS temp folder.
|
|
||||||
The absolute path name of the dumped file is returned.
|
|
||||||
If an error occurs, the method returns None. If p_format is
|
|
||||||
specified, OpenOffice will be called for converting the dumped file
|
|
||||||
to the desired format. In this case, p_tool, a Appy tool, must be
|
|
||||||
provided. Indeed, any Appy tool contains parameters for contacting
|
|
||||||
OpenOffice in server mode.'''
|
|
||||||
if not filePath:
|
|
||||||
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
|
|
||||||
normalizeString(self.name))
|
|
||||||
f = file(filePath, 'w')
|
|
||||||
if self.content.__class__.__name__ == 'Pdata':
|
|
||||||
# The file content is splitted in several chunks.
|
|
||||||
f.write(self.content.data)
|
|
||||||
nextPart = self.content.next
|
|
||||||
while nextPart:
|
|
||||||
f.write(nextPart.data)
|
|
||||||
nextPart = nextPart.next
|
|
||||||
else:
|
|
||||||
# Only one chunk
|
|
||||||
f.write(self.content)
|
|
||||||
f.close()
|
|
||||||
if format:
|
|
||||||
if not tool: return
|
|
||||||
# Convert the dumped file using OpenOffice
|
|
||||||
errorMessage = tool.convert(filePath, format)
|
|
||||||
# Even if we have an "error" message, it could be a simple warning.
|
|
||||||
# So we will continue here and, as a subsequent check for knowing if
|
|
||||||
# an error occurred or not, we will test the existence of the
|
|
||||||
# converted file (see below).
|
|
||||||
os.remove(filePath)
|
|
||||||
# Return the name of the converted file.
|
|
||||||
baseName, ext = os.path.splitext(filePath)
|
|
||||||
if (ext == '.%s' % format):
|
|
||||||
filePath = '%s.res.%s' % (baseName, format)
|
|
||||||
else:
|
|
||||||
filePath = '%s.%s' % (baseName, format)
|
|
||||||
if not os.path.exists(filePath):
|
|
||||||
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
|
|
||||||
return
|
|
||||||
return filePath
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
def getClassName(klass, appName=None):
|
def getClassName(klass, appName=None):
|
||||||
'''Generates, from appy-class p_klass, the name of the corresponding
|
'''Generates, from appy-class p_klass, the name of the corresponding
|
||||||
|
|
11
install.txt
11
install.txt
|
@ -1,11 +0,0 @@
|
||||||
Installation under Windows or MacOS
|
|
||||||
-----------------------------------
|
|
||||||
Copy the content of this folder to
|
|
||||||
<where_you_installed_python>\Lib\site-packages\appy
|
|
||||||
|
|
||||||
Installation under Linux
|
|
||||||
------------------------
|
|
||||||
Copy the content of this folder wherever you want (in /opt/appy for example)
|
|
||||||
and make a symbolic link in your Python lib folder (for example:
|
|
||||||
"ln -s /opt/appy /usr/lib/python2.5/site-packages/appy").
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import os, os.path, time, shutil, struct, random
|
import os, os.path, time, shutil, struct, random
|
||||||
from appy.pod import PodError
|
from appy.pod import PodError
|
||||||
from appy.pod.odf_parser import OdfEnvironment
|
from appy.pod.odf_parser import OdfEnvironment
|
||||||
|
from appy.shared.utils import FileWrapper
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
FILE_NOT_FOUND = "'%s' does not exist or is not a file."
|
FILE_NOT_FOUND = "'%s' does not exist or is not a file."
|
||||||
|
@ -59,9 +60,12 @@ class DocImporter:
|
||||||
self.importPath = self.moveFile(at, self.importPath)
|
self.importPath = self.moveFile(at, self.importPath)
|
||||||
else:
|
else:
|
||||||
# We need to dump the file content (in self.content) in a temp file
|
# We need to dump the file content (in self.content) in a temp file
|
||||||
# first. self.content may be binary or a file handler.
|
# first. self.content may be binary, a file handler or a
|
||||||
|
# FileWrapper.
|
||||||
if isinstance(self.content, file):
|
if isinstance(self.content, file):
|
||||||
fileContent = self.content.read()
|
fileContent = self.content.read()
|
||||||
|
elif isinstance(self.content, FileWrapper):
|
||||||
|
fileContent = content.content
|
||||||
else:
|
else:
|
||||||
fileContent = self.content
|
fileContent = self.content
|
||||||
f = file(self.importPath, 'wb')
|
f = file(self.importPath, 'wb')
|
||||||
|
|
|
@ -26,6 +26,7 @@ from appy.pod import PodError
|
||||||
from appy.shared import mimeTypesExts
|
from appy.shared import mimeTypesExts
|
||||||
from appy.shared.xml_parser import XmlElement
|
from appy.shared.xml_parser import XmlElement
|
||||||
from appy.shared.utils import FolderDeleter, executeCommand
|
from appy.shared.utils import FolderDeleter, executeCommand
|
||||||
|
from appy.shared.utils import FileWrapper
|
||||||
from appy.pod.pod_parser import PodParser, PodEnvironment, OdInsert
|
from appy.pod.pod_parser import PodParser, PodEnvironment, OdInsert
|
||||||
from appy.pod.converter import FILE_TYPES
|
from appy.pod.converter import FILE_TYPES
|
||||||
from appy.pod.buffers import FileBuffer
|
from appy.pod.buffers import FileBuffer
|
||||||
|
@ -281,6 +282,8 @@ class Renderer:
|
||||||
if not content and not at:
|
if not content and not at:
|
||||||
raise PodError(DOC_NOT_SPECIFIED)
|
raise PodError(DOC_NOT_SPECIFIED)
|
||||||
# Guess document format
|
# Guess document format
|
||||||
|
if isinstance(content, FileWrapper):
|
||||||
|
format = content.mimeType
|
||||||
if not format:
|
if not format:
|
||||||
# It should be deduced from p_at
|
# It should be deduced from p_at
|
||||||
if not at:
|
if not at:
|
||||||
|
|
|
@ -410,4 +410,82 @@ class LinesCounter:
|
||||||
elif ext in exts['pt']:
|
elif ext in exts['pt']:
|
||||||
self.zpt[self.inTest].analyseFile(j(root, fileName))
|
self.zpt[self.inTest].analyseFile(j(root, fileName))
|
||||||
self.printReport()
|
self.printReport()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
|
||||||
|
class FileWrapper:
|
||||||
|
'''When you get, from an appy object, the value of a File attribute, you
|
||||||
|
get an instance of this class.'''
|
||||||
|
def __init__(self, zopeFile):
|
||||||
|
'''This constructor is only used by Appy to create a nice File instance
|
||||||
|
from a Zope corresponding instance (p_zopeFile). If you need to
|
||||||
|
create a new file and assign it to a File attribute, use the
|
||||||
|
attribute setter, do not create yourself an instance of this
|
||||||
|
class.'''
|
||||||
|
d = self.__dict__
|
||||||
|
d['_zopeFile'] = zopeFile # Not for you!
|
||||||
|
d['name'] = zopeFile.filename
|
||||||
|
d['content'] = zopeFile.data
|
||||||
|
d['mimeType'] = zopeFile.content_type
|
||||||
|
d['size'] = zopeFile.size # In bytes
|
||||||
|
|
||||||
|
def __setattr__(self, name, v):
|
||||||
|
d = self.__dict__
|
||||||
|
if name == 'name':
|
||||||
|
self._zopeFile.filename = v
|
||||||
|
d['name'] = v
|
||||||
|
elif name == 'content':
|
||||||
|
self._zopeFile.update_data(v, self.mimeType, len(v))
|
||||||
|
d['content'] = v
|
||||||
|
d['size'] = len(v)
|
||||||
|
elif name == 'mimeType':
|
||||||
|
self._zopeFile.content_type = self.mimeType = v
|
||||||
|
else:
|
||||||
|
raise 'Impossible to set attribute %s. "Settable" attributes ' \
|
||||||
|
'are "name", "content" and "mimeType".' % name
|
||||||
|
|
||||||
|
def dump(self, filePath=None, format=None, tool=None):
|
||||||
|
'''Writes the file on disk. If p_filePath is specified, it is the
|
||||||
|
path name where the file will be dumped; folders mentioned in it
|
||||||
|
must exist. If not, the file will be dumped in the OS temp folder.
|
||||||
|
The absolute path name of the dumped file is returned.
|
||||||
|
If an error occurs, the method returns None. If p_format is
|
||||||
|
specified, OpenOffice will be called for converting the dumped file
|
||||||
|
to the desired format. In this case, p_tool, a Appy tool, must be
|
||||||
|
provided. Indeed, any Appy tool contains parameters for contacting
|
||||||
|
OpenOffice in server mode.'''
|
||||||
|
if not filePath:
|
||||||
|
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
|
||||||
|
normalizeString(self.name))
|
||||||
|
f = file(filePath, 'w')
|
||||||
|
if self.content.__class__.__name__ == 'Pdata':
|
||||||
|
# The file content is splitted in several chunks.
|
||||||
|
f.write(self.content.data)
|
||||||
|
nextPart = self.content.next
|
||||||
|
while nextPart:
|
||||||
|
f.write(nextPart.data)
|
||||||
|
nextPart = nextPart.next
|
||||||
|
else:
|
||||||
|
# Only one chunk
|
||||||
|
f.write(self.content)
|
||||||
|
f.close()
|
||||||
|
if format:
|
||||||
|
if not tool: return
|
||||||
|
# Convert the dumped file using OpenOffice
|
||||||
|
errorMessage = tool.convert(filePath, format)
|
||||||
|
# Even if we have an "error" message, it could be a simple warning.
|
||||||
|
# So we will continue here and, as a subsequent check for knowing if
|
||||||
|
# an error occurred or not, we will test the existence of the
|
||||||
|
# converted file (see below).
|
||||||
|
os.remove(filePath)
|
||||||
|
# Return the name of the converted file.
|
||||||
|
baseName, ext = os.path.splitext(filePath)
|
||||||
|
if (ext == '.%s' % format):
|
||||||
|
filePath = '%s.res.%s' % (baseName, format)
|
||||||
|
else:
|
||||||
|
filePath = '%s.%s' % (baseName, format)
|
||||||
|
if not os.path.exists(filePath):
|
||||||
|
tool.log(CONVERSION_ERROR % (cmd, errorMessage), type='error')
|
||||||
|
return
|
||||||
|
return filePath
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in a new issue