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:
Gaetan Delannay 2011-12-15 22:56:53 +01:00
parent e78cf62694
commit 6ece750d9a
15 changed files with 237 additions and 210 deletions

View file

@ -109,8 +109,9 @@ class NewScript:
f.write(self.patchRex.sub('<!--Del. includePlugins-->',fileContent))
f.close()
filesToPatch2 = ('profiles/default/skins.xml')
def patchPlone4(self):
missingIncludes = ('plone.app.upgrade', 'plonetheme.sunburst',
'plonetheme.classic')
def patchPlone4(self, versions):
'''Patches Plone 4 that can't live without buildout as-is.'''
self.patchPlone3x() # We still need this for Plone 4 as well.
# bin/zopectl
@ -124,42 +125,44 @@ class NewScript:
f.write(content)
f.close()
j = os.path.join
themeFolder = '%s/plonetheme' % self.libFolder
for theme in os.listdir(themeFolder):
# Create a simlink to every theme in self.productsFolder
tFolder = j(themeFolder, theme)
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.
# As eggs have been deleted, versions of components are lost. Reify
# them from p_versions.
dVersions = ['"%s":"%s"' % (n, v) for n, v in versions.iteritems()]
sVersions = 'appyVersions = {' + ','.join(dVersions) + '}'
codeFile = "%s/pkg_resources.py" % self.libFolder
f = file(codeFile)
content = f.read()
content = sVersions + '\n' + f.read()
f.close()
content = content.replace("raise DistributionNotFound(req)",
"dist = Distribution(project_name=req.project_name, " \
"version='1.1.1', platform='linux2', location='%s')" % \
self.instancePath)
"version=appyVersions[req.project_name], platform='linux2', " \
"location='%s')" % self.instancePath)
f = file(codeFile, 'w')
f.write(content)
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):
'''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
eggsFolder = j(self.plonePath, 'buildout-cache/eggs')
self.ploneThemes = []
res = {}
for name in os.listdir(eggsFolder):
if name == 'EGG-INFO': continue
splittedName = name.split('-')
res[splittedName[0]] = splittedName[1]
absName = j(eggsFolder, name)
# Copy every file or sub-folder into self.libFolder or
# self.productsFolder.
@ -175,6 +178,7 @@ class NewScript:
copyFolder(absFileName, j(self.libFolder, fileName))
else:
shutil.copy(absFileName, self.libFolder)
return res
def createInstance(self, linksForProducts):
'''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'):
self.installPlone25or30Stuff(linksForProducts)
elif self.ploneVersion in ('plone3x', 'plone4'):
self.copyEggs()
versions = self.copyEggs()
if self.ploneVersion == 'plone3x':
self.patchPlone3x()
elif self.ploneVersion == 'plone4':
@ -234,7 +238,7 @@ class NewScript:
j(self.instancePath, 'lib/python'))
print cmd
os.system(cmd)
self.patchPlone4()
self.patchPlone4(versions)
# Remove .bat files under Linux
if os.name == 'posix':
cleanFolder(j(self.instancePath, 'bin'), exts=('.bat',))

181
bin/publish.py Executable file → Normal file
View file

@ -1,6 +1,6 @@
#!/usr/bin/python2.4.4
#!/usr/bin/python
# Imports ----------------------------------------------------------------------
import os, os.path, shutil, re, zipfile, sys, ftplib, time
import os, os.path, sys, shutil, re, zipfile, sys, ftplib, time
import appy
from appy.shared import appyPath
from appy.shared.utils import FolderDeleter, LinesCounter
@ -9,17 +9,23 @@ from appy.gen.utils import produceNiceMessage
# ------------------------------------------------------------------------------
versionRex = re.compile('(\d+\.\d+\.\d+)')
eggInfo = '''import os, setuptools
setuptools.setup(
name = "appy", version = "%s", description = "The Appy framework",
long_description = "See http://appyframework.org",
author = "Gaetan Delannay", author_email = "gaetan.delannay AT gmail.com",
license = "GPL", keywords = "plone, pod, pdf, odt, document",
url = 'http://appyframework.org',
classifiers = ['Development Status :: 4 - Beta', "License :: OSI Approved"],
packages = setuptools.find_packages('src'), include_package_data = True,
package_dir = {'':'src'}, data_files = [('.', [])],
namespace_packages = ['appy'], zip_safe = False)'''
distInfo = '''from distutils.core import setup
setup(name = "appy", version = "%s",
description = "The Appy framework",
long_description = "Appy builds simple but complex web Python apps.",
author = "Gaetan Delannay",
author_email = "gaetan.delannay AT geezteem.com",
license = "GPL", platforms="all",
url = 'http://appyframework.org',
packages = [%s],
package_data = {'':["*.*"]})
'''
manifestInfo = '''
recursive-include appy/bin *
recursive-include appy/gen *
recursive-include appy/pod *
recursive-include appy/shared *
'''
def askLogin():
print 'Login: ',
@ -28,29 +34,6 @@ def askLogin():
passwd = sys.stdin.readline().strip()
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:
'''Represents a folder on a FTP site.'''
def __init__(self, name):
@ -247,52 +230,95 @@ class Publisher:
self.versionLong = '%s (%s)' % (self.versionShort,
time.strftime('%Y/%m/%d %H:%M'))
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):
'''Executes the system command p_cmd.'''
print 'Executing %s...' % cmd
os.system(cmd)
def createCodeAndEggReleases(self):
'''Publishes the egg on pypi.python.org.'''
distExcluded = ('appy/doc', 'appy/temp', 'appy/versions', 'appy/gen/test')
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()
if askQuestion('Upload eggs on PyPI?', default='no'):
# Create egg structure
eggFolder = '%s/egg' % self.genFolder
os.mkdir(eggFolder)
f = file('%s/setup.py' % eggFolder, 'w')
f.write(eggInfo % self.versionShort)
f.close()
os.mkdir('%s/docs' % eggFolder)
os.mkdir('%s/src' % eggFolder)
os.mkdir('%s/src/appy' % eggFolder)
shutil.copy('%s/doc/version.txt' % appyPath,
'%s/docs/HISTORY.txt' % eggFolder)
shutil.copy('%s/doc/license.txt' % appyPath,
'%s/docs/LICENSE.txt' % eggFolder)
# Move appy sources within the egg
os.rename('%s/appy' % self.genFolder, '%s/src/appy' % eggFolder)
# Create eggs and publish them on pypi
os.chdir(eggFolder)
print 'Uploading appy%s source egg on PyPI...' % self.versionShort
#self.executeCommand('python setup.py sdist upload')
self.executeCommand('python setup.py sdist')
for pythonTarget in self.pythonTargets:
print 'Uploading appy%s binary egg for python%s...' % \
(self.versionShort, pythonTarget)
#self.executeCommand('python%s setup.py bdist_egg upload' % \
# pythonTarget)
self.executeCommand('python%s setup.py bdist_egg' % \
pythonTarget)
distFolder = '%s/dist' % self.genFolder
# Create setup.py
os.mkdir(distFolder)
f = file('%s/setup.py' % distFolder, 'w')
# List all packages to include
packages = []
os.chdir(os.path.dirname(appyPath))
for dir, dirnames, filenames in os.walk('appy'):
if self.isDistExcluded(dir): continue
packageName = dir.replace('/', '.')
packages.append('"%s"' % packageName)
f.write(distInfo % (self.versionShort, ','.join(packages)))
f.close()
# Create MANIFEST.in
f = file('%s/MANIFEST.in' % distFolder, 'w')
f.write(manifestInfo)
f.close()
# Move appy sources within the dist folder
os.rename('%s/appy' % self.genFolder, '%s/appy' % distFolder)
# Create the source distribution
os.chdir(distFolder)
self.executeCommand('python setup.py sdist')
# DistUtils has created the .tar.gz file. Copy it into folder "versions"
name = 'appy-%s.tar.gz' % self.versionShort
os.rename('%s/dist/%s' % (distFolder, name),
'%s/versions/%s' % (appyPath, name))
# Clean temp files
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):
'''Creates a zip file with the appy sources.'''
newZipRelease = '%s/versions/appy%s.zip' % (appyPath, self.versionShort)
if os.path.exists(newZipRelease):
if not askQuestion('"%s" already exists. Replace it?' % \
newZipRelease, default='yes'):
print 'Publication cancelled.'
if not self.askQuestion('"%s" already exists. Replace it?' % \
newZipRelease, default='yes'):
print 'Publication canceled.'
sys.exit(1)
print 'Removing obsolete %s...' % newZipRelease
os.remove(newZipRelease)
@ -306,8 +332,6 @@ class Publisher:
# [2:] is there to avoid havin './' in the path in the zip file.
zipFile.close()
os.chdir(curdir)
# Remove the "appy" folder within the gen folder.
FolderDeleter.delete(os.path.join(self.genFolder, 'appy'))
def applyTemplate(self):
'''Decorates each page with the template.'''
@ -405,7 +429,7 @@ class Publisher:
# Create a temp clean copy of appy sources (without .svn folders, etc)
genSrcFolder = '%s/appy' % self.genFolder
os.mkdir(genSrcFolder)
for aFile in ('__init__.py', 'install.txt'):
for aFile in ('__init__.py',):
shutil.copy('%s/%s' % (appyPath, aFile), genSrcFolder)
for aFolder in ('gen', 'pod', 'shared', 'bin'):
shutil.copytree('%s/%s' % (appyPath, aFolder),
@ -438,17 +462,18 @@ class Publisher:
# Perform a small analysis on the Appy code
LinesCounter(appy).run()
print 'Generating site in %s...' % self.genFolder
minimalist = askQuestion('Minimalist (shipped without tests)?',
default='no')
minimalist = self.askQuestion('Minimalist (shipped without tests)?',
default='no')
self.prepareGenFolder(minimalist)
self.createDocToc()
self.applyTemplate()
self.createZipRelease()
#self.createCodeAndEggReleases()
if askQuestion('Do you want to publish the site on ' \
'appyframework.org?', default='no'):
tarball = self.createDistRelease()
if self.askQuestion('Upload %s on PyPI?' % tarball, default='no'):
self.uploadOnPypi(tarball)
if self.askQuestion('Publish on appyframework.org?', default='no'):
AppySite().publish()
if askQuestion('Delete locally generated site ?', default='no'):
if self.askQuestion('Delete locally generated site ?', default='yes'):
FolderDeleter.delete(self.genFolder)
# ------------------------------------------------------------------------------