Eradicated Flavour and PodTemplate classes (for the latter, use Pod fields instead); Added a code analyser; Groups can now be slaves in master/slaves relationships; Refs have more params (show a confirmation popup before adding an object, add an object without creation form); Code for Refs has been refactored to comply with the new way to organize Types; Added a WebDAV client library.

This commit is contained in:
Gaetan Delannay 2010-10-14 14:43:56 +02:00
parent 9f4db88bdf
commit 990e16c6e7
47 changed files with 1006 additions and 1297 deletions

107
shared/dav.py Normal file
View file

@ -0,0 +1,107 @@
# ------------------------------------------------------------------------------
import os, re, httplib, sys
from StringIO import StringIO
from mimetypes import guess_type
from base64 import encodestring
# ------------------------------------------------------------------------------
urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I)
# ------------------------------------------------------------------------------
class Resource:
'''Every instance of this class represents some web resource accessible
through WebDAV.'''
def __init__(self, url, username=None, password=None):
self.username = username
self.password = password
self.url = url
# Split the URL into its components
res = urlRex.match(url)
if res:
host, port, uri = res.group(1,2,3)
self.host = host
self.port = port and int(port[1:]) or 80
self.uri = uri or '/'
else: raise 'Wrong URL: %s' % str(url)
def updateHeaders(self, headers):
# Add credentials if present
if not (self.username and self.password): return
if headers.has_key('Authorization'): return
credentials = '%s:%s' % (self.username,self.password)
credentials = credentials.replace('\012','')
headers['Authorization'] = "Basic %s" % encodestring(credentials)
headers['User-Agent'] = 'WebDAV.client'
headers['Host'] = self.host
headers['Connection'] = 'close'
headers['Accept'] = '*/*'
return headers
def sendRequest(self, method, uri, body=None, headers={}):
'''Sends a HTTP request with p_method, for p_uri.'''
conn = httplib.HTTP()
conn.connect(self.host, self.port)
conn.putrequest(method, uri)
# Add HTTP headers
self.updateHeaders(headers)
for n, v in headers.items(): conn.putheader(n, v)
conn.endheaders()
if body: conn.send(body)
ver, code, msg = conn.getreply()
data = conn.getfile().read()
conn.close()
return data
def mkdir(self, name):
'''Creates a folder named p_name in this resource.'''
folderUri = self.uri + '/' + name
#body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \
# '<d:displayname>%s</d:displayname></d:prop></d:set>' \
# '</d:propertyupdate>' % name
return self.sendRequest('MKCOL', folderUri)
def delete(self, name):
'''Deletes a file or a folder (and all contained files if any) named
p_name within this resource.'''
toDeleteUri = self.uri + '/' + name
return self.sendRequest('DELETE', toDeleteUri)
def add(self, content, type='fileName', name=''):
'''Adds a file in this resource. p_type can be:
- "fileName" In this case, p_content is the path to a file on disk
and p_name is ignored;
- "zope" In this case, p_content is an instance of
OFS.Image.File and the name of the file is given in
p_name.
'''
if type == 'fileName':
# p_content is the name of a file on disk
f = file(content, 'rb')
body = f.read()
f.close()
fileName = os.path.basename(content)
fileType, encoding = guess_type(fileName)
elif type == 'zope':
# p_content is a "Zope" file, ie a OFS.Image.File instance
fileName = name
fileType = content.content_type
encoding = None
if isinstance(content.data, basestring):
# The file content is here, in one piece
body = content.data
else:
# There are several parts to this file.
body = ''
data = content.data
while data is not None:
body += data.data
data = data.next
fileUri = self.uri + '/' + fileName
headers = {}
if fileType: headers['Content-Type'] = fileType
if encoding: headers['Content-Encoding'] = encoding
headers['Content-Length'] = str(len(body))
return self.sendRequest('PUT', fileUri, body, headers)
# ------------------------------------------------------------------------------

View file

@ -125,4 +125,153 @@ def normalizeString(s, usage='fileName'):
# ------------------------------------------------------------------------------
typeLetters = {'b': bool, 'i': int, 'j': long, 'f':float, 's':str, 'u':unicode,
'l': list, 'd': dict}
# ------------------------------------------------------------------------------
class CodeAnalysis:
'''This class holds information about some code analysis (line counts) that
spans some folder hierarchy.'''
def __init__(self, name):
self.name = name # Let's give a name for the analysis
self.numberOfFiles = 0 # The total number of analysed files
self.emptyLines = 0 # The number of empty lines within those files
self.commentLines = 0 # The number of comment lines
# A code line is defined as anything that is not an empty or comment
# line.
self.codeLines = 0
def numberOfLines(self):
'''Computes the total number of lines within analysed files.'''
return self.emptyLines + self.commentLines + self.codeLines
def analyseZptFile(self, theFile):
'''Analyses the ZPT file named p_fileName.'''
inDoc = False
for line in theFile:
stripped = line.strip()
# Manage a comment
if not inDoc and (line.find('<tal:comment ') != -1):
inDoc = True
if inDoc:
self.commentLines += 1
if line.find('</tal:comment>') != -1:
inDoc = False
continue
# Manage an empty line
if not stripped:
self.emptyLines += 1
else:
self.codeLines += 1
docSeps = ('"""', "'''")
def isPythonDoc(self, line, start, isStart=False):
'''Returns True if we find, in p_line, the start of a docstring (if
p_start is True) or the end of a docstring (if p_start is False).
p_isStart indicates if p_line is the start of the docstring.'''
if start:
res = line.startswith(self.docSeps[0]) or \
line.startswith(self.docSeps[1])
else:
sepOnly = (line == self.docSeps[0]) or (line == self.docSeps[1])
if sepOnly:
# If the line contains the separator only, is this the start or
# the end of the docstring?
if isStart: res = False
else: res = True
else:
res = line.endswith(self.docSeps[0]) or \
line.endswith(self.docSeps[1])
return res
def analysePythonFile(self, theFile):
'''Analyses the Python file named p_fileName.'''
# Are we in a docstring ?
inDoc = False
for line in theFile:
stripped = line.strip()
# Manage a line that is within a docstring
inDocStart = False
if not inDoc and self.isPythonDoc(stripped, start=True):
inDoc = True
inDocStart = True
if inDoc:
self.commentLines += 1
if self.isPythonDoc(stripped, start=False, isStart=inDocStart):
inDoc = False
continue
# Manage an empty line
if not stripped:
self.emptyLines += 1
continue
# Manage a comment line
if line.startswith('#'):
self.commentLines += 1
continue
# If we are here, we have a code line.
self.codeLines += 1
def analyseFile(self, fileName):
'''Analyses file named p_fileName.'''
self.numberOfFiles += 1
theFile = file(fileName)
if fileName.endswith('.py'):
self.analysePythonFile(theFile)
elif fileName.endswith('.pt'):
self.analyseZptFile(theFile)
theFile.close()
def printReport(self):
'''Returns the analysis report as a string, only if there is at least
one analysed line.'''
lines = self.numberOfLines()
if not lines: return
commentRate = (self.commentLines / float(lines)) * 100.0
blankRate = (self.emptyLines / float(lines)) * 100.0
print '%s: %d files, %d lines (%.0f%% comments, %.0f%% blank)' % \
(self.name, self.numberOfFiles, lines, commentRate, blankRate)
class LinesCounter:
'''Counts and classifies the lines of code within a folder hierarchy.'''
def __init__(self, folderOrModule):
if isinstance(folderOrModule, basestring):
# It is the path of some folder
self.folder = folderOrModule
else:
# It is a Python module
self.folder = os.path.dirname(folderOrModule.__file__)
# These dicts will hold information about analysed files
self.python = {False: CodeAnalysis('Python'),
True: CodeAnalysis('Python (test)')}
self.zpt = {False: CodeAnalysis('ZPT'),
True: CodeAnalysis('ZPT (test)')}
# Are we currently analysing real or test code?
self.inTest = False
def printReport(self):
'''Displays on stdout a small analysis report about self.folder.'''
for zone in (False, True): self.python[zone].printReport()
for zone in (False, True): self.zpt[zone].printReport()
def run(self):
'''Let's start the analysis of self.folder.'''
# The test markers will allow us to know if we are analysing test code
# or real code within a given part of self.folder code hierarchy.
testMarker1 = '%stest%s' % (os.sep, os.sep)
testMarker2 = '%stest' % os.sep
j = os.path.join
for root, folders, files in os.walk(self.folder):
rootName = os.path.basename(root)
if rootName.startswith('.') or \
(rootName in ('tmp', 'temp')):
continue
# Are we in real code or in test code ?
self.inTest = False
if root.endswith(testMarker2) or (root.find(testMarker1) != -1):
self.inTest = True
# Scan the files in this folder
for fileName in files:
if fileName.endswith('.py'):
self.python[self.inTest].analyseFile(j(root, fileName))
elif fileName.endswith('.pt'):
self.zpt[self.inTest].analyseFile(j(root, fileName))
self.printReport()
# ------------------------------------------------------------------------------