Implemented blueprints https://blueprints.launchpad.net/appy/+spec/gen-create-root-objects, https://blueprints.launchpad.net/appy/+spec/gen-get-flavour and https://blueprints.launchpad.net/appy/+spec/pod-define-variables
This commit is contained in:
parent
ec17f900a6
commit
10eea7d735
|
@ -226,12 +226,6 @@ class Text2Html:
|
|||
self.txtFile.close()
|
||||
self.htmlFile.close()
|
||||
|
||||
class TodoConverter(Text2Html):
|
||||
title = 'To do'
|
||||
firstChar = 1 # Position of the first relevant char in each line
|
||||
def retainLine(self, line):
|
||||
return line.startswith('v') and len(line) > 2
|
||||
|
||||
class VersionsConverter(Text2Html):
|
||||
title = 'Versions'
|
||||
firstChar = 0
|
||||
|
@ -482,7 +476,6 @@ class Publisher:
|
|||
f.write('verbose = "%s"' % self.versionLong)
|
||||
f.close()
|
||||
# Remove unwanted files
|
||||
os.remove('%s/todo.txt' % self.genFolder)
|
||||
os.remove('%s/version.txt' % self.genFolder)
|
||||
os.remove('%s/license.txt' % self.genFolder)
|
||||
os.remove('%s/template.html' % self.genFolder)
|
||||
|
@ -492,10 +485,7 @@ class Publisher:
|
|||
for dirName in dirs:
|
||||
if dirName == '.svn':
|
||||
FolderDeleter.delete(os.path.join(root, dirName))
|
||||
# Generates the "to do" and "versions" pages, based on todo.txt and
|
||||
# version.txt
|
||||
TodoConverter('%s/doc/todo.txt' % appyPath,
|
||||
'%s/todo.html' % self.genFolder).run()
|
||||
# Generates the "versions" page, based on version.txt
|
||||
VersionsConverter('%s/doc/version.txt' % appyPath,
|
||||
'%s/version.html' % self.genFolder).run()
|
||||
|
||||
|
|
69
doc/todo.txt
69
doc/todo.txt
|
@ -1,69 +0,0 @@
|
|||
vpod
|
||||
v- Error in buffers.py line 422 (in method evaluate): evalEntry.action may be None but I don't know why (maybe due to buffers cut?)
|
||||
v- Document the ability to read the template from memory, not only from the file system (it seems to work)
|
||||
v- Size of resulting ODTs is too big.
|
||||
v- Try to convert xhtml chunks that are not utf-8-encoded
|
||||
v- XHTML conversion is not implemented for all XHTML tags.
|
||||
v- XHTML conversion produces empty results for some XHTML tag combinations
|
||||
v- Implement a function "pod" that allows to insert the content of another pod template into the current file
|
||||
v- Implement a "retry n times" function when calling OO in server mode ("n" should be parameterized)
|
||||
v- Add a "do bullet" (li, ul) ?
|
||||
v- Add a function "text" that replaces "carriage returns" with </p><p> (ou <br/>?) and that replaces leading dashes with bullets.
|
||||
v- Deprecate "xhtml" function -> New function text(format=...) with format="xhtml" or "text" or...
|
||||
v- ImageImporter: adding 'g' to file names may lead to too long file names.
|
||||
v- Finalize function "document" (manage Microsoft formats: doc, xls...)
|
||||
v- converter.py: allow to call converter.py without updating sections and/or indexes (may cause performance problems?)
|
||||
v- When an error occurs in a for loop, the iteratr variable may not be incremented, which duplicates the errors in subsequent iterations. Try:finally?
|
||||
|
||||
vgen
|
||||
v- View on AppyFolder must be query.pt
|
||||
v- Build automatically a research page (included fields are selectable through the flavour) only with "searchable" fields.
|
||||
v- Correct some navigation problems (after actions like delete, edit, etc)
|
||||
v- Validation system: check if it is possible to forget about duplicating page and group labels in child classes. Possible to remove xxx_list labels?
|
||||
v- Add a button for checking if current Python running Zope is UNO-compliant.
|
||||
v- __setattr__ for Refs on Wrappers do not work, but the problem is solved in method "create" (inspiration source).
|
||||
v- Generate a static site from a gen-application
|
||||
v- Xml export (on file system or via webdav/REST)
|
||||
v- Add an option to generator, where un-managed i18n labels are removed (useful during development, when fields are creates, deleted, renamed...)
|
||||
v- Make the list of reserved attribute and method names (tool, trigger, state, ...) and raise errors on generation if those names are used by a gen-class
|
||||
v- produceNiceDefault: manage numbers (when a number is encountered, add a space, etc).
|
||||
v- When editing (not creating) a flavour (or when reinstalling the product), create ALL the portal_types if they do not exist (some may have been added after flavour creation).
|
||||
v- By flavour or date object, add 2 params: date format with and without hour. Create a appy-specific macro for displaying dates, that will use those params.
|
||||
v- Move some generated code (appyMixins, CommonMethods...) in appy.gen (minimize generated code).
|
||||
v- Create the "pod" field type
|
||||
v- Add on tool special action "uninstall" (similar to special action "install") + question "update workflow settings or not" ?
|
||||
v- At startup, patch actions "tool.install" -> show = si selon Plone le Product doit etre reinstallé, i18n messages, etc.
|
||||
v- Allow to create objects in the app root folder (and not only in the tool) from the tool (or anywhere else).
|
||||
v- On dashboards and Ref tables, fields rendering does not always take into account permissions, show, etc.
|
||||
v- Add "self.flavour" on any wrapper.
|
||||
v- Config instance: add supposed number of flavours (for generating corresponding i18n labels _1, _2, etc, for flavour-specific content-types)
|
||||
v- Config instance: create a defaultWorkflow attribute, similar to defaultCreators
|
||||
v- Duplicate all special fields (optionalFieldsFor, defaultValueFor, etc) generated in a flavour for all child classes of a given class.
|
||||
v- Permissions-to-roles mappings: if you don't specify a value for a given field-specific read/write permission, by default it will take the same value as for the whole-gen-class read/write permission.
|
||||
v- Implement abstract workflows
|
||||
v- displayTabs: do not display tabs for which no widget shows up (ie edit tabs on which no Ref widget is shown)
|
||||
v- Add i18n labels for messages when transitions are triggered (! transition may potentially return a custom message!)
|
||||
v- On all widgets: implement "warning method" -> kind of condition. If True, a warning message appears somewhere near the field. Useful for actions, transitions and pod templates.
|
||||
v- Action fields: add param "confirm" (also for workflow transitions): if True a popup appears (with possibility to define a i18n message)
|
||||
v- Ref fields: add parameter "sort" for sorting Ref fields (this param may be the name of a field or a custom method). On Refs with link=True, it is used for sorting the list of all references that may be linked with the current object; on Refs with add=True, it is used for inserting the newly created object.
|
||||
v- tool.getReferenceLabel: use brain if fields are indexed (wake up object only when needed)
|
||||
v- Delete tool.maxListBoxWidth and use param "width" of Ref field instead.
|
||||
v- Comments in ZPTs: use everywhere tal tag "comment"
|
||||
v- Common method "fieldValueSelected" -> does it produce the right value when the page is shown again due to a validation error? Must we check this case explicitly via emptyness of dict "errors" in request?
|
||||
v- Document how to write your own code generator
|
||||
v- pod integratio: when returning a PDF/ODT file, file name is not set (or file extension).
|
||||
v- Add a param to pod fields will allow to freeze generated result, and to unfreeze or re-generate them (with admin access only or...?)
|
||||
v- Take into account field-specific permissions when displaying backward fields
|
||||
v- Improve graphical rendering of pages with additional classes (Group, Page,...) that may be specified instead of strings for parameters "group", "page" on fields
|
||||
v- Add an event management system (generalize the mail notification mechanism from PloneMeeting) that one may customize per flavour, with some predefined action types (emails, log, freeze pdf,...)
|
||||
v- Interoperability: class and worklow inheritance from one product to another and Refs to objects coming from other standard non Appy Plone products.
|
||||
v- Add a mechanism that will allow developers to add things like ZPTs, etc, into the Plone product.
|
||||
v- Write a test system.
|
||||
v- Bug (seems to come from standard Plone): when defining a field multiselection mandatory, when unselecting all values, it does not do anything (if the field is not mandatory it works).
|
||||
v- When we copy fields in class Flavour (because of editDefault), we must also recopy methods that were defined on the original Appy class on the FLavour class generated in appyWrappers.py.
|
||||
v- Check accessors get_flavour defined multiple times in PodTemplate_Wrapper
|
||||
v- Bug: when we specify encoding in a Python file, the generated Python AST is empty.
|
||||
v- plone25: remove warnings by setting deprecated=True for all generated classes in configure.zcml
|
||||
v- Workflow dependencies: permissions of a given object can depend on the ones defined on another (containing?).
|
||||
v- Add an on-line help system (popup for every field)
|
||||
|
|
@ -408,9 +408,11 @@ class Generator(AbstractGenerator):
|
|||
self.copyFile('workflows.py', repls, destFolder='Extensions')
|
||||
|
||||
def generateWrapperProperty(self, attrName, appyType):
|
||||
# Generate getter
|
||||
'''Generates the getter for attribute p_attrName having type
|
||||
p_appyType.'''
|
||||
res = ' def get_%s(self):\n' % attrName
|
||||
blanks = ' '*8
|
||||
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
|
||||
if isinstance(appyType, Ref):
|
||||
res += blanks + 'return self.o._appy_getRefs("%s", ' \
|
||||
'noListIfSingleObj=True)\n' % attrName
|
||||
|
@ -418,8 +420,11 @@ class Generator(AbstractGenerator):
|
|||
res += blanks + 'appyType = getattr(self.klass, "%s")\n' % attrName
|
||||
res += blanks + 'return self.o.getComputedValue(' \
|
||||
'appyType.__dict__)\n'
|
||||
elif isinstance(appyType, File):
|
||||
res += blanks + 'v = self.o.%s()\n' % getterName
|
||||
res += blanks + 'if not v: return None\n'
|
||||
res += blanks + 'else: return FileWrapper(v)\n'
|
||||
else:
|
||||
getterName = 'get%s%s' % (attrName[0].upper(), attrName[1:])
|
||||
if attrName in ArchetypeFieldDescriptor.specialParams:
|
||||
getterName = attrName.capitalize()
|
||||
res += blanks + 'return self.o.%s()\n' % getterName
|
||||
|
|
|
@ -142,9 +142,14 @@ class ToolMixin(AbstractMixin):
|
|||
else:
|
||||
# Create a FieldDescr instance
|
||||
appyType = anObject.getAppyType(fieldName)
|
||||
atField = anObject.schema.get(fieldName)
|
||||
fieldDescr = FieldDescr(atField, appyType, None)
|
||||
res.append(fieldDescr.get())
|
||||
if not appyType:
|
||||
res.append({'atField': None, 'name': fieldName})
|
||||
# The field name is wrong.
|
||||
# We return it so we can show it in an error message.
|
||||
else:
|
||||
atField = anObject.schema.get(fieldName)
|
||||
fieldDescr = FieldDescr(atField, appyType, None)
|
||||
res.append(fieldDescr.get())
|
||||
return res
|
||||
|
||||
xhtmlToText = re.compile('<.*?>', re.S)
|
||||
|
|
|
@ -654,7 +654,8 @@
|
|||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||
<tal:otherFields repeat="fieldDescr fieldDescrs">
|
||||
<tal:standardField condition="python: fieldDescr != 'workflowState'">
|
||||
<td tal:attributes="id python:'field_%s' % fieldDescr['atField'].getName()">
|
||||
<td tal:condition="fieldDescr/atField"
|
||||
tal:attributes="id python:'field_%s' % fieldDescr['atField'].getName()">
|
||||
<tal:field define="contextObj python:obj;
|
||||
isEdit python:False;
|
||||
showLabel python:False;
|
||||
|
@ -663,6 +664,9 @@
|
|||
<metal:field use-macro="here/<!macros!>/macros/showArchetypesField"/>
|
||||
</tal:field>
|
||||
</td>
|
||||
<td tal:condition="not: fieldDescr/atField" style="color:red">Field
|
||||
<span tal:replace="fieldDescr/name"/> not found.
|
||||
</td>
|
||||
</tal:standardField>
|
||||
<tal:workflowState condition="python: fieldDescr == 'workflowState'">
|
||||
<td id="field_workflow_state" i18n:translate="" tal:content="obj/getWorkflowLabel"></td>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen import *
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper, FileWrapper
|
||||
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
|
||||
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
|
||||
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
|
||||
|
|
|
@ -18,6 +18,7 @@ from OFS.Image import File
|
|||
from DateTime import DateTime
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
from Products.CMFPlone.PloneBatch import Batch
|
||||
from OFS.Image import File
|
||||
import logging
|
||||
logger = logging.getLogger('<!applicationName!>')
|
||||
|
||||
|
|
|
@ -2,7 +2,14 @@
|
|||
developer the real classes used by the undelrying web framework.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import time
|
||||
import time, os.path, mimetypes
|
||||
from appy.gen.utils import sequenceTypes
|
||||
from appy.shared.utils import getOsTempFolder
|
||||
|
||||
# Some error messages ----------------------------------------------------------
|
||||
WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
|
||||
'2-tuple (fileName, fileContent) or a 3-tuple (fileName, fileContent, ' \
|
||||
'mimeType).'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class AbstractWrapper:
|
||||
|
@ -10,8 +17,56 @@ class AbstractWrapper:
|
|||
of this class.'''
|
||||
def __init__(self, o):
|
||||
self.__dict__['o'] = o
|
||||
def _set_file_attribute(self, name, v):
|
||||
'''Updates the value of a file attribute named p_name with value p_v.
|
||||
p_v may be:
|
||||
- a string value containing the path to a file on disk;
|
||||
- a 2-tuple (fileName, fileContent) where
|
||||
* fileName = the name of the file (ie "myFile.odt")
|
||||
* fileContent = the binary or textual content of the file or an
|
||||
open file handler.
|
||||
- a 3-tuple (fileName, fileContent, mimeType) where mimeType is the
|
||||
v MIME type of the file.'''
|
||||
ploneFileClass = self.o.getProductConfig().File
|
||||
if isinstance(v, ploneFileClass):
|
||||
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
|
||||
elif isinstance(v, FileWrapper):
|
||||
setattr(self, name, v._atFile)
|
||||
elif isinstance(v, basestring):
|
||||
f = file(v)
|
||||
fileName = os.path.basename(v)
|
||||
fileId = 'file.%f' % time.time()
|
||||
ploneFile = ploneFileClass(fileId, fileName, f)
|
||||
ploneFile.filename = fileName
|
||||
ploneFile.content_type = mimetypes.guess_type(fileName)[0]
|
||||
setattr(self, name, ploneFile)
|
||||
f.close()
|
||||
elif type(v) in sequenceTypes:
|
||||
# It should be a 2-tuple or 3-tuple
|
||||
fileName = None
|
||||
mimeType = None
|
||||
if len(v) == 2:
|
||||
fileName, fileContent = v
|
||||
elif len(v) == 3:
|
||||
fileName, fileContent, mimeType = v
|
||||
else:
|
||||
raise WRONG_FILE_TUPLE
|
||||
if fileName:
|
||||
fileId = 'file.%f' % time.time()
|
||||
ploneFile = ploneFileClass(fileId, fileName, fileContent)
|
||||
ploneFile.filename = fileName
|
||||
if not mimeType:
|
||||
mimeType = mimetypes.guess_type(fileName)[0]
|
||||
ploneFile.content_type = mimeType
|
||||
setattr(self, name, ploneFile)
|
||||
def __setattr__(self, name, v):
|
||||
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
|
||||
appyType = self.o.getAppyType(name)
|
||||
if not appyType and (name != 'title'):
|
||||
raise 'Attribute "%s" does not exist.' % name
|
||||
if appyType and (appyType['type'] == 'File'):
|
||||
self._set_file_attribute(name, v)
|
||||
else:
|
||||
exec "self.o.set%s%s(v)" % (name[0].upper(), name[1:])
|
||||
def __cmp__(self, other):
|
||||
if other:
|
||||
return cmp(self.o, other.o)
|
||||
|
@ -20,6 +75,9 @@ class AbstractWrapper:
|
|||
def get_tool(self):
|
||||
return self.o.getTool()._appy_getWrapper(force=True)
|
||||
tool = property(get_tool)
|
||||
def get_flavour(self):
|
||||
return self.o.getTool().getFlavour(self.o, appy=True)
|
||||
flavour = property(get_flavour)
|
||||
def get_session(self):
|
||||
return self.o.REQUEST.SESSION
|
||||
session = property(get_session)
|
||||
|
@ -61,21 +119,37 @@ class AbstractWrapper:
|
|||
sortedRefField
|
||||
getattr(self.o, sortedRefField).append(obj.UID())
|
||||
|
||||
def create(self, fieldName, **kwargs):
|
||||
'''This method allows to create an object and link it to the current
|
||||
one through reference field named p_fieldName.'''
|
||||
# Determine object id and portal type
|
||||
portalType = self.o.getAppyRefPortalType(fieldName)
|
||||
def create(self, fieldNameOrClass, **kwargs):
|
||||
'''If p_fieldNameOfClass is the name of a field, this method allows to
|
||||
create an object and link it to the current one (self) through
|
||||
reference field named p_fieldName.
|
||||
If p_fieldNameOrClass is a class from the gen-application, it must
|
||||
correspond to a root class and this method allows to create a
|
||||
root object in the application folder.'''
|
||||
isField = isinstance(fieldNameOrClass, basestring)
|
||||
# Determine the portal type of the object to create
|
||||
if isField:
|
||||
fieldName = fieldNameOrClass
|
||||
idPrefix = fieldName
|
||||
portalType = self.o.getAppyRefPortalType(fieldName)
|
||||
else:
|
||||
theClass = fieldNameOrClass
|
||||
idPrefix = theClass.__name__
|
||||
portalType = self.o._appy_getAtType(theClass, self.flavour.o)
|
||||
# Determine object id
|
||||
if kwargs.has_key('id'):
|
||||
objId = kwargs['id']
|
||||
del kwargs['id']
|
||||
else:
|
||||
objId = '%s.%f' % (fieldName, time.time())
|
||||
# Where must I create te object?
|
||||
if hasattr(self, 'folder') and self.folder:
|
||||
folder = self.o
|
||||
objId = '%s.%f' % (idPrefix, time.time())
|
||||
# Where must I create the object?
|
||||
if not isField:
|
||||
folder = self.o.getTool().getAppFolder()
|
||||
else:
|
||||
folder = self.o.getParentNode()
|
||||
if hasattr(self, 'folder') and self.folder:
|
||||
folder = self.o
|
||||
else:
|
||||
folder = self.o.getParentNode()
|
||||
# Create the object
|
||||
folder.invokeFactory(portalType, objId)
|
||||
ploneObj = getattr(folder, objId)
|
||||
|
@ -93,13 +167,15 @@ class AbstractWrapper:
|
|||
pass
|
||||
else:
|
||||
getattr(ploneObj, setterName)(attrValue)
|
||||
# Link the object to this one
|
||||
self.link(fieldName, ploneObj)
|
||||
if isField:
|
||||
# Link the object to this one
|
||||
self.link(fieldName, ploneObj)
|
||||
self.o.reindexObject()
|
||||
# Call custom initialization
|
||||
try:
|
||||
appyObj.onEdit(True) # Call custom initialization
|
||||
appyObj.onEdit(True)
|
||||
except AttributeError:
|
||||
pass
|
||||
self.o.reindexObject()
|
||||
ploneObj.reindexObject()
|
||||
return appyObj
|
||||
|
||||
|
@ -133,4 +209,49 @@ class AbstractWrapper:
|
|||
self.o._v_appy_do = {'doAction': doAction, 'doNotify': doNotify}
|
||||
wfTool.doActionFor(self.o, transitionName, comment=comment)
|
||||
del self.o._v_appy_do
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
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, atFile):
|
||||
'''This constructor is only used by Appy to create a nice File instance
|
||||
from a Plone/Zope corresponding instance (p_atFile). 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['_atFile'] = atFile # Not for you!
|
||||
d['name'] = atFile.filename
|
||||
d['content'] = atFile.data
|
||||
d['mimeType'] = atFile.content_type
|
||||
d['size'] = atFile.size # In bytes
|
||||
|
||||
def __setattr__(self, name, v):
|
||||
d = self.__dict__
|
||||
if name == 'name':
|
||||
self._atFile.filename = v
|
||||
d['name'] = v
|
||||
elif name == 'content':
|
||||
self._atFile.update_data(v, self.mimeType, len(v))
|
||||
d['content'] = v
|
||||
d['size'] = len(v)
|
||||
elif name == 'mimeType':
|
||||
self._atFile.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):
|
||||
'''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 absoulte path name of the dumped file is returned.'''
|
||||
if not filePath:
|
||||
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
|
||||
self.name)
|
||||
f = file(filePath, 'w')
|
||||
f.write(self.content)
|
||||
f.close()
|
||||
return filePath
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -95,7 +95,7 @@ class BufferAction:
|
|||
|
||||
class IfAction(BufferAction):
|
||||
'''Action that determines if we must include the content of the buffer in
|
||||
the result or not.'''
|
||||
the result or not.'''
|
||||
def do(self):
|
||||
if self.exprResult:
|
||||
self.evaluateBuffer()
|
||||
|
@ -122,7 +122,7 @@ class ElseAction(IfAction):
|
|||
|
||||
class ForAction(BufferAction):
|
||||
'''Actions that will include the content of the buffer as many times as
|
||||
specified by the action parameters.'''
|
||||
specified by the action parameters.'''
|
||||
def __init__(self, name, buffer, expr, elem, minus, iter, source, fromExpr):
|
||||
BufferAction.__init__(self, name, buffer, expr, elem, minus, source,
|
||||
fromExpr)
|
||||
|
@ -202,4 +202,28 @@ class NullAction(BufferAction):
|
|||
allows to insert in a buffer arbitrary odt content.'''
|
||||
def do(self):
|
||||
self.evaluateBuffer()
|
||||
|
||||
class VariableAction(BufferAction):
|
||||
'''Action that allows to define a variable somewhere in the template.'''
|
||||
def __init__(self, name, buffer, expr, elem, minus, varName, source,
|
||||
fromExpr):
|
||||
BufferAction.__init__(self, name, buffer, expr, elem, minus, source,
|
||||
fromExpr)
|
||||
self.varName = varName # Name of the variable
|
||||
def do(self):
|
||||
context = self.buffer.env.context
|
||||
# Remember the variable hidden by our variable definition, if any
|
||||
hasHiddenVariable = False
|
||||
if context.has_key(self.varName):
|
||||
hiddenVariable = context[self.varName]
|
||||
hasHiddenVariable = True
|
||||
# Add the variable to the context
|
||||
context[self.varName] = self.exprResult
|
||||
# Evaluate the buffer
|
||||
self.evaluateBuffer()
|
||||
# Restore hidden variable if any
|
||||
if hasHiddenVariable:
|
||||
context[self.varName] = hiddenVariable
|
||||
else:
|
||||
del context[self.varName]
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -21,7 +21,8 @@ import re
|
|||
|
||||
from appy.pod import PodError, XML_SPECIAL_CHARS
|
||||
from appy.pod.elements import *
|
||||
from appy.pod.actions import IfAction, ElseAction, ForAction, NullAction
|
||||
from appy.pod.actions import IfAction, ElseAction, ForAction, VariableAction, \
|
||||
NullAction
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ParsingError(Exception): pass
|
||||
|
@ -62,6 +63,12 @@ ELSE_WITHOUT_IF = 'No previous "if" statement could be found for this "else" ' \
|
|||
ELSE_WITHOUT_NAMED_IF = 'I could not find an "if" statement named "%s".'
|
||||
BAD_FOR_EXPRESSION = 'Bad "for" expression "%s". A "for" expression ' + \
|
||||
FOR_EXPRESSION
|
||||
BAD_VAR_EXPRESSION = 'Bad variable definition "%s". A variable definition ' \
|
||||
'must have the form {name} = {expression}. {name} must be a Python-' \
|
||||
'compliant variable name. {expression} is a Python expression. When ' \
|
||||
'encountering such a statement, pod will define, in the specified part ' \
|
||||
'of the document, a variable {name} whose value will be the evaluated ' \
|
||||
'{expression}.'
|
||||
EVAL_EXPR_ERROR = 'Error while evaluating expression "%s". %s'
|
||||
NULL_ACTION_ERROR = 'There was a problem with this action. Possible causes: ' \
|
||||
'(1) you specified no action (ie "do text") while not ' \
|
||||
|
@ -168,8 +175,9 @@ class FileBuffer(Buffer):
|
|||
# ------------------------------------------------------------------------------
|
||||
class MemoryBuffer(Buffer):
|
||||
actionRex = re.compile('(?:(\w+)\s*\:\s*)?do\s+(\w+)(-)?' \
|
||||
'(?:\s+(for|if|else)\s*(.*))?')
|
||||
'(?:\s+(for|if|else|with)\s*(.*))?')
|
||||
forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)')
|
||||
varRex = re.compile('\s*([\w\-_]+)\s+=\s+(.*)')
|
||||
def __init__(self, env, parent):
|
||||
Buffer.__init__(self, env, parent)
|
||||
self.content = u''
|
||||
|
@ -338,6 +346,13 @@ class MemoryBuffer(Buffer):
|
|||
iter, subExpr = forRes.groups()
|
||||
self.action = ForAction(statementName, self, subExpr, podElem,
|
||||
minus, iter, source, fromClause)
|
||||
elif actionType == 'with':
|
||||
varRes = MemoryBuffer.varRex.match(subExpr.strip())
|
||||
if not varRes:
|
||||
raise ParsingError(BAD_VAR_EXPRESSION % subExpr)
|
||||
varName, subExpr = varRes.groups()
|
||||
self.action = VariableAction(statementName, self, subExpr,
|
||||
podElem, minus, varName, source, fromClause)
|
||||
else: # null action
|
||||
if not fromClause:
|
||||
raise ParsingError(NULL_ACTION_ERROR)
|
||||
|
|
|
@ -48,4 +48,7 @@ class OdfEnvironment(XmlEnvironment):
|
|||
|
||||
class OdfParser(XmlParser):
|
||||
'''XML parser that is specific for parsing ODF files.'''
|
||||
def __init__(self, env=None, caller=None):
|
||||
if not env: env = OdfEnvironment()
|
||||
XmlParser.__init__(self, env, caller)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -285,7 +285,7 @@ class Renderer:
|
|||
except OSError, oe:
|
||||
raise PodError(CANT_WRITE_RESULT % (self.result, oe))
|
||||
except IOError, ie:
|
||||
raise PodError(CANT_WRITE_RESULT % (self.result, oe))
|
||||
raise PodError(CANT_WRITE_RESULT % (self.result, ie))
|
||||
self.result = os.path.abspath(self.result)
|
||||
os.remove(self.result)
|
||||
# Check that temp folder does not exist
|
||||
|
|
3082
pod/test/Tests.rtf
3082
pod/test/Tests.rtf
File diff suppressed because it is too large
Load diff
2
pod/test/contexts/VarStatements.py
Executable file
2
pod/test/contexts/VarStatements.py
Executable file
|
@ -0,0 +1,2 @@
|
|||
var1 = 'VAR1 not overridden'
|
||||
var2 = 'VAR2 not overridden'
|
BIN
pod/test/results/varDef.odt
Normal file
BIN
pod/test/results/varDef.odt
Normal file
Binary file not shown.
BIN
pod/test/templates/VarStatements.odt
Executable file
BIN
pod/test/templates/VarStatements.odt
Executable file
Binary file not shown.
|
@ -46,3 +46,15 @@ class Traceback:
|
|||
get = staticmethod(get)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def getOsTempFolder():
|
||||
tmp = '/tmp'
|
||||
if os.path.exists(tmp) and os.path.isdir(tmp):
|
||||
res = tmp
|
||||
elif os.environ.has_key('TMP'):
|
||||
res = os.environ['TMP']
|
||||
elif os.environ.has_key('TEMP'):
|
||||
res = os.environ['TEMP']
|
||||
else:
|
||||
raise "Sorry, I can't find a temp folder on your machine."
|
||||
return res
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -85,11 +85,12 @@ class XmlParser(ContentHandler, ErrorHandler):
|
|||
'''Basic XML content handler that does things like :
|
||||
- remembering the currently parsed element;
|
||||
- managing namespace declarations.'''
|
||||
def __init__(self, env, caller=None):
|
||||
def __init__(self, env=None, caller=None):
|
||||
'''p_env should be an instance of a class that inherits from
|
||||
XmlEnvironment: it specifies the environment to use for this SAX
|
||||
parser.'''
|
||||
ContentHandler.__init__(self)
|
||||
if not env: env = XmlEnvironment()
|
||||
self.env = env
|
||||
self.env.parser = self
|
||||
self.caller = caller # The class calling this parser
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue