pver supporte la gestion des versions maven

This commit is contained in:
Jephté Clain 2016-11-22 15:28:28 +04:00
parent 190519d968
commit 2980d15769
21 changed files with 2599 additions and 550 deletions

View File

@ -217,7 +217,7 @@ Expression Context Objects
The context contains the following attributes and methods:
.. attribute:: default_namespace
The default namespace URI.
.. attribute:: namespaces

View File

@ -1,10 +1,149 @@
import exceptions
from xpath.exceptions import *
import xpath.exceptions
import xpath.expr
import xpath.parser
import xpath.yappsrt
from _xpath import api, XPathContext, XPath
from exceptions import *
__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath']
__all__.extend((x for x in dir(xpath.exceptions) if not x.startswith('_')))
__all__ = ['find', 'findnode', 'findvalue', 'findvalues', 'XPathContext', 'XPath']
__all__.extend((x for x in dir(exceptions) if not x.startswith('_')))
def api(f):
"""Decorator for functions and methods that are part of the external
module API and that can throw XPathError exceptions.
The call stack for these exceptions can be very large, and not very
interesting to the user. This decorator rethrows XPathErrors to
trim the stack.
"""
def api_function(*args, **kwargs):
try:
return f(*args, **kwargs)
except XPathError, e:
raise e
api_function.__name__ = f.__name__
api_function.__doc__ = f.__doc__
return api_function
class XPathContext(object):
def __init__(self, document=None, **kwargs):
self.default_namespace = None
self.namespaces = {}
self.variables = {}
if document is not None:
if document.nodeType != document.DOCUMENT_NODE:
document = document.ownerDocument
if document.documentElement is not None:
attrs = document.documentElement.attributes
for attr in (attrs.item(i) for i in xrange(attrs.length)):
if attr.name == 'xmlns':
self.default_namespace = attr.value
elif attr.name.startswith('xmlns:'):
self.namespaces[attr.name[6:]] = attr.value
self.update(**kwargs)
def clone(self):
dup = XPathContext()
dup.default_namespace = self.default_namespace
dup.namespaces.update(self.namespaces)
dup.variables.update(self.variables)
return dup
def update(self, default_namespace=None, namespaces=None,
variables=None, **kwargs):
if default_namespace is not None:
self.default_namespace = default_namespace
if namespaces is not None:
self.namespaces = namespaces
if variables is not None:
self.variables = variables
self.variables.update(kwargs)
@api
def find(self, expr, node, **kwargs):
return xpath.find(expr, node, context=self, **kwargs)
@api
def findnode(self, expr, node, **kwargs):
return xpath.findnode(expr, node, context=self, **kwargs)
@api
def findvalue(self, expr, node, **kwargs):
return xpath.findvalue(expr, node, context=self, **kwargs)
@api
def findvalues(self, expr, node, **kwargs):
return xpath.findvalues(expr, node, context=self, **kwargs)
class XPath():
_max_cache = 100
_cache = {}
def __init__(self, expr):
"""Init docs.
"""
try:
parser = xpath.parser.XPath(xpath.parser.XPathScanner(str(expr)))
self.expr = parser.XPath()
except xpath.yappsrt.SyntaxError, e:
raise XPathParseError(str(expr), e.pos, e.msg)
@classmethod
def get(cls, s):
if isinstance(s, cls):
return s
try:
return cls._cache[s]
except KeyError:
if len(cls._cache) > cls._max_cache:
cls._cache.clear()
expr = cls(s)
cls._cache[s] = expr
return expr
@api
def find(self, node, context=None, **kwargs):
if context is None:
context = XPathContext(node, **kwargs)
elif kwargs:
context = context.clone()
context.update(**kwargs)
return self.expr.evaluate(node, 1, 1, context)
@api
def findnode(self, node, context=None, **kwargs):
result = self.find(node, context, **kwargs)
if not xpath.expr.nodesetp(result):
raise XPathTypeError("expression is not a node-set")
if len(result) == 0:
return None
return result[0]
@api
def findvalue(self, node, context=None, **kwargs):
result = self.find(node, context, **kwargs)
if xpath.expr.nodesetp(result):
if len(result) == 0:
return None
result = xpath.expr.string(result)
return result
@api
def findvalues(self, node, context=None, **kwargs):
result = self.find(node, context, **kwargs)
if not xpath.expr.nodesetp(result):
raise XPathTypeError("expression is not a node-set")
return [xpath.expr.string_value(x) for x in result]
def __repr__(self):
return '%s.%s(%s)' % (self.__class__.__module__,
self.__class__.__name__,
repr(str(self.expr)))
def __str__(self):
return str(self.expr)
@api
def find(expr, node, **kwargs):

View File

@ -15,7 +15,7 @@ class XPathParseError(XPathError):
XPathError.__init__(self)
self.expr = expr
self.pos = pos
self.message = message
self.err = message
def __str__(self):
return ("Syntax error:\n" +

View File

@ -6,7 +6,8 @@ import re
import xml.dom
import weakref
from exceptions import *
from xpath.exceptions import *
import xpath
#
@ -21,6 +22,8 @@ def string_value(node):
for n in axes['descendant'](node):
if n.nodeType == n.TEXT_NODE:
s += n.data
elif n.nodeType == n.CDATA_SECTION_NODE:
s += n.nodeValue
return s
elif node.nodeType == node.ATTRIBUTE_NODE:
@ -31,9 +34,12 @@ def string_value(node):
node.nodeType == node.TEXT_NODE):
return node.data
elif node.nodeType == node.CDATA_SECTION_NODE:
return node.nodeValue
def document_order(node):
"""Compute a document order value for the node.
cmp(document_order(a), document_order(b)) will return -1, 0, or 1 if
a is before, identical to, or after b in the document respectively.
@ -100,10 +106,10 @@ def string(v):
return u'Infinity'
elif v == float('-inf'):
return u'-Infinity'
elif int(v) == v and v <= 0xffffffff:
v = int(v)
elif str(v) == 'nan':
return u'NaN'
elif int(v) == v and v <= 0xffffffff:
v = int(v)
return unicode(v)
elif booleanp(v):
return u'true' if v else u'false'
@ -747,7 +753,7 @@ class PathExpr(Expr):
class PredicateList(Expr):
"""A list of predicates.
Predicates are handled as an expression wrapping the expression
filtered by the predicates.
@ -883,7 +889,8 @@ class CommentTest(object):
class TextTest(object):
def match(self, node, axis, context):
return node.nodeType == node.TEXT_NODE
return (node.nodeType == node.TEXT_NODE or
node.nodeType == node.CDATA_SECTION_NODE)
def __str__(self):
return 'text()'

View File

@ -1,252 +0,0 @@
import expr as X
from yappsrt import *
%%
parser XPath:
option: 'no-support-module'
ignore: r'\s+'
token END: r'$'
token FORWARD_AXIS_NAME:
r'child|descendant-or-self|attribute|self|descendant|following-sibling|following|namespace'
token REVERSE_AXIS_NAME:
r'parent|preceding-sibling|preceding|ancestor-or-self|ancestor'
# Dire hack here, since yapps2 has only one token of lookahead: NCNAME
# does not match when followed by a open paren.
token NCNAME: r'[a-zA-Z_][a-zA-Z0-9_\-\.\w]*(?!\()'
token FUNCNAME: r'[a-zA-Z_][a-zA-Z0-9_\-\.\w]*'
token DQUOTE: r'\"(?:[^\"])*\"'
token SQUOTE: r"\'(?:[^\'])*\'"
token NUMBER: r'((\.[0-9]+)|([0-9]+(\.[0-9]*)?))([eE][\+\-]?[0-9]+)?'
token EQ_COMP: r'\!?\='
token REL_COMP: r'[\<\>]\=?'
token ADD_COMP: r'[\+\-]'
token MUL_COMP: r'\*|div|mod'
rule XPath:
Expr END {{ return Expr }}
rule Expr:
OrExpr {{ return OrExpr }}
rule OrExpr:
AndExpr {{ Expr = AndExpr }}
(
r'or' AndExpr
{{ Expr = X.OrExpr('or', Expr, AndExpr) }}
)* {{ return Expr }}
rule AndExpr:
EqualityExpr {{ Expr = EqualityExpr }}
(
r'and' EqualityExpr
{{ Expr = X.AndExpr('and', Expr, EqualityExpr) }}
)* {{ return Expr }}
rule EqualityExpr:
RelationalExpr {{ Expr = RelationalExpr }}
(
EQ_COMP
RelationalExpr
{{ Expr = X.EqualityExpr(EQ_COMP, Expr, RelationalExpr) }}
)* {{ return Expr }}
rule RelationalExpr:
AdditiveExpr {{ Expr = AdditiveExpr }}
(
REL_COMP
AdditiveExpr
{{ Expr = X.EqualityExpr(REL_COMP, Expr, AdditiveExpr) }}
)* {{ return Expr }}
rule AdditiveExpr:
MultiplicativeExpr {{ Expr = MultiplicativeExpr }}
(
ADD_COMP
MultiplicativeExpr
{{ Expr = X.ArithmeticalExpr(ADD_COMP, Expr, MultiplicativeExpr) }}
)* {{ return Expr }}
rule MultiplicativeExpr:
UnionExpr {{ Expr = UnionExpr }}
(
MUL_COMP
UnionExpr
{{ Expr = X.ArithmeticalExpr(MUL_COMP, Expr, UnionExpr) }}
)* {{ return Expr }}
rule UnionExpr:
UnaryExpr {{ Expr = UnaryExpr }}
(
'\|' UnaryExpr
{{ Expr = X.UnionExpr('|', Expr, UnaryExpr) }}
)* {{ return Expr }}
rule UnaryExpr:
r'\-' ValueExpr {{ return X.NegationExpr(ValueExpr) }}
| ValueExpr {{ return ValueExpr }}
rule ValueExpr:
PathExpr {{ return PathExpr }}
rule PathExpr:
r'\/' {{ path = None }}
[
RelativePathExpr {{ path = RelativePathExpr }}
] {{ return X.AbsolutePathExpr(path) }}
| r'\/\/' RelativePathExpr
{{ step = X.AxisStep('descendant-or-self') }}
{{ RelativePathExpr.steps.insert(0, step) }}
{{ return X.AbsolutePathExpr(RelativePathExpr) }}
| RelativePathExpr {{ return RelativePathExpr }}
rule RelativePathExpr:
StepExpr {{ steps = [StepExpr] }}
(
(
r'\/'
| r'\/\/'
{{ steps.append(X.AxisStep('descendant-or-self')) }}
)
StepExpr {{ steps.append(StepExpr) }}
)*
{{ return X.PathExpr(steps) }}
rule StepExpr:
AxisStep {{ return AxisStep }}
| FilterExpr {{ return FilterExpr }}
rule AxisStep:
(
ForwardStep {{ step = ForwardStep }}
| ReverseStep {{ step = ReverseStep }}
) {{ expr = X.AxisStep(*step) }}
[
PredicateList
{{ expr = X.PredicateList(expr, PredicateList, step[0]) }}
]
{{ return expr }}
rule ForwardStep:
ForwardAxis NodeTest {{ return [ForwardAxis, NodeTest] }}
| AbbrevForwardStep {{ return AbbrevForwardStep }}
rule ForwardAxis:
FORWARD_AXIS_NAME r'::' {{ return FORWARD_AXIS_NAME }}
rule AbbrevForwardStep:
{{ axis = 'child' }}
[
r'@' {{ axis = 'attribute' }}
]
NodeTest {{ return [axis, NodeTest] }}
rule ReverseStep:
ReverseAxis NodeTest {{ return [ReverseAxis, NodeTest] }}
| AbbrevReverseStep {{ return AbbrevReverseStep }}
rule ReverseAxis:
REVERSE_AXIS_NAME r'::' {{ return REVERSE_AXIS_NAME }}
rule AbbrevReverseStep:
r'\.\.' {{ return ['parent', None] }}
rule NodeTest:
KindTest {{ return KindTest }}
| NameTest {{ return NameTest }}
rule NameTest:
# We also support the XPath 2.0 <name>:*.
{{ prefix = None }}
WildcardOrNCName {{ localpart = WildcardOrNCName }}
[
r':' WildcardOrNCName {{ prefix = localpart }}
{{ localpart = WildcardOrNCName }}
]
{{ return X.NameTest(prefix, localpart) }}
rule WildcardOrNCName:
r'\*' {{ return '*' }}
| NCNAME {{ return NCNAME }}
rule FilterExpr:
PrimaryExpr
[
PredicateList
{{ PrimaryExpr = X.PredicateList(PrimaryExpr,PredicateList) }}
] {{ return PrimaryExpr }}
rule PredicateList:
Predicate {{ predicates = [Predicate] }}
(
Predicate {{ predicates.append(Predicate) }}
)* {{ return predicates }}
rule Predicate:
r'\[' Expr r'\]' {{ return Expr }}
rule PrimaryExpr:
Literal {{ return X.LiteralExpr(Literal) }}
| VariableReference {{ return VariableReference }}
| r'\(' Expr r'\)' {{ return Expr }}
| ContextItemExpr {{ return ContextItemExpr }}
| FunctionCall {{ return FunctionCall }}
rule VariableReference:
r'\$' QName
{{ return X.VariableReference(*QName) }}
rule ContextItemExpr:
r'\.' {{ return X.AxisStep('self') }}
rule FunctionCall:
FUNCNAME r'\(' {{ args = [] }}
[
Expr {{ args.append(Expr) }}
(
r'\,' Expr {{ args.append(Expr) }}
)*
] r'\)' {{ return X.Function(FUNCNAME, args) }}
rule KindTest:
PITest {{ return PITest }}
| CommentTest {{ return CommentTest }}
| TextTest {{ return TextTest }}
| AnyKindTest {{ return AnyKindTest }}
rule PITest:
r'processing-instruction' {{ name = None }}
r'\(' [
NCNAME {{ name = NCNAME }}
| StringLiteral {{ name = StringLiteral }}
] r'\)' {{ return X.PITest(name) }}
rule CommentTest:
r'comment' r'\(' r'\)' {{ return X.CommentTest() }}
rule TextTest:
r'text' r'\(' r'\)' {{ return X.TextTest() }}
rule AnyKindTest:
r'node' r'\(' r'\)' {{ return X.AnyKindTest() }}
rule Literal:
NumericLiteral {{ return NumericLiteral }}
| StringLiteral {{ return StringLiteral }}
rule NumericLiteral:
NUMBER {{ return float(NUMBER) }}
rule StringLiteral:
DQUOTE {{ return DQUOTE[1:-1] }}
| SQUOTE {{ return SQUOTE[1:-1] }}
rule QName:
NCNAME {{ name = NCNAME }}
[
r'\:' NCNAME {{ return (name, NCNAME) }}
] {{ return (None, name) }}

View File

@ -1,5 +1,5 @@
import expr as X
from yappsrt import *
import xpath.expr as X
from xpath.yappsrt import *
from string import *
@ -8,10 +8,10 @@ import re
class XPathScanner(Scanner):
patterns = [
("r'\\:'", re.compile('\\:')),
("r'node'", re.compile('node')),
("r'text'", re.compile('text')),
("r'comment'", re.compile('comment')),
("r'processing-instruction'", re.compile('processing-instruction')),
("r'node\\s*\\('", re.compile('node\\s*\\(')),
("r'text\\s*\\('", re.compile('text\\s*\\(')),
("r'comment\\s*\\('", re.compile('comment\\s*\\(')),
("r'processing-instruction\\s*\\('", re.compile('processing-instruction\\s*\\(')),
("r'\\,'", re.compile('\\,')),
("r'\\.'", re.compile('\\.')),
("r'\\$'", re.compile('\\$')),
@ -121,7 +121,7 @@ class XPath(Parser):
return Expr
def UnaryExpr(self):
_token_ = self._peek("r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME')
_token_ = self._peek("r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ == "r'\\-'":
self._scan("r'\\-'")
ValueExpr = self.ValueExpr()
@ -135,11 +135,11 @@ class XPath(Parser):
return PathExpr
def PathExpr(self):
_token_ = self._peek("r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME')
_token_ = self._peek("r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ == "r'\\/'":
self._scan("r'\\/'")
path = None
if self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME', "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") not in ["'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'"]:
if self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME', "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") not in ["'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'"]:
RelativePathExpr = self.RelativePathExpr()
path = RelativePathExpr
return X.AbsolutePathExpr(path)
@ -168,7 +168,7 @@ class XPath(Parser):
return X.PathExpr(steps)
def StepExpr(self):
_token_ = self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME')
_token_ = self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ not in ["r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE']:
AxisStep = self.AxisStep()
return AxisStep
@ -177,7 +177,7 @@ class XPath(Parser):
return FilterExpr
def AxisStep(self):
_token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME')
_token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ not in ['REVERSE_AXIS_NAME', "r'\\.\\.'"]:
ForwardStep = self.ForwardStep()
step = ForwardStep
@ -191,7 +191,7 @@ class XPath(Parser):
return expr
def ForwardStep(self):
_token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME')
_token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ == 'FORWARD_AXIS_NAME':
ForwardAxis = self.ForwardAxis()
NodeTest = self.NodeTest()
@ -207,7 +207,7 @@ class XPath(Parser):
def AbbrevForwardStep(self):
axis = 'child'
if self._peek("r'@'", "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') == "r'@'":
if self._peek("r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') == "r'@'":
self._scan("r'@'")
axis = 'attribute'
NodeTest = self.NodeTest()
@ -233,7 +233,7 @@ class XPath(Parser):
return ['parent', None]
def NodeTest(self):
_token_ = self._peek("r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME')
_token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ not in ["r'\\*'", 'NCNAME']:
KindTest = self.KindTest()
return KindTest
@ -315,7 +315,7 @@ class XPath(Parser):
FUNCNAME = self._scan('FUNCNAME')
self._scan("r'\\('")
args = []
if self._peek("r'\\,'", "r'\\)'", "r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') not in ["r'\\,'", "r'\\)'"]:
if self._peek("r'\\,'", "r'\\)'", "r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') not in ["r'\\,'", "r'\\)'"]:
Expr = self.Expr()
args.append(Expr)
while self._peek("r'\\,'", "r'\\)'") == "r'\\,'":
@ -326,24 +326,23 @@ class XPath(Parser):
return X.Function(FUNCNAME, args)
def KindTest(self):
_token_ = self._peek("r'processing-instruction'", "r'comment'", "r'text'", "r'node'")
if _token_ == "r'processing-instruction'":
_token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('")
if _token_ == "r'processing-instruction\\s*\\('":
PITest = self.PITest()
return PITest
elif _token_ == "r'comment'":
elif _token_ == "r'comment\\s*\\('":
CommentTest = self.CommentTest()
return CommentTest
elif _token_ == "r'text'":
elif _token_ == "r'text\\s*\\('":
TextTest = self.TextTest()
return TextTest
else:# == "r'node'"
else:# == "r'node\\s*\\('"
AnyKindTest = self.AnyKindTest()
return AnyKindTest
def PITest(self):
self._scan("r'processing-instruction'")
self._scan("r'processing-instruction\\s*\\('")
name = None
self._scan("r'\\('")
if self._peek('NCNAME', "r'\\)'", 'DQUOTE', 'SQUOTE') != "r'\\)'":
_token_ = self._peek('NCNAME', 'DQUOTE', 'SQUOTE')
if _token_ == 'NCNAME':
@ -356,20 +355,17 @@ class XPath(Parser):
return X.PITest(name)
def CommentTest(self):
self._scan("r'comment'")
self._scan("r'\\('")
self._scan("r'comment\\s*\\('")
self._scan("r'\\)'")
return X.CommentTest()
def TextTest(self):
self._scan("r'text'")
self._scan("r'\\('")
self._scan("r'text\\s*\\('")
self._scan("r'\\)'")
return X.TextTest()
def AnyKindTest(self):
self._scan("r'node'")
self._scan("r'\\('")
self._scan("r'node\\s*\\('")
self._scan("r'\\)'")
return X.AnyKindTest()

View File

@ -51,14 +51,14 @@ class Scanner:
raise NotImplementedError("Unimplemented: restriction set changed")
return self.tokens[i]
raise NoMoreTokens()
def __repr__(self):
"""Print the last 10 tokens that have been scanned in"""
output = ''
for t in self.tokens[-10:]:
output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3]))
return output
def scan(self, restrict):
"""Should scan another token and add it to the list, self.tokens,
and add the restriction to self.restrictions"""
@ -77,7 +77,7 @@ class Scanner:
# We got a match that's better than the previous one
best_pat = p
best_match = len(m.group(0))
# If we didn't find anything, raise an error
if best_pat == '(error)' and best_match < 0:
msg = "Bad Token"
@ -105,13 +105,13 @@ class Parser:
def __init__(self, scanner):
self._scanner = scanner
self._pos = 0
def _peek(self, *types):
"""Returns the token type for lookahead; if there are any args
then the list of args is the set of token types to allow"""
tok = self._scanner.token(self._pos, types)
return tok[2]
def _scan(self, type):
"""Returns the matched text, and moves to the next token"""
tok = self._scanner.token(self._pos, [type])

View File

@ -14,6 +14,10 @@ rm -f .nutools-devel
# supprimer fichiers de développement
rm -rf lib/pyulib/{build,devel,migrate,test}
# compiler les modules python de support
estep "Compilation des modules python"
python -m compileall lib/ulib/support/python
# liens pour les scripts python
for i in plver plbck uencdetect urandomize umail uxpath wofixsql; do
ln -s lib/pywrapper "$i"
@ -21,6 +25,7 @@ done
ln -s lib/ulib/support/cgiupload.py
ln -s lib/ulib/support/cgiparams.py
ln -s lib/ulib/support/cgilsxml.py
ln -s lib/ulib/support/xpathtool.py
# liens pour les scripts shell
for i in cg cgs; do

View File

@ -3,8 +3,9 @@
##@cooked nocomments
##@include vcs
##@include semver
##@include xmlsupport
uprovide ptools
urequire vcs semver
urequire vcs semver xmlsupport
function is_any_branch() {
local branch="$1"; shift
@ -99,43 +100,93 @@ function list_feature_branches() {
################################################################################
# Outils de haut niveau
function __pom_get_version() {
# obtenir la version dans le pom $1(=pom.xml)
local pom="${1:-pom.xml}"
awk <"$pom" '/^[ \t]*<version>/ {
sub(/^.*<version>/, "")
sub(/<\/version>.*$/, "")
print
exit
}'
__pver_perror() {
local r="$1"; shift
[ $# -gt 0 ] || set "Une erreur s'est produite"
local m=$r
[ $m -gt $# ] && m=$#
[ $m -gt 0 ] && eerror "${!m}"
return $r
}
function __pom_set_version() {
# modifier la version du le fichier $1(=pom.xml) à la valeur
# $2(=1.0.0-SNAPSHOT)
__pver_unless() {
local r m
if [[ "$1" == -* ]]; then
[ "${@:1:2}" ] 2>/dev/null; r=$?
shift; shift
else
[ "${@:1:3}" ] 2>/dev/null; r=$?
shift; shift; shift
fi
__pver_perror $r "$@"
}
function __pver_get_prel_version() {
# retourner la version correspondant à la branche courante si c'est une
# branche de release. retourner 1 si la branche courante n'est pas une
# branche de release, 2 si on n'est pas dans un dépôt git
local branch
branch="$(git_get_branch)" || return 2
if [[ "$branch" == release-* ]]; then
echo "${branch#release-}"
return 0
else
return 1
fi
}
function __pver_pom_get_vpath() {
# pour le fichier $1(=pom.xml), retourner le chemin dans lequel se trouve la
# version du projet
local pom="${1:-pom.xml}"
if xpathtool -tf "$pom" /project/version 2>/dev/null; then
echo /project/version
elif xpathtool -tf "$pom" /project/parent/version 2>/dev/null; then
echo /project/parent/version
else
echo /project/version
fi
}
function __pver_pom_get_version() {
# obtenir la version dans le pom $1(=pom.xml) à partir du chemin $2, qui est
# déterminé automatiquement s'il n'est pas spécifié.
local pom="${1:-pom.xml}"
local vpath="$2"
local version
if [ -n "$vpath" ]; then
xpathtool -f "$pom" -g "$vpath"
return
fi
version="$(xpathtool -f "$pom" -g /project/version 2>/dev/null)"
if [ -n "$version" ]; then
echo "$version"
return
fi
version="$(xpathtool -f "$pom" -g /project/parent/version 2>/dev/null)"
if [ -n "$version" ]; then
echo "$version"
return
fi
}
function __pver_pom_set_version() {
# modifier la version dans le pom $1(=pom.xml) à la valeur
# $2(=1.0.0-SNAPSHOT) en utilisant le chemin xpath $3, qui est déterminé
# automatiquement s'il n'est pas spécifié.
# retourner 0 si la version a été mise à jour dans le chemin /project/version
# retourner 1 si la version a été mise à jour dans le chemin /project/parent/version
# retourner 2 si la version a été mise à jour dans un autre chemin
# retourner 3 si une erreur s'est produite
local pom="${1:-pom.xml}"
local version="${2:-1.0.0-SNAPSHOT}"
local tmpfile; ac_set_tmpfile tmpfile
awk <"$pom" >"$tmpfile" '
BEGIN {
version = '"$(qawk "$version")"'
found = 0
}
!found && $0 ~ /^[ \t]*<version>/ {
prefix = "<version>"
if (match($0, /^.*<version>/)) {
prefix = substr($0, RSTART, RLENGTH)
}
suffix = "</version>"
if (match($0, /<\/version>.*$/)) {
suffix = substr($0, RSTART, RLENGTH)
}
print prefix version suffix
found = 1
next
}
{ print }'
cat "$tmpfile" >"$pom"
ac_clean "$tmpfile"
local vpath="$3"
[ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$pom")"
xpathtool -f "$pom" -s "$vpath" "$version" || return 3
case "$vpath" in
/project/version) return 0;;
/project/parent/version) return 1;;
*) return 2;;
esac
}
function pver() {
@ -145,92 +196,170 @@ function pver() {
local -a args
local action=auto
local source=auto
local file=
local git=
local version=
local allow_empty=
local convert=auto
local operator=
local oversion=
local setversion=
local incversion=
local setprelease=
local setalpha=
local setbeta=
local setrc=
local setrelease=
local setmetadata= addmetadata=
local vcsmetadata=
local file git version operator oversion
local setversion incversion
local setpr setprelease setsnapshot setalpha setbeta setrc setrelease
local setmd resetmetadata setmetadata addmetadata vcsmetadata
local vpath setmapfile mapfile allow_empty convert=auto maven_update
parse_opts "${PRETTYOPTS[@]}" \
--help '$exit_with pver_display_help' \
-f:,--file: '$set@ file; source=file' \
-e:,--maven:,--pom: '$set@ file; source=pom' \
-F:,--file-string: '$set@ file; source=file-string' \
-g:,--git-string: '$set@ git; source=git-string' \
-s:,--string: '$set@ version; source=string' \
-w:,--w:,--fw:,--auto-file: '$set@ file; source=auto-file' \
--sw:,--auto-string: '$set@ file; source=auto-string' \
--gw:,--auto-git-string: '$set@ git; source=auto-git-string' \
-e:,--e:,--pom:,--maven: '$set@ file; source=pom' \
-E:,--E:,--pom-string:,--maven-string: '$set@ file; source=pom-string' \
-f:,--f:,--file: '$set@ file; source=file' \
-F:,--F:,--file-string: '$set@ file; source=file-string' \
-g:,--g:,--git-file-string:,--git-string: '$set@ git; source=git-file-string' \
-G:,--G:,--git-pom-string: '$set@ git; source=git-pom-string' \
--git-prel-string source=git-prel-string \
-s:,--s:,--string: '$set@ version; source=string' \
--show action=show \
--allow-empty allow_empty=1 \
--show-source action=show-source \
--check action=check \
--convert convert=1 \
--no-convert convert= \
--eq: '$action=compare; operator=eq; set@ oversion' \
--ne: '$action=compare; operator=ne; set@ oversion' \
--lt: '$action=compare; operator=lt; set@ oversion' \
--le: '$action=compare; operator=le; set@ oversion' \
--gt: '$action=compare; operator=gt; set@ oversion' \
--ge: '$action=compare; operator=ge; set@ oversion' \
-v:,--set-version: '$action=update; set@ setversion; incversion=' \
--prel '$action=update; setversion=prel; incversion=' \
-u,--update '$action=update; [ -z "$incversion" ] && incversion=auto' \
--menu '$action=update; incversion=menu' \
-v:,--set-version: '$action=update; set@ setversion; incversion=' \
--prel '$action=update; setversion=prel; incversion=' \
-x,--major '$action=update; incversion=major' \
-z,--minor '$action=update; incversion=minor' \
-p,--patchlevel '$action=update; incversion=patchlevel' \
-l:,--prelease:,--prerelease: '$action=update; set@ setprelease; [ -z "$setprelease" ] && { setalpha=; setbeta=; setrc=; setrelease=1; }' \
-a,--alpha '$action=update; setalpha=1; setbeta=; setrc=; setrelease=' \
-b,--beta '$action=update; setalpha=; setbeta=1; setrc=; setrelease=' \
-r,--rc '$action=update; setalpha=; setbeta=; setrc=1; setrelease=' \
-R,--release,--final '$action=update; setalpha=; setbeta=; setrc=; setrelease=1' \
-m:,--metadata:,--set-metadata: '$action=update; set@ setmetadata' \
--add-metadata: '$action=update; set@ addmetadata' \
-M,--vcs-metadata '$action=update; vcsmetadata=1' \
-k,--keep '$action=update; incversion=' \
-l:,--prelease:,--prerelease: '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=; setrc=; setrelease=; set@ setprelease; [ -z "$setprelease" ] && setrelease=1' \
-S,--snapshot '$action=update; setpr=1; setsnapshot=1; setalpha=; setbeta=; setrc=; setrelease=' \
-a,--alpha '$action=update; setpr=1; setsnapshot=; setalpha=1; setbeta=; setrc=; setrelease=' \
-b,--beta '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=1; setrc=; setrelease=' \
-r,--rc '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=; setrc=1; setrelease=' \
-R,--release,--final '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=; setrc=; setrelease=1' \
-m:,--metadata:,--set-metadata: '$action=update; setmd=1; resetmetadata=1; set@ setmetadata' \
--add-metadata: '$action=update; setmd=1; set@ addmetadata' \
-M,--vcs-metadata '$action=update; setmd=1; vcsmetadata=1' \
--vpath: vpath= \
--map: '$setmapfile=1; set@ mapfile' \
--allow-empty allow_empty=1 \
--convert convert=1 \
--no-convert convert= \
-t,--maven-update maven_update=1 \
@ args -- "$@" && set -- "${args[@]}" || { eerror "$args"; return 1; }
# Calculer la source
if [ "$source" == auto-file -o "$source" == auto-string ]; then
[ -n "$file" ] || file=.
if [ -d "$file" ]; then
if [ -f "$file/$DEFAULT_POM" ]; then
file="$file/$DEFAULT_POM"
elif [ -f "$file/$DEFAULT_FILE" ]; then
file="$file/$DEFAULT_FILE"
elif [ -f "$file/version.txt" ]; then
file="$file/version.txt"
else
file="$file/$DEFAULT_FILE"
fi
fi
if [ "$source" == auto-file ]; then
if [[ "$file" == *.xml ]]; then source=pom
else source=file
fi
elif [ "$source" == auto-string ]; then
if [[ "$file" == *.xml ]]; then source=pom-string
else source=file-string
fi
fi
edebug "Auto-sélection de $(ppath "$file")"
elif [ "$source" == auto-git-string ]; then
git_check_gitvcs || return 2
splitfsep2 "$git" : branch name
[ -n "$branch" ] || branch=master
if [ "$(git cat-file -t "$branch:$name" 2>/dev/null)" == tree ]; then
if git rev-parse --verify --quiet "$branch:${name:+$name/}$DEFAULT_POM" >/dev/null; then
name="${name:+$name/}$DEFAULT_POM"
elif git rev-parse --verify --quiet "$branch:${name:+$name/}$DEFAULT_FILE" >/dev/null; then
name="${name:+$name/}$DEFAULT_FILE"
elif git rev-parse --verify --quiet "$branch:${name:+$name/}version.txt" >/dev/null; then
name="${name:+$name/}version.txt"
fi
fi
if git rev-parse --verify --quiet "$branch:$name" >/dev/null; then
if [[ "$name" == *.xml ]]; then source=git-pom-string
else source=git-file-string
fi
git="$branch:$name"
edebug "Auto-sélection de $git"
else
eerror "$name: fichier introuvable sur la branche $branch"
return 1
fi
fi
if [ "$source" == auto ]; then
source=file
for i in "$DEFAULT_FILE" version.txt "$DEFAULT_POM"; do
for i in "$DEFAULT_POM" "$DEFAULT_FILE" version.txt; do
if [ -f "$i" ]; then
case "$i" in
"$DEFAULT_POM")
source=pom
file="$i"
break
;;
*)
source=file
file="$i"
break
;;
"$DEFAULT_POM") source=pom; file="$i"; break;;
*) source=file; file="$i"; break;;
esac
fi
done
elif [ "$source" == file ]; then
elif [ "$source" == file -o "$source" == pom ]; then
[ "$action" == auto ] && action=update
fi
[ "$source" == file -a -z "$file" ] && file="$DEFAULT_FILE"
[ "$source" == pom -a -z "$file" ] && file="$DEFAULT_POM"
[ "$action" == auto ] && action=show
if [[ "$source" == auto* ]]; then
eerror "bug: impossible de déterminer la source"
return 1
fi
if [ "$action" == show-source ]; then
echo "$source"
return 0
fi
# Lire la version
local -a maprules
if [ "$source" == file -o "$source" == pom ]; then
local mapdir="$(dirname -- "$file")"
if [ -z "$setmapfile" ]; then
local tmpfile
tmpfile="$mapdir/.pver-map"
[ -f "$tmpfile" ] && mapfile="$tmpfile"
fi
if [ -n "$mapfile" ]; then
__pver_unless -f "$mapfile" "$mapfile: fichier introuvable" || return
mapdir="$(dirname -- "$mapfile")"
array_from_lines maprules "$(<"$mapfile" filter_conf)"
local maprule filespec filevpath filename
local -a filenames
for maprule in "${maprules[@]}"; do
splitpair "$maprule" filespec filevpath
[ "$filevpath" != - ] || continue
array_lsfiles filenames "$mapdir" "$filespec"
[ ${#filenames[*]} -gt 0 ] || continue
file="${filenames[0]}"
if [[ "$file" == *.xml ]]; then source=pom
else source=file
fi
edebug "Sélection de $file comme fichier de référence"
break
done
fi
fi
[ "$source" == pom ] && maven_update=1
if [ "$source" == file ]; then
[ -f "$file" ] && version="$(<"$file")"
elif [ "$source" == pom ]; then
[ -f "$file" ] || {
eerror "$file: fichier introuvable"
return 1
}
version="$(__pom_get_version "$file")"
elif [ "$source" == file-string ]; then
if [ -z "$file" ]; then
file="$DEFAULT_FILE"
@ -239,14 +368,51 @@ function pver() {
fi
[ -f "$file" ] && version="$(<"$file")"
file=
elif [ "$source" == git-string ]; then
elif [ "$source" == git-file-string ]; then
git_check_gitvcs || return 2
splitfsep2 "$git" : branch name
[ -n "$branch" ] || branch=master
[ -n "$name" ] || name="$DEFAULT_FILE"
if git rev-parse --verify --quiet "$branch:$name" >/dev/null; then
version="$(git cat-file blob "$branch:$name" 2>/dev/null)"
fi
elif [ "$source" == pom ]; then
__pver_unless -f "$file" "$file: fichier introuvable" || return
[ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$file")"
version="$(__pver_pom_get_version "$file" "$vpath")"
elif [ "$source" == pom-string ]; then
if [ -z "$file" ]; then file="$DEFAULT_POM"
elif [ -d "$file" ]; then file="$file/$DEFAULT_POM"
fi
__pver_unless -f "$file" "$file: fichier introuvable" || return
[ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$file")"
version="$(__pver_pom_get_version "$file" "$vpath")"
file=
elif [ "$source" == git-pom-string ]; then
git_check_gitvcs || return 2
splitfsep2 "$git" : branch name
[ -n "$branch" ] || branch=master
[ -n "$name" ] || name="$DEFAULT_POM"
if git rev-parse --verify --quiet "$branch:$name" >/dev/null; then
local tmpfile; ac_set_tmpfile tmpfile
git cat-file blob "$branch:$name" >"$tmpfile" 2>/dev/null
[ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$tmpfile")"
version="$(__pver_pom_get_version "$tmpfile" "$vpath")"
ac_clean "$tmpfile"
else
eerror "$name: fichier introuvable sur la branche $branch"
return 1
fi
elif [ "$source" == git-prel-string ]; then
version="$(__pver_get_prel_version)" || return
fi
[ -n "$version" -o -n "$allow_empty" ] || version=0.0.0
# Conversion éventuelle du numéro de version
@ -285,7 +451,7 @@ BEGIN {
version = version ".0"
}
# afficher la version migrée au format semver
if (metadata != "") print version "+" metadata
if (metadata != "") print version "+r" metadata
else print version
### maven, pom.xml
@ -339,7 +505,7 @@ BEGIN {
fi
if [ "$action" == check ]; then
[ -n "$valid" ] || { eerror "Numéro de version invalide: $version"; return 1; }
__pver_unless -n "$valid" "Numéro de version invalide: $version" || return
elif [ "$action" == compare ]; then
psemver_parse "$oversion" o
@ -423,94 +589,100 @@ BEGIN {
elif [ "$action" == update ]; then
[ -z "$version" -a -n "$allow_empty" ] && return 1
[ -n "$valid" ] || { eerror "Numéro de version invalide: $version"; return 1; }
__pver_unless -n "$valid" "Numéro de version invalide: $version" || return
if [ -n "$file" ]; then
if [ -f "$file" ]; then
if isatty; then
estepi "La version actuelle est $version"
fi
isatty && estepi "La version actuelle est $version"
elif [ "$source" == pom ]; then
eerror "$file: fichier introuvable"
return 1
else
if isatty; then
ask_yesno "Le fichier $(ppath "$file") n'existe pas. Faut-il le créer?" O || return 1
fi
elif isatty; then
ask_yesno "Le fichier $(ppath "$file") n'existe pas. Faut-il le créer?" O || return 1
fi
fi
# forcer le numéro de version
if [ -n "$setversion" ]; then
if [ "$setversion" == prel ]; then
local branch; branch="$(git_get_branch)" || return 2
if [[ "$branch" == release-* ]]; then
setversion="${branch#release-}"
else
eerror "$branch: n'est pas une release branch"
return 1
fi
setversion="$(__pver_get_prel_version)"
__pver_perror $? \
"$(git_get_branch): n'est pas une release branch" \
"Dépôt git introuvable" || return
fi
psemver_setversion "$setversion" "" || { eerror "Numéro de version invalide: $setversion"; return 1; }
psemver_setversion "$setversion" "" || {
eerror "Numéro de version invalide: $setversion"
return 1
}
fi
# Calculer metadata
if [ -n "$vcsmetadata" ]; then
resetmetadata=1
setmetadata="$(git rev-parse --short HEAD)" || return 1
fi
# incrémenter les numéros de version
if [ "$incversion" == auto ]; then
if [ -n "$setrelease" -o -n "$setprelease" -o -n "$setmetadata" -o -n "$addmetadata" ]; then
incversion=
else
incversion=menu
if [ -n "$maven_update" -a -n "$incversion" ]; then
# il y a des règles particulières pour maven
if [ -n "$setrelease" -a -z "$prelease" ]; then
# on est déjà en release, faire le traitement normal
maven_update=
elif [ -n "$setsnapshot" -a "$prelease" == SNAPSHOT ]; then
# on est déjà en snapshot, faire le traitement normal, mais garder le préfixe SNAPSHOT
maven_update=
if [ "$incversion" == auto ]; then
[ -n "$setmd" ] && incversion= || incversion=menu
fi
elif [ -z "$prelease" ]; then
# on est en release, passer en snapshot
setsnapshot=1
setrelease=
if [ "$incversion" == auto ]; then
[ -n "$setmd" ] && incversion= || incversion=menu
fi
elif [ "$prelease" == SNAPSHOT ]; then
# on est en snapshot, passer en release
setsnapshot=
setrelease=1
if [ "$incversion" == auto ]; then
[ -n "$setmd" ] && incversion= || incversion=menu
fi
fi
fi
if [ "$incversion" == auto ]; then
[ -n "$setpr" -o -n "$setmd" ] && incversion= || incversion=menu
fi
if [ -z "$incversion" -a -n "$maven_update" ]; then
# on doit incrémenter avec les règles maven, mais aucune des options
# -[xzp] n'est spécifiée
if [ -n "$setrelease" ]; then
incversion=patchlevel
elif [ -n "$setsnapshot" ]; then
if [ "$patchlevel" -eq 0 ]; then
incversion=minor
else
incversion=patchlevel
fi
fi
fi
if [ -n "$setrelease" ]; then setprelease=
elif [ -n "$setsnapshot" ]; then setprelease=SNAPSHOT
fi
if [ "$incversion" == menu ]; then
psemver_copy x; {
psemver_incmajor x
psemver_setprelease "$setprelease" x
if [ -n "$addmetadata" ]; then
[ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" x
psemver_addmetadata "$addmetadata" x
else
psemver_setmetadata "$setmetadata" x
fi
psemver_setvar versionx x
}
psemver_copy z; {
psemver_incminor z
psemver_setprelease "$setprelease" z
if [ -n "$addmetadata" ]; then
[ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" z
psemver_addmetadata "$addmetadata" z
else
psemver_setmetadata "$setmetadata" z
fi
psemver_setvar versionz z
}
psemver_copy p; {
psemver_incpatchlevel p
psemver_setprelease "$setprelease" p
if [ -n "$addmetadata" ]; then
[ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" p
psemver_addmetadata "$addmetadata" p
else
psemver_setmetadata "$setmetadata" p
fi
psemver_setvar versionp p
}
psemver_copy k; {
psemver_setprelease "$setprelease" k
if [ -n "$addmetadata" ]; then
[ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" k
psemver_addmetadata "$addmetadata" k
else
psemver_setmetadata "$setmetadata" k
fi
psemver_setvar versionk k
}
psemver_copy x
psemver_incsetprmd major x "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update"
psemver_setvar versionx x
psemver_copy z
psemver_incsetprmd minor z "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update"
psemver_setvar versionz z
psemver_copy p
psemver_incsetprmd patchlevel p "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update"
psemver_setvar versionp p
psemver_copy k
psemver_incsetprmd "" k "$setprelease" "$resetmetadata" "$setmetadata" "$addmetadata" "$maven_update"
psemver_setvar versionk k
nextvs=(
"$versionx : maj incompatibles de l'API (-x)"
"$versionz : maj compatibles de l'API (-z)"
@ -528,49 +700,74 @@ BEGIN {
*) incversion=;;
esac
fi
local r
if [ -n "$incversion" ]; then
case "$incversion" in
major) psemver_incmajor;;
minor) psemver_incminor;;
patchlevel) psemver_incpatchlevel;;
esac
# Quand on incrémente, réinitialiser la valeur de prérelease et metadata
psemver_setprelease
[ -z "$addmetadata" ] && psemver_setmetadata
fi
# spécifier prerelease
if [ -n "$setrelease" ]; then
psemver_setprelease ""
elif [ -n "$setprelease" ]; then
psemver_setprelease "$setprelease" || { eerror "Identifiant de pre-release invalide: $setprelease"; return 1; }
fi
if [ -n "$setalpha" ]; then
: #XXX
elif [ -n "$setbeta" ]; then
: #XXX
elif [ -n "$setrc" ]; then
: #XXX
fi
# spécifier metadata
if [ -n "$setmetadata" ]; then
psemver_setmetadata "$setmetadata" || { eerror "Identifiant de build invalide: $setmetadata"; return 1; }
fi
if [ -n "$addmetadata" ]; then
psemver_addmetadata "$addmetadata" || { eerror "Identifiant de build invalide: $addmetadata"; return 1; }
psemver_incsetprmd "$incversion" "" "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update"
r=$?
else
psemver_setprmd "" "$setprelease" "$resetmetadata" "$setmetadata" "$addmetadata"
r=$?
fi
case $r in
2) eerror "Identifiant de pre-release invalide: $setprelease"; return 1;;
3) eerror "Identifiant de build invalide: $setmetadata"; return 1;;
4) eerror "Identifiant de build invalide: $addmetadata"; return 1;;
esac
# afficher le résultat final
local -a depfiles
psemver_setvar version
if [ -n "$file" ]; then
case "$source" in
file) echo "$version" >"$file";;
pom) __pom_set_version "$file" "$version";;
esac
if [ "$source" == file ]; then
echo "$version" >"$file"
elif [ "$source" == pom ]; then
__pver_pom_set_version "$file" "$version"
[ $? -eq 3 ] && return 1
fi
if [ ${#maprules[*]} -gt 0 ]; then
# mettre à jour les fichiers mentionnés dans mapfile
local -a donefiles
for maprule in "${maprules[@]}"; do
splitpair "$maprule" filespec filevpath
array_lsfiles filenames "$mapdir" "$filespec"
for file in "${filenames[@]}"; do
file="$(abspath "$file")"
edebug "Examen de $file"
# si c'est un wildcard, ne traiter que si le fichier n'a
# pas encore été traité
case "$filespec" in *\**|*\?*) array_contains donefiles "$file" && break;; esac
if [ "$filevpath" != - ]; then
if [[ "$file" == *.xml ]]; then
edebug "Maj version de $file au chemin XPATH ${filevpath:-par défaut}"
__pver_pom_set_version "$file" "$version" "$filevpath"
case $? in
0|1) :;;
2) array_addu depfiles "$file";;
*) return 1;;
esac
else
edebug "Maj version de $file"
echo "$version" >"$file"
fi
else
edebug "Fichier $file ignoré"
fi
array_addu donefiles "$file"
done
done
fi
fi
if isatty; then
estepn "La nouvelle version est $version"
if [ ${#depfiles[*]} -gt 0 ]; then
local msg="Les fichiers suivants ont été modifiés, et leur version doit être mise à jour:"
for file in "${depfiles[@]}"; do
msg="$msg
$(qvals pver -uzw "$file" -R --menu)"
done
eimportant "$msg"
fi
else
echo "$version"
fi

View File

@ -10,6 +10,7 @@ function __semver_check_prelease() { [ -z "${1//[a-zA-Z0-9.-]/}" ]; }
function __semver_check_metadata() { [ -z "${1//[a-zA-Z0-9.-]/}" ]; }
function semver_parse() {
# args: version majorV minorV patchlevelV preleaseV metadataV validV
local __p_ver="$1"
local __p_ma="${2:-major}" __p_mi="${3:-minor}" __p_pl="${4:-patchlevel}"
local __p_pr="${5:-prelease}" __p_md="${6:-metadata}" __p_va="${7:-valid}"
@ -84,34 +85,79 @@ function semver_parse() {
}
function semver_incmajor() {
setv "$1" $((${!1} + 1))
setv "$2" 0
setv "$3" 0
array_new "$4"
# args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease
if [ -z "$6" ]; then
setv "$1" $((${!1} + 1))
setv "$2" 0
setv "$3" 0
array_new "$4"
elif [ -z "$7" ]; then
# maven snapshot --> release
if [ ${!2} -ne 0 -o ${!3} -ne 0 ]; then
setv "$1" $((${!1} + 1))
setv "$2" 0
setv "$3" 0
fi
array_new "$4"
else
# maven release --> snapshot
setv "$2" $((${!2} + 1))
setv "$3" 0
array_split "$4" "$setprelease" .
fi
}
function semver_incminor() {
setv "$2" $((${!2} + 1))
setv "$3" 0
array_new "$4"
# args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease
if [ -z "$6" ]; then
setv "$2" $((${!2} + 1))
setv "$3" 0
array_new "$4"
elif [ -z "$7" ]; then
# maven snapshot --> release
if [ ${!3} -ne 0 ]; then
setv "$2" $((${!2} + 1))
setv "$3" 0
fi
array_new "$4"
else
# maven release --> snapshot
setv "$2" $((${!2} + 1))
setv "$3" 0
array_split "$4" "$setprelease" .
fi
}
function semver_incpatchlevel() {
setv "$3" $((${!3} + 1))
array_new "$4"
# args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease
if [ -z "$6" ]; then
setv "$3" $((${!3} + 1))
array_new "$4"
elif [ -z "$7" ]; then
# maven snapshot --> release
:
else
# maven release --> snapshot
setv "$3" $((${!3} + 1))
array_split "$4" "$setprelease" .
fi
}
function semver_setversion() {
# args: version majorV minorV patchlevelV preleaseV metadataV
local __sv_ma __sv_mi __sv_svl __sv_svr __sv_md __sv_va
semver_parse "$1" __sv_ma __sv_mi __sv_pl __sv_pr __sv_md __sv_va
[ -n "$__sv_va" ] || return 1
setv "$2" "$__sv_ma"
setv "$3" "$__sv_mi"
setv "$4" "$__sv_pl"
array_copy "$5" __sv_pr
array_copy "$6" __sv_md
return 0
}
function semver_setprelease() {
# args: setprelease majorV minorV patchlevelV preleaseV metadataV
if [ -n "$1" ]; then
__semver_check_prelease "$1" || return 1
array_split "$5" "$1" .
@ -122,6 +168,7 @@ function semver_setprelease() {
}
function semver_compare_prelease() {
# args: prelease1 prelease2
local -a __cp_pr1 __cp_pr2 __cp_len i __cp_v1 __cp_v2
array_copy __cp_pr1 "$1"
array_copy __cp_pr2 "$2"
@ -179,6 +226,7 @@ function semver_compare_prelease() {
}
function semver_setmetadata() {
# args: setmetadata majorV minorV patchlevelV preleaseV metadataV
if [ -n "$1" ]; then
__semver_check_metadata "$1" || return 1
array_split "$6" "$1" .
@ -189,6 +237,7 @@ function semver_setmetadata() {
}
function semver_addmetadata() {
# args: addmetadata majorV minorV patchlevelV preleaseV metadataV
if [ -n "$1" ]; then
__semver_check_metadata "$1" || return 1
local -a __sam_metadata
@ -199,11 +248,13 @@ function semver_addmetadata() {
}
function semver_compare_metadata() {
# args: metadata1 metadata2
# même algo que pour prelease
semver_compare_prelease "$@"
}
function semver_copy() {
# args: majorDV minorDV patchlevelDV preleaseDV metadataDV majorSV minorSV patchlevelSV preleaseSV metadataSV
setv "$1" "${!6}"
setv "$2" "${!7}"
setv "$3" "${!8}"
@ -212,6 +263,7 @@ function semver_copy() {
}
function semver_build() {
# args: majorV minorV patchlevelV preleaseV metadataV
echo_ "${!1}.${!2}.${!3}"
array_isempty "$4" || recho_ "-$(array_join "$4" .)"
array_isempty "$5" || recho_ "+$(array_join "$5" .)"
@ -219,6 +271,7 @@ function semver_build() {
}
function semver_setvar() {
# args: versionV majorV minorV patchlevelV preleaseV metadataV
setv "$1" "$(semver_build "$2" "$3" "$4" "$5" "$6")"
}
@ -228,9 +281,9 @@ function semver_setvar() {
# uniquement un préfixe pour les noms de variable
function psemver_parse() { semver_parse "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata" "${2}valid"; }
function psemver_incmajor() { semver_incmajor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; }
function psemver_incminor() { semver_incminor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; }
function psemver_incpatchlevel() { semver_incpatchlevel "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; }
function psemver_incmajor() { semver_incmajor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${2}metadata" "$2" "$3"; }
function psemver_incminor() { semver_incminor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${2}metadata" "$2" "$3"; }
function psemver_incpatchlevel() { semver_incpatchlevel "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${2}metadata" "$2" "$3"; }
function psemver_setversion() { semver_setversion "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; }
function psemver_setprelease() { semver_setprelease "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; }
function psemver_compare_prelease() { semver_compare_prelease "${1}prelease" "${2}prelease"; }
@ -240,3 +293,32 @@ function psemver_compare_metadata() { semver_compare_metadata "${1}metadata" "${
function psemver_copy() { semver_copy "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; }
function psemver_build() { semver_build "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; }
function psemver_setvar() { semver_setvar "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; }
function psemver_setprmd() {
local setprelease="$2" resetmetadata="$3" setmetadata="$4" addmetadata="$5"
psemver_setprelease "$setprelease" "$1" || return 2
# XXX pas encore implémenté
local setalpha setbeta setrc
if [ -n "$setalpha" ]; then :
elif [ -n "$setbeta" ]; then :
elif [ -n "$setrc" ]; then :
fi
[ -n "$resetmetadata" ] && psemver_setmetadata "" "$1"
if [ -n "$setmetadata" ]; then
psemver_setmetadata "$setmetadata" "$1" || return 3
fi
if [ -n "$addmetadata" ]; then
psemver_addmetadata "$addmetadata" "$1" || return 4
fi
return 0
}
function psemver_incsetprmd() {
local setprelease="$3" resetmetadata="$4" setmetadata="$5" addmetadata="$6" maven_update="$7"
[ -n "$1" ] && "psemver_inc$1" "$2" "$maven_update" "$setprelease"
shift
psemver_setprmd "$@"
}

View File

View File

@ -1,8 +1,11 @@
import expr as E
import parser as P
import yappsrt as Y
from xpath.exceptions import *
import xpath.exceptions
import xpath.expr
import xpath.parser
import xpath.yappsrt
from exceptions import *
__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath']
__all__.extend((x for x in dir(xpath.exceptions) if not x.startswith('_')))
def api(f):
"""Decorator for functions and methods that are part of the external
@ -60,19 +63,19 @@ class XPathContext(object):
@api
def find(self, expr, node, **kwargs):
return XPath.get(expr).find(node, context=self, **kwargs)
return xpath.find(expr, node, context=self, **kwargs)
@api
def findnode(self, expr, node, **kwargs):
return XPath.get(expr).findnode(node, context=self, **kwargs)
return xpath.findnode(expr, node, context=self, **kwargs)
@api
def findvalue(self, expr, node, **kwargs):
return XPath.get(expr).findvalue(node, context=self, **kwargs)
return xpath.findvalue(expr, node, context=self, **kwargs)
@api
def findvalues(self, expr, node, **kwargs):
return XPath.get(expr).findvalues(node, context=self, **kwargs)
return xpath.findvalues(expr, node, context=self, **kwargs)
class XPath():
_max_cache = 100
@ -82,9 +85,9 @@ class XPath():
"""Init docs.
"""
try:
parser = P.XPath(P.XPathScanner(str(expr)))
parser = xpath.parser.XPath(xpath.parser.XPathScanner(str(expr)))
self.expr = parser.XPath()
except Y.SyntaxError, e:
except xpath.yappsrt.SyntaxError, e:
raise XPathParseError(str(expr), e.pos, e.msg)
@classmethod
@ -112,7 +115,7 @@ class XPath():
@api
def findnode(self, node, context=None, **kwargs):
result = self.find(node, context, **kwargs)
if not E.nodesetp(result):
if not xpath.expr.nodesetp(result):
raise XPathTypeError("expression is not a node-set")
if len(result) == 0:
return None
@ -121,18 +124,18 @@ class XPath():
@api
def findvalue(self, node, context=None, **kwargs):
result = self.find(node, context, **kwargs)
if E.nodesetp(result):
if xpath.expr.nodesetp(result):
if len(result) == 0:
return None
result = E.string(result)
result = xpath.expr.string(result)
return result
@api
def findvalues(self, node, context=None, **kwargs):
result = self.find(node, context, **kwargs)
if not E.nodesetp(result):
if not xpath.expr.nodesetp(result):
raise XPathTypeError("expression is not a node-set")
return [E.string_value(x) for x in result]
return [xpath.expr.string_value(x) for x in result]
def __repr__(self):
return '%s.%s(%s)' % (self.__class__.__module__,
@ -141,3 +144,19 @@ class XPath():
def __str__(self):
return str(self.expr)
@api
def find(expr, node, **kwargs):
return XPath.get(expr).find(node, **kwargs)
@api
def findnode(expr, node, **kwargs):
return XPath.get(expr).findnode(node, **kwargs)
@api
def findvalue(expr, node, **kwargs):
return XPath.get(expr).findvalue(node, **kwargs)
@api
def findvalues(expr, node, **kwargs):
return XPath.get(expr).findvalues(node, **kwargs)

View File

@ -0,0 +1,49 @@
class XPathError(Exception):
"""Base exception class used for all XPath exceptions."""
class XPathNotImplementedError(XPathError):
"""Raised when an XPath expression contains a feature of XPath which
has not been implemented.
"""
class XPathParseError(XPathError):
"""Raised when an XPath expression could not be parsed."""
def __init__(self, expr, pos, message):
XPathError.__init__(self)
self.expr = expr
self.pos = pos
self.err = message
def __str__(self):
return ("Syntax error:\n" +
self.expr.replace("\n", " ") + "\n" +
("-" * self.pos) + "^")
class XPathTypeError(XPathError):
"""Raised when an XPath expression is found to contain a type error.
For example, the expression "string()/node()" contains a type error
because the "string()" function does not return a node-set.
"""
class XPathUnknownFunctionError(XPathError):
"""Raised when an XPath expression contains a function that has no
binding in the expression context.
"""
class XPathUnknownPrefixError(XPathError):
"""Raised when an XPath expression contains a QName with a namespace
prefix that has no corresponding namespace declaration in the expression
context.
"""
class XPathUnknownVariableError(XPathError):
"""Raised when an XPath expression contains a variable that has no
binding in the expression context.
"""

View File

@ -0,0 +1,903 @@
from __future__ import division
from itertools import *
import math
import operator
import re
import xml.dom
import weakref
from xpath.exceptions import *
import xpath
#
# Data model functions.
#
def string_value(node):
"""Compute the string-value of a node."""
if (node.nodeType == node.DOCUMENT_NODE or
node.nodeType == node.ELEMENT_NODE):
s = u''
for n in axes['descendant'](node):
if n.nodeType == n.TEXT_NODE:
s += n.data
elif n.nodeType == n.CDATA_SECTION_NODE:
s += n.nodeValue
return s
elif node.nodeType == node.ATTRIBUTE_NODE:
return node.value
elif (node.nodeType == node.PROCESSING_INSTRUCTION_NODE or
node.nodeType == node.COMMENT_NODE or
node.nodeType == node.TEXT_NODE):
return node.data
elif node.nodeType == node.CDATA_SECTION_NODE:
return node.nodeValue
def document_order(node):
"""Compute a document order value for the node.
cmp(document_order(a), document_order(b)) will return -1, 0, or 1 if
a is before, identical to, or after b in the document respectively.
We represent document order as a list of sibling indexes. That is,
the third child of the document node has an order of [2]. The first
child of that node has an order of [2,0].
Attributes have a sibling index of -1 (coming before all children of
their node) and are further ordered by name--e.g., [2,0,-1,'href'].
"""
# Attributes: parent-order + [-1, attribute-name]
if node.nodeType == node.ATTRIBUTE_NODE:
order = document_order(node.ownerElement)
order.extend((-1, node.name))
return order
# The document root (hopefully): []
if node.parentNode is None:
return []
# Determine which child this is of its parent.
sibpos = 0
sib = node
while sib.previousSibling is not None:
sibpos += 1
sib = sib.previousSibling
# Order: parent-order + [sibling-position]
order = document_order(node.parentNode)
order.append(sibpos)
return order
#
# Type functions, operating on the various XPath types.
#
# Internally, we use the following representations:
# nodeset - list of DOM tree nodes in document order
# string - str or unicode
# boolean - bool
# number - int or float
#
def nodeset(v):
"""Convert a value to a nodeset."""
if not nodesetp(v):
raise XPathTypeError, "value is not a node-set"
return v
def nodesetp(v):
"""Return true iff 'v' is a node-set."""
if isinstance(v, list):
return True
def string(v):
"""Convert a value to a string."""
if nodesetp(v):
if not v:
return u''
return string_value(v[0])
elif numberp(v):
if v == float('inf'):
return u'Infinity'
elif v == float('-inf'):
return u'-Infinity'
elif str(v) == 'nan':
return u'NaN'
elif int(v) == v and v <= 0xffffffff:
v = int(v)
return unicode(v)
elif booleanp(v):
return u'true' if v else u'false'
return v
def stringp(v):
"""Return true iff 'v' is a string."""
return isinstance(v, basestring)
def boolean(v):
"""Convert a value to a boolean."""
if nodesetp(v):
return len(v) > 0
elif numberp(v):
if v == 0 or v != v:
return False
return True
elif stringp(v):
return v != ''
return v
def booleanp(v):
"""Return true iff 'v' is a boolean."""
return isinstance(v, bool)
def number(v):
"""Convert a value to a number."""
if nodesetp(v):
v = string(v)
try:
return float(v)
except ValueError:
return float('NaN')
def numberp(v):
"""Return true iff 'v' is a number."""
return (not(isinstance(v, bool)) and
(isinstance(v, int) or isinstance(v, float)))
class Expr(object):
"""Abstract base class for XPath expressions."""
def evaluate(self, node, pos, size, context):
"""Evaluate the expression.
The context node, context position, and context size are passed as
arguments.
Returns an XPath value: a nodeset, string, boolean, or number.
"""
class BinaryOperatorExpr(Expr):
"""Base class for all binary operators."""
def __init__(self, op, left, right):
self.op = op
self.left = left
self.right = right
def evaluate(self, node, pos, size, context):
# Subclasses either override evaluate() or implement operate().
return self.operate(self.left.evaluate(node, pos, size, context),
self.right.evaluate(node, pos, size, context))
def __str__(self):
return '(%s %s %s)' % (self.left, self.op, self.right)
class AndExpr(BinaryOperatorExpr):
"""<x> and <y>"""
def evaluate(self, node, pos, size, context):
# Note that XPath boolean operations short-circuit.
return (boolean(self.left.evaluate(node, pos, size, context) and
boolean(self.right.evaluate(node, pos, size, context))))
class OrExpr(BinaryOperatorExpr):
"""<x> or <y>"""
def evaluate(self, node, pos, size, context):
# Note that XPath boolean operations short-circuit.
return (boolean(self.left.evaluate(node, pos, size, context) or
boolean(self.right.evaluate(node, pos, size, context))))
class EqualityExpr(BinaryOperatorExpr):
"""<x> = <y>, <x> != <y>, etc."""
operators = {
'=' : operator.eq,
'!=' : operator.ne,
'<=' : operator.le,
'<' : operator.lt,
'>=' : operator.ge,
'>' : operator.gt,
}
def operate(self, a, b):
if nodesetp(a):
for node in a:
if self.operate(string_value(node), b):
return True
return False
if nodesetp(b):
for node in b:
if self.operate(a, string_value(node)):
return True
return False
if self.op in ('=', '!='):
if booleanp(a) or booleanp(b):
convert = boolean
elif numberp(a) or numberp(b):
convert = number
else:
convert = string
else:
convert = number
a, b = convert(a), convert(b)
return self.operators[self.op](a, b)
def divop(x, y):
try:
return x / y
except ZeroDivisionError:
if x == 0 and y == 0:
return float('nan')
if x < 0:
return float('-inf')
return float('inf')
class ArithmeticalExpr(BinaryOperatorExpr):
"""<x> + <y>, <x> - <y>, etc."""
# Note that we must use math.fmod for the correct modulo semantics.
operators = {
'+' : operator.add,
'-' : operator.sub,
'*' : operator.mul,
'div' : divop,
'mod' : math.fmod
}
def operate(self, a, b):
return self.operators[self.op](number(a), number(b))
class UnionExpr(BinaryOperatorExpr):
"""<x> | <y>"""
def operate(self, a, b):
if not nodesetp(a) or not nodesetp(b):
raise XPathTypeError("union operand is not a node-set")
# Need to sort the result to preserve document order.
return sorted(set(chain(a, b)), key=document_order)
class NegationExpr(Expr):
"""- <x>"""
def __init__(self, expr):
self.expr = expr
def evaluate(self, node, pos, size, context):
return -number(self.expr.evaluate(node, pos, size, context))
def __str__(self):
return '(-%s)' % self.expr
class LiteralExpr(Expr):
"""Literals--either numbers or strings."""
def __init__(self, literal):
self.literal = literal
def evaluate(self, node, pos, size, context):
return self.literal
def __str__(self):
if stringp(self.literal):
if "'" in self.literal:
return '"%s"' % self.literal
else:
return "'%s'" % self.literal
return string(self.literal)
class VariableReference(Expr):
"""Variable references."""
def __init__(self, prefix, name):
self.prefix = prefix
self.name = name
def evaluate(self, node, pos, size, context):
try:
if self.prefix is not None:
try:
namespaceURI = context.namespaces[self.prefix]
except KeyError:
raise XPathUnknownPrefixError(self.prefix)
return context.variables[(namespaceURI, self.name)]
else:
return context.variables[self.name]
except KeyError:
raise XPathUnknownVariableError(str(self))
def __str__(self):
if self.prefix is None:
return '$%s' % self.name
else:
return '$%s:%s' % (self.prefix, self.name)
class Function(Expr):
"""Functions."""
def __init__(self, name, args):
self.name = name
self.args = args
self.evaluate = getattr(self, 'f_%s' % name.replace('-', '_'), None)
if self.evaluate is None:
raise XPathUnknownFunctionError, 'unknown function "%s()"' % name
if len(self.args) < self.evaluate.minargs:
raise XPathTypeError, 'too few arguments for "%s()"' % name
if (self.evaluate.maxargs is not None and
len(self.args) > self.evaluate.maxargs):
raise XPathTypeError, 'too many arguments for "%s()"' % name
#
# XPath functions are implemented by methods of the Function class.
#
# A method implementing an XPath function is decorated with the function
# decorator, and receives the evaluated function arguments as positional
# parameters.
#
def function(minargs, maxargs, implicit=False, first=False, convert=None):
"""Function decorator.
minargs -- Minimum number of arguments taken by the function.
maxargs -- Maximum number of arguments taken by the function.
implicit -- True for functions which operate on a nodeset consisting
of the current context node when passed no argument.
(e.g., string() and number().)
convert -- When non-None, a function used to filter function arguments.
"""
def decorator(f):
def new_f(self, node, pos, size, context):
if implicit and len(self.args) == 0:
args = [[node]]
else:
args = [x.evaluate(node, pos, size, context)
for x in self.args]
if first:
args[0] = nodeset(args[0])
if len(args[0]) > 0:
args[0] = args[0][0]
else:
args[0] = None
if convert is not None:
args = [convert(x) for x in args]
return f(self, node, pos, size, context, *args)
new_f.minargs = minargs
new_f.maxargs = maxargs
new_f.__name__ = f.__name__
new_f.__doc__ = f.__doc__
return new_f
return decorator
# Node Set Functions
@function(0, 0)
def f_last(self, node, pos, size, context):
return size
@function(0, 0)
def f_position(self, node, pos, size, context):
return pos
@function(1, 1, convert=nodeset)
def f_count(self, node, pos, size, context, nodes):
return len(nodes)
@function(1, 1)
def f_id(self, node, pos, size, context, arg):
if nodesetp(arg):
ids = (string_value(x) for x in arg)
else:
ids = [string(arg)]
if node.nodeType != node.DOCUMENT_NODE:
node = node.ownerDocument
return list(filter(None, (node.getElementById(id) for id in ids)))
@function(0, 1, implicit=True, first=True)
def f_local_name(self, node, pos, size, context, argnode):
if argnode is None:
return ''
if (argnode.nodeType == argnode.ELEMENT_NODE or
argnode.nodeType == argnode.ATTRIBUTE_NODE):
return argnode.localName
elif argnode.nodeType == argnode.PROCESSING_INSTRUCTION_NODE:
return argnode.target
return ''
@function(0, 1, implicit=True, first=True)
def f_namespace_uri(self, node, pos, size, context, argnode):
if argnode is None:
return ''
return argnode.namespaceURI
@function(0, 1, implicit=True, first=True)
def f_name(self, node, pos, size, context, argnode):
if argnode is None:
return ''
if argnode.nodeType == argnode.ELEMENT_NODE:
return argnode.tagName
elif argnode.nodeType == argnode.ATTRIBUTE_NODE:
return argnode.name
elif argnode.nodeType == argnode.PROCESSING_INSTRUCTION_NODE:
return argnode.target
return ''
# String Functions
@function(0, 1, implicit=True, convert=string)
def f_string(self, node, pos, size, context, arg):
return arg
@function(2, None, convert=string)
def f_concat(self, node, pos, size, context, *args):
return ''.join((x for x in args))
@function(2, 2, convert=string)
def f_starts_with(self, node, pos, size, context, a, b):
return a.startswith(b)
@function(2, 2, convert=string)
def f_contains(self, node, pos, size, context, a, b):
return b in a
@function(2, 2, convert=string)
def f_substring_before(self, node, pos, size, context, a, b):
try:
return a[0:a.index(b)]
except ValueError:
return ''
@function(2, 2, convert=string)
def f_substring_after(self, node, pos, size, context, a, b):
try:
return a[a.index(b)+len(b):]
except ValueError:
return ''
@function(2, 3)
def f_substring(self, node, pos, size, context, s, start, count=None):
s = string(s)
start = round(number(start))
if start != start:
# Catch NaN
return ''
if count is None:
end = len(s) + 1
else:
end = start + round(number(count))
if end != end:
# Catch NaN
return ''
if end > len(s):
end = len(s)+1
if start < 1:
start = 1
if start > len(s):
return ''
if end <= start:
return ''
return s[int(start)-1:int(end)-1]
@function(0, 1, implicit=True, convert=string)
def f_string_length(self, node, pos, size, context, s):
return len(s)
@function(0, 1, implicit=True, convert=string)
def f_normalize_space(self, node, pos, size, context, s):
return re.sub(r'\s+', ' ', s.strip())
@function(3, 3, convert=lambda x: unicode(string(x)))
def f_translate(self, node, pos, size, context, s, source, target):
# str.translate() and unicode.translate() are completely different.
# The translate() arguments are coerced to unicode.
table = {}
for schar, tchar in izip(source, target):
schar = ord(schar)
if schar not in table:
table[schar] = tchar
if len(source) > len(target):
for schar in source[len(target):]:
schar = ord(schar)
if schar not in table:
table[schar] = None
return s.translate(table)
# Boolean functions
@function(1, 1, convert=boolean)
def f_boolean(self, node, pos, size, context, b):
return b
@function(1, 1, convert=boolean)
def f_not(self, node, pos, size, context, b):
return not b
@function(0, 0)
def f_true(self, node, pos, size, context):
return True
@function(0, 0)
def f_false(self, node, pos, size, context):
return False
@function(1, 1, convert=string)
def f_lang(self, node, pos, size, context, s):
s = s.lower()
for n in axes['ancestor-or-self'](node):
if n.nodeType == n.ELEMENT_NODE and n.hasAttribute('xml:lang'):
lang = n.getAttribute('xml:lang').lower()
if s == lang or lang.startswith(s + u'-'):
return True
break
return False
# Number functions
@function(0, 1, implicit=True, convert=number)
def f_number(self, node, pos, size, context, n):
return n
@function(1, 1, convert=nodeset)
def f_sum(self, node, pos, size, context, nodes):
return sum((number(string_value(x)) for x in nodes))
@function(1, 1, convert=number)
def f_floor(self, node, pos, size, context, n):
return math.floor(n)
@function(1, 1, convert=number)
def f_ceiling(self, node, pos, size, context, n):
return math.ceil(n)
@function(1, 1, convert=number)
def f_round(self, node, pos, size, context, n):
# XXX round(-0.0) should be -0.0, not 0.0.
# XXX round(-1.5) should be -1.0, not -2.0.
return round(n)
def __str__(self):
return '%s(%s)' % (self.name, ', '.join((str(x) for x in self.args)))
#
# XPath axes.
#
# Dictionary of all axis functions.
axes = {}
def axisfn(reverse=False, principal_node_type=xml.dom.Node.ELEMENT_NODE):
"""Axis function decorator.
An axis function will take a node as an argument and return a sequence
over the nodes along an XPath axis. Axis functions have two extra
attributes indicating the axis direction and principal node type.
"""
def decorate(f):
f.__name__ = f.__name__.replace('_', '-')
f.reverse = reverse
f.principal_node_type = principal_node_type
return f
return decorate
def make_axes():
"""Define functions to walk each of the possible XPath axes."""
@axisfn()
def child(node):
return node.childNodes
@axisfn()
def descendant(node):
for child in node.childNodes:
for node in descendant_or_self(child):
yield node
@axisfn()
def parent(node):
if node.parentNode is not None:
yield node.parentNode
@axisfn(reverse=True)
def ancestor(node):
while node.parentNode is not None:
node = node.parentNode
yield node
@axisfn()
def following_sibling(node):
while node.nextSibling is not None:
node = node.nextSibling
yield node
@axisfn(reverse=True)
def preceding_sibling(node):
while node.previousSibling is not None:
node = node.previousSibling
yield node
@axisfn()
def following(node):
while node is not None:
while node.nextSibling is not None:
node = node.nextSibling
for n in descendant_or_self(node):
yield n
node = node.parentNode
@axisfn(reverse=True)
def preceding(node):
while node is not None:
while node.previousSibling is not None:
node = node.previousSibling
# Could be more efficient here.
for n in reversed(list(descendant_or_self(node))):
yield n
node = node.parentNode
@axisfn(principal_node_type=xml.dom.Node.ATTRIBUTE_NODE)
def attribute(node):
if node.attributes is not None:
return (node.attributes.item(i)
for i in xrange(node.attributes.length))
return ()
@axisfn()
def namespace(node):
raise XPathNotImplementedError("namespace axis is not implemented")
@axisfn()
def self(node):
yield node
@axisfn()
def descendant_or_self(node):
yield node
for child in node.childNodes:
for node in descendant_or_self(child):
yield node
@axisfn(reverse=True)
def ancestor_or_self(node):
return chain([node], ancestor(node))
# Place each axis function defined here into the 'axes' dict.
for axis in locals().values():
axes[axis.__name__] = axis
make_axes()
def merge_into_nodeset(target, source):
"""Place all the nodes from the source node-set into the target
node-set, preserving document order. Both node-sets must be in
document order to begin with.
"""
if len(target) == 0:
target.extend(source)
return
source = [n for n in source if n not in target]
if len(source) == 0:
return
# If the last node in the target set comes before the first node in the
# source set, then we can just concatenate the sets. Otherwise, we
# will need to sort. (We could also check to see if the last node in
# the source set comes before the first node in the target set, but this
# situation is very unlikely in practice.)
if document_order(target[-1]) < document_order(source[0]):
target.extend(source)
else:
target.extend(source)
target.sort(key=document_order)
class AbsolutePathExpr(Expr):
"""Absolute location paths."""
def __init__(self, path):
self.path = path
def evaluate(self, node, pos, size, context):
if node.nodeType != node.DOCUMENT_NODE:
node = node.ownerDocument
if self.path is None:
return [node]
return self.path.evaluate(node, 1, 1, context)
def __str__(self):
return '/%s' % (self.path or '')
class PathExpr(Expr):
"""Location path expressions."""
def __init__(self, steps):
self.steps = steps
def evaluate(self, node, pos, size, context):
# The first step in the path is evaluated in the current context.
# If this is the only step in the path, the return value is
# unimportant. If there are other steps, however, it must be a
# node-set.
result = self.steps[0].evaluate(node, pos, size, context)
if len(self.steps) > 1 and not nodesetp(result):
raise XPathTypeError("path step is not a node-set")
# Subsequent steps are evaluated for each node in the node-set
# resulting from the previous step.
for step in self.steps[1:]:
aggregate = []
for i in xrange(len(result)):
nodes = step.evaluate(result[i], i+1, len(result), context)
if not nodesetp(nodes):
raise XPathTypeError("path step is not a node-set")
merge_into_nodeset(aggregate, nodes)
result = aggregate
return result
def __str__(self):
return '/'.join((str(s) for s in self.steps))
class PredicateList(Expr):
"""A list of predicates.
Predicates are handled as an expression wrapping the expression
filtered by the predicates.
"""
def __init__(self, expr, predicates, axis='child'):
self.predicates = predicates
self.expr = expr
self.axis = axes[axis]
def evaluate(self, node, pos, size, context):
result = self.expr.evaluate(node, pos, size, context)
if not nodesetp(result):
raise XPathTypeError("predicate input is not a node-set")
if self.axis.reverse:
result.reverse()
for pred in self.predicates:
match = []
for i, node in izip(count(1), result):
r = pred.evaluate(node, i, len(result), context)
# If a predicate evaluates to a number, select the node
# with that position. Otherwise, select nodes for which
# the boolean value of the predicate is true.
if numberp(r):
if r == i:
match.append(node)
elif boolean(r):
match.append(node)
result = match
if self.axis.reverse:
result.reverse()
return result
def __str__(self):
s = str(self.expr)
if '/' in s:
s = '(%s)' % s
return s + ''.join(('[%s]' % x for x in self.predicates))
class AxisStep(Expr):
"""One step in a location path expression."""
def __init__(self, axis, test=None, predicates=None):
if test is None:
test = AnyKindTest()
self.axis = axes[axis]
self.test = test
def evaluate(self, node, pos, size, context):
match = []
for n in self.axis(node):
if self.test.match(n, self.axis, context):
match.append(n)
if self.axis.reverse:
match.reverse()
return match
def __str__(self):
return '%s::%s' % (self.axis.__name__, self.test)
#
# Node tests.
#
class Test(object):
"""Abstract base class for node tests."""
def match(self, node, axis, context):
"""Return True if 'node' matches the test along 'axis'."""
class NameTest(object):
def __init__(self, prefix, localpart):
self.prefix = prefix
self.localName = localpart
if self.prefix == None and self.localName == '*':
self.prefix = '*'
def match(self, node, axis, context):
if node.nodeType != axis.principal_node_type:
return False
if self.prefix != '*':
namespaceURI = None
if self.prefix is not None:
try:
namespaceURI = context.namespaces[self.prefix]
except KeyError:
raise XPathUnknownPrefixError(self.prefix)
elif axis.principal_node_type == node.ELEMENT_NODE:
namespaceURI = context.default_namespace
if namespaceURI != node.namespaceURI:
return False
if self.localName != '*':
if self.localName != node.localName:
return False
return True
def __str__(self):
if self.prefix is not None:
return '%s:%s' % (self.prefix, self.localName)
else:
return self.localName
class PITest(object):
def __init__(self, name=None):
self.name = name
def match(self, node, axis, context):
return (node.nodeType == node.PROCESSING_INSTRUCTION_NODE and
(self.name is None or node.target == self.name))
def __str__(self):
if self.name is None:
name = ''
elif "'" in self.name:
name = '"%s"' % self.name
else:
name = "'%s'" % self.name
return 'processing-instruction(%s)' % name
class CommentTest(object):
def match(self, node, axis, context):
return node.nodeType == node.COMMENT_NODE
def __str__(self):
return 'comment()'
class TextTest(object):
def match(self, node, axis, context):
return (node.nodeType == node.TEXT_NODE or
node.nodeType == node.CDATA_SECTION_NODE)
def __str__(self):
return 'text()'
class AnyKindTest(object):
def match(self, node, axis, context):
return True
def __str__(self):
return 'node()'

View File

@ -0,0 +1,416 @@
import xpath.expr as X
from xpath.yappsrt import *
from string import *
import re
class XPathScanner(Scanner):
patterns = [
("r'\\:'", re.compile('\\:')),
("r'node\\s*\\('", re.compile('node\\s*\\(')),
("r'text\\s*\\('", re.compile('text\\s*\\(')),
("r'comment\\s*\\('", re.compile('comment\\s*\\(')),
("r'processing-instruction\\s*\\('", re.compile('processing-instruction\\s*\\(')),
("r'\\,'", re.compile('\\,')),
("r'\\.'", re.compile('\\.')),
("r'\\$'", re.compile('\\$')),
("r'\\)'", re.compile('\\)')),
("r'\\('", re.compile('\\(')),
("r'\\]'", re.compile('\\]')),
("r'\\['", re.compile('\\[')),
("r'\\*'", re.compile('\\*')),
("r':'", re.compile(':')),
("r'\\.\\.'", re.compile('\\.\\.')),
("r'@'", re.compile('@')),
("r'::'", re.compile('::')),
("r'\\/\\/'", re.compile('\\/\\/')),
("r'\\/'", re.compile('\\/')),
("r'\\-'", re.compile('\\-')),
("'\\|'", re.compile('\\|')),
("r'and'", re.compile('and')),
("r'or'", re.compile('or')),
('\\s+', re.compile('\\s+')),
('END', re.compile('$')),
('FORWARD_AXIS_NAME', re.compile('child|descendant-or-self|attribute|self|descendant|following-sibling|following|namespace')),
('REVERSE_AXIS_NAME', re.compile('parent|preceding-sibling|preceding|ancestor-or-self|ancestor')),
('NCNAME', re.compile('[a-zA-Z_][a-zA-Z0-9_\\-\\.\\w]*(?!\\()')),
('FUNCNAME', re.compile('[a-zA-Z_][a-zA-Z0-9_\\-\\.\\w]*')),
('DQUOTE', re.compile('\\"(?:[^\\"])*\\"')),
('SQUOTE', re.compile("\\'(?:[^\\'])*\\'")),
('NUMBER', re.compile('((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))([eE][\\+\\-]?[0-9]+)?')),
('EQ_COMP', re.compile('\\!?\\=')),
('REL_COMP', re.compile('[\\<\\>]\\=?')),
('ADD_COMP', re.compile('[\\+\\-]')),
('MUL_COMP', re.compile('\\*|div|mod')),
]
def __init__(self, str):
Scanner.__init__(self,None,['\\s+'],str)
class XPath(Parser):
def XPath(self):
Expr = self.Expr()
END = self._scan('END')
return Expr
def Expr(self):
OrExpr = self.OrExpr()
return OrExpr
def OrExpr(self):
AndExpr = self.AndExpr()
Expr = AndExpr
while self._peek("r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'or'":
self._scan("r'or'")
AndExpr = self.AndExpr()
Expr = X.OrExpr('or', Expr, AndExpr)
return Expr
def AndExpr(self):
EqualityExpr = self.EqualityExpr()
Expr = EqualityExpr
while self._peek("r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'and'":
self._scan("r'and'")
EqualityExpr = self.EqualityExpr()
Expr = X.AndExpr('and', Expr, EqualityExpr)
return Expr
def EqualityExpr(self):
RelationalExpr = self.RelationalExpr()
Expr = RelationalExpr
while self._peek('EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'EQ_COMP':
EQ_COMP = self._scan('EQ_COMP')
RelationalExpr = self.RelationalExpr()
Expr = X.EqualityExpr(EQ_COMP, Expr, RelationalExpr)
return Expr
def RelationalExpr(self):
AdditiveExpr = self.AdditiveExpr()
Expr = AdditiveExpr
while self._peek('REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'REL_COMP':
REL_COMP = self._scan('REL_COMP')
AdditiveExpr = self.AdditiveExpr()
Expr = X.EqualityExpr(REL_COMP, Expr, AdditiveExpr)
return Expr
def AdditiveExpr(self):
MultiplicativeExpr = self.MultiplicativeExpr()
Expr = MultiplicativeExpr
while self._peek('ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'ADD_COMP':
ADD_COMP = self._scan('ADD_COMP')
MultiplicativeExpr = self.MultiplicativeExpr()
Expr = X.ArithmeticalExpr(ADD_COMP, Expr, MultiplicativeExpr)
return Expr
def MultiplicativeExpr(self):
UnionExpr = self.UnionExpr()
Expr = UnionExpr
while self._peek('MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'MUL_COMP':
MUL_COMP = self._scan('MUL_COMP')
UnionExpr = self.UnionExpr()
Expr = X.ArithmeticalExpr(MUL_COMP, Expr, UnionExpr)
return Expr
def UnionExpr(self):
UnaryExpr = self.UnaryExpr()
Expr = UnaryExpr
while self._peek("'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "'\\|'":
self._scan("'\\|'")
UnaryExpr = self.UnaryExpr()
Expr = X.UnionExpr('|', Expr, UnaryExpr)
return Expr
def UnaryExpr(self):
_token_ = self._peek("r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ == "r'\\-'":
self._scan("r'\\-'")
ValueExpr = self.ValueExpr()
return X.NegationExpr(ValueExpr)
else:
ValueExpr = self.ValueExpr()
return ValueExpr
def ValueExpr(self):
PathExpr = self.PathExpr()
return PathExpr
def PathExpr(self):
_token_ = self._peek("r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ == "r'\\/'":
self._scan("r'\\/'")
path = None
if self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME', "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") not in ["'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'"]:
RelativePathExpr = self.RelativePathExpr()
path = RelativePathExpr
return X.AbsolutePathExpr(path)
elif _token_ == "r'\\/\\/'":
self._scan("r'\\/\\/'")
RelativePathExpr = self.RelativePathExpr()
step = X.AxisStep('descendant-or-self')
RelativePathExpr.steps.insert(0, step)
return X.AbsolutePathExpr(RelativePathExpr)
else:
RelativePathExpr = self.RelativePathExpr()
return RelativePathExpr
def RelativePathExpr(self):
StepExpr = self.StepExpr()
steps = [StepExpr]
while self._peek("r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") in ["r'\\/'", "r'\\/\\/'"]:
_token_ = self._peek("r'\\/'", "r'\\/\\/'")
if _token_ == "r'\\/'":
self._scan("r'\\/'")
else:# == "r'\\/\\/'"
self._scan("r'\\/\\/'")
steps.append(X.AxisStep('descendant-or-self'))
StepExpr = self.StepExpr()
steps.append(StepExpr)
return X.PathExpr(steps)
def StepExpr(self):
_token_ = self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ not in ["r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE']:
AxisStep = self.AxisStep()
return AxisStep
else:
FilterExpr = self.FilterExpr()
return FilterExpr
def AxisStep(self):
_token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ not in ['REVERSE_AXIS_NAME', "r'\\.\\.'"]:
ForwardStep = self.ForwardStep()
step = ForwardStep
else:# in ['REVERSE_AXIS_NAME', "r'\\.\\.'"]
ReverseStep = self.ReverseStep()
step = ReverseStep
expr = X.AxisStep(*step)
if self._peek("r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\['":
PredicateList = self.PredicateList()
expr = X.PredicateList(expr, PredicateList, step[0])
return expr
def ForwardStep(self):
_token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ == 'FORWARD_AXIS_NAME':
ForwardAxis = self.ForwardAxis()
NodeTest = self.NodeTest()
return [ForwardAxis, NodeTest]
else:
AbbrevForwardStep = self.AbbrevForwardStep()
return AbbrevForwardStep
def ForwardAxis(self):
FORWARD_AXIS_NAME = self._scan('FORWARD_AXIS_NAME')
self._scan("r'::'")
return FORWARD_AXIS_NAME
def AbbrevForwardStep(self):
axis = 'child'
if self._peek("r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') == "r'@'":
self._scan("r'@'")
axis = 'attribute'
NodeTest = self.NodeTest()
return [axis, NodeTest]
def ReverseStep(self):
_token_ = self._peek('REVERSE_AXIS_NAME', "r'\\.\\.'")
if _token_ == 'REVERSE_AXIS_NAME':
ReverseAxis = self.ReverseAxis()
NodeTest = self.NodeTest()
return [ReverseAxis, NodeTest]
else:# == "r'\\.\\.'"
AbbrevReverseStep = self.AbbrevReverseStep()
return AbbrevReverseStep
def ReverseAxis(self):
REVERSE_AXIS_NAME = self._scan('REVERSE_AXIS_NAME')
self._scan("r'::'")
return REVERSE_AXIS_NAME
def AbbrevReverseStep(self):
self._scan("r'\\.\\.'")
return ['parent', None]
def NodeTest(self):
_token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME')
if _token_ not in ["r'\\*'", 'NCNAME']:
KindTest = self.KindTest()
return KindTest
else:# in ["r'\\*'", 'NCNAME']
NameTest = self.NameTest()
return NameTest
def NameTest(self):
prefix = None
WildcardOrNCName = self.WildcardOrNCName()
localpart = WildcardOrNCName
if self._peek("r':'", "r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r':'":
self._scan("r':'")
WildcardOrNCName = self.WildcardOrNCName()
prefix = localpart
localpart = WildcardOrNCName
return X.NameTest(prefix, localpart)
def WildcardOrNCName(self):
_token_ = self._peek("r'\\*'", 'NCNAME')
if _token_ == "r'\\*'":
self._scan("r'\\*'")
return '*'
else:# == 'NCNAME'
NCNAME = self._scan('NCNAME')
return NCNAME
def FilterExpr(self):
PrimaryExpr = self.PrimaryExpr()
if self._peek("r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\['":
PredicateList = self.PredicateList()
PrimaryExpr = X.PredicateList(PrimaryExpr,PredicateList)
return PrimaryExpr
def PredicateList(self):
Predicate = self.Predicate()
predicates = [Predicate]
while self._peek("r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\['":
Predicate = self.Predicate()
predicates.append(Predicate)
return predicates
def Predicate(self):
self._scan("r'\\['")
Expr = self.Expr()
self._scan("r'\\]'")
return Expr
def PrimaryExpr(self):
_token_ = self._peek("r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE')
if _token_ not in ["r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME']:
Literal = self.Literal()
return X.LiteralExpr(Literal)
elif _token_ == "r'\\$'":
VariableReference = self.VariableReference()
return VariableReference
elif _token_ == "r'\\('":
self._scan("r'\\('")
Expr = self.Expr()
self._scan("r'\\)'")
return Expr
elif _token_ == "r'\\.'":
ContextItemExpr = self.ContextItemExpr()
return ContextItemExpr
else:# == 'FUNCNAME'
FunctionCall = self.FunctionCall()
return FunctionCall
def VariableReference(self):
self._scan("r'\\$'")
QName = self.QName()
return X.VariableReference(*QName)
def ContextItemExpr(self):
self._scan("r'\\.'")
return X.AxisStep('self')
def FunctionCall(self):
FUNCNAME = self._scan('FUNCNAME')
self._scan("r'\\('")
args = []
if self._peek("r'\\,'", "r'\\)'", "r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') not in ["r'\\,'", "r'\\)'"]:
Expr = self.Expr()
args.append(Expr)
while self._peek("r'\\,'", "r'\\)'") == "r'\\,'":
self._scan("r'\\,'")
Expr = self.Expr()
args.append(Expr)
self._scan("r'\\)'")
return X.Function(FUNCNAME, args)
def KindTest(self):
_token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('")
if _token_ == "r'processing-instruction\\s*\\('":
PITest = self.PITest()
return PITest
elif _token_ == "r'comment\\s*\\('":
CommentTest = self.CommentTest()
return CommentTest
elif _token_ == "r'text\\s*\\('":
TextTest = self.TextTest()
return TextTest
else:# == "r'node\\s*\\('"
AnyKindTest = self.AnyKindTest()
return AnyKindTest
def PITest(self):
self._scan("r'processing-instruction\\s*\\('")
name = None
if self._peek('NCNAME', "r'\\)'", 'DQUOTE', 'SQUOTE') != "r'\\)'":
_token_ = self._peek('NCNAME', 'DQUOTE', 'SQUOTE')
if _token_ == 'NCNAME':
NCNAME = self._scan('NCNAME')
name = NCNAME
else:# in ['DQUOTE', 'SQUOTE']
StringLiteral = self.StringLiteral()
name = StringLiteral
self._scan("r'\\)'")
return X.PITest(name)
def CommentTest(self):
self._scan("r'comment\\s*\\('")
self._scan("r'\\)'")
return X.CommentTest()
def TextTest(self):
self._scan("r'text\\s*\\('")
self._scan("r'\\)'")
return X.TextTest()
def AnyKindTest(self):
self._scan("r'node\\s*\\('")
self._scan("r'\\)'")
return X.AnyKindTest()
def Literal(self):
_token_ = self._peek('NUMBER', 'DQUOTE', 'SQUOTE')
if _token_ == 'NUMBER':
NumericLiteral = self.NumericLiteral()
return NumericLiteral
else:# in ['DQUOTE', 'SQUOTE']
StringLiteral = self.StringLiteral()
return StringLiteral
def NumericLiteral(self):
NUMBER = self._scan('NUMBER')
return float(NUMBER)
def StringLiteral(self):
_token_ = self._peek('DQUOTE', 'SQUOTE')
if _token_ == 'DQUOTE':
DQUOTE = self._scan('DQUOTE')
return DQUOTE[1:-1]
else:# == 'SQUOTE'
SQUOTE = self._scan('SQUOTE')
return SQUOTE[1:-1]
def QName(self):
NCNAME = self._scan('NCNAME')
name = NCNAME
if self._peek("r'\\:'", "r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\:'":
self._scan("r'\\:'")
NCNAME = self._scan('NCNAME')
return (name, NCNAME)
return (None, name)
def parse(rule, text):
P = XPath(XPathScanner(text))
return wrap_error_reporter(P, rule)
if __name__ == '__main__':
from sys import argv, stdin
if len(argv) >= 2:
if len(argv) >= 3:
f = open(argv[2],'r')
else:
f = stdin
print parse(argv[1], f.read())
else: print 'Args: <rule> [<filename>]'

View File

@ -0,0 +1,174 @@
# Yapps 2.0 Runtime
#
# This module is needed to run generated parsers.
from string import join, count, find, rfind
import re
class SyntaxError(Exception):
"""When we run into an unexpected token, this is the exception to use"""
def __init__(self, pos=-1, msg="Bad Token"):
Exception.__init__(self)
self.pos = pos
self.msg = msg
def __repr__(self):
if self.pos < 0: return "#<syntax-error>"
else: return "SyntaxError[@ char %s: %s]" % (repr(self.pos), self.msg)
class NoMoreTokens(Exception):
"""Another exception object, for when we run out of tokens"""
pass
class Scanner:
def __init__(self, patterns, ignore, input):
"""Patterns is [(terminal,regex)...]
Ignore is [terminal,...];
Input is a string"""
self.tokens = []
self.restrictions = []
self.input = input
self.pos = 0
self.ignore = ignore
# The stored patterns are a pair (compiled regex,source
# regex). If the patterns variable passed in to the
# constructor is None, we assume that the class already has a
# proper .patterns list constructed
if patterns is not None:
self.patterns = []
for k, r in patterns:
self.patterns.append( (k, re.compile(r)) )
def token(self, i, restrict=0):
"""Get the i'th token, and if i is one past the end, then scan
for another token; restrict is a list of tokens that
are allowed, or 0 for any token."""
if i == len(self.tokens): self.scan(restrict)
if i < len(self.tokens):
# Make sure the restriction is more restricted
if restrict and self.restrictions[i]:
for r in restrict:
if r not in self.restrictions[i]:
raise NotImplementedError("Unimplemented: restriction set changed")
return self.tokens[i]
raise NoMoreTokens()
def __repr__(self):
"""Print the last 10 tokens that have been scanned in"""
output = ''
for t in self.tokens[-10:]:
output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3]))
return output
def scan(self, restrict):
"""Should scan another token and add it to the list, self.tokens,
and add the restriction to self.restrictions"""
# Keep looking for a token, ignoring any in self.ignore
while 1:
# Search the patterns for the longest match, with earlier
# tokens in the list having preference
best_match = -1
best_pat = '(error)'
for p, regexp in self.patterns:
# First check to see if we're ignoring this token
if restrict and p not in restrict and p not in self.ignore:
continue
m = regexp.match(self.input, self.pos)
if m and len(m.group(0)) > best_match:
# We got a match that's better than the previous one
best_pat = p
best_match = len(m.group(0))
# If we didn't find anything, raise an error
if best_pat == '(error)' and best_match < 0:
msg = "Bad Token"
if restrict:
msg = "Trying to find one of "+join(restrict,", ")
raise SyntaxError(self.pos, msg)
# If we found something that isn't to be ignored, return it
if best_pat not in self.ignore:
# Create a token with this data
token = (self.pos, self.pos+best_match, best_pat,
self.input[self.pos:self.pos+best_match])
self.pos = self.pos + best_match
# Only add this token if it's not in the list
# (to prevent looping)
if not self.tokens or token != self.tokens[-1]:
self.tokens.append(token)
self.restrictions.append(restrict)
return
else:
# This token should be ignored ..
self.pos = self.pos + best_match
class Parser:
def __init__(self, scanner):
self._scanner = scanner
self._pos = 0
def _peek(self, *types):
"""Returns the token type for lookahead; if there are any args
then the list of args is the set of token types to allow"""
tok = self._scanner.token(self._pos, types)
return tok[2]
def _scan(self, type):
"""Returns the matched text, and moves to the next token"""
tok = self._scanner.token(self._pos, [type])
if tok[2] != type:
raise SyntaxError(tok[0], 'Trying to find '+type)
self._pos = 1+self._pos
return tok[3]
def print_error(input, err, scanner):
"""This is a really dumb long function to print error messages nicely."""
p = err.pos
# Figure out the line number
line = count(input[:p], '\n')
print err.msg+" on line "+repr(line+1)+":"
# Now try printing part of the line
text = input[max(p-80, 0):p+80]
p = p - max(p-80, 0)
# Strip to the left
i = rfind(text[:p], '\n')
j = rfind(text[:p], '\r')
if i < 0 or (0 <= j < i): i = j
if 0 <= i < p:
p = p - i - 1
text = text[i+1:]
# Strip to the right
i = find(text,'\n', p)
j = find(text,'\r', p)
if i < 0 or (0 <= j < i): i = j
if i >= 0:
text = text[:i]
# Now shorten the text
while len(text) > 70 and p > 60:
# Cut off 10 chars
text = "..." + text[10:]
p = p - 7
# Now print the string, along with an indicator
print '> ',text
print '> ',' '*p + '^'
print 'List of nearby tokens:', scanner
def wrap_error_reporter(parser, rule):
return_value = None
try:
return_value = getattr(parser, rule)()
except SyntaxError, s:
input = parser._scanner.input
try:
print_error(input, s, parser._scanner)
except ImportError:
print 'Syntax Error',s.msg,'on line',1+count(input[:s.pos], '\n')
except NoMoreTokens:
print 'Could not complete parsing; stopped around here:'
print parser._scanner
return return_value

158
lib/ulib/support/xpathtool.py Executable file
View File

@ -0,0 +1,158 @@
#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
u"""Ce script permet d'obtenir ou de modifier un élément identifié par une expression XPATH
"""
import os, sys, re, types, tempfile, codecs, shutil
from os import path
sys.path.insert(0, path.join(path.dirname(__file__), 'python'))
from xml.dom import Node, minidom
import xpath
def get_text(node, as_xml=False):
if isinstance(node, Node):
if node.nodeType == Node.ELEMENT_NODE:
if as_xml: return node.toxml("utf-8")
else: return xpath.expr.string_value(node).encode("utf-8")
elif node.nodeType == Node.ATTRIBUTE_NODE:
return node.value.encode("utf-8")
elif node.nodeType == Node.TEXT_NODE or node.nodeType == Node.CDATA_SECTION_NODE:
return node.data.encode("utf-8")
elif type(node) is types.UnicodeType:
return node.encode("utf-8")
else:
return str(node)
def set_text(node, value, doc):
if not isinstance(node, Node):
raise ValueError("L'expression ne désigne pas un noeud XML")
if node.nodeType == Node.ELEMENT_NODE:
firstChild = node.firstChild
if value is not None:
textNode = doc.createTextNode(value)
if firstChild is None:
node.appendChild(textNode)
elif firstChild.nodeType == Node.TEXT_NODE or firstChild.nodeType == Node.CDATA_SECTION_NODE:
node.replaceChild(textNode, firstChild)
else:
node.insertBefore(textNode, firstChild)
elif firstChild is not None:
if firstChild.nodeType == Node.TEXT_NODE or firstChild.nodeType == Node.CDATA_SECTION_NODE:
node.removeChild(firstChild)
elif node.nodeType == Node.ATTRIBUTE_NODE:
if value is not None: node.value = value
else: pass # impossible d'accéder au parent d'un attribut
elif node.nodeType == Node.TEXT_NODE or node.nodeType == Node.CDATA_SECTION_NODE:
if value is not None: node.data = value
else: node.parentNode.removeChild(node)
else:
raise ValueError("Type de noeud non supporté: %s" % node.nodeType)
RE_PARENT0 = re.compile(r'(^|/)parent\[')
RE_PARENT1 = re.compile(r'(^|/)parent(?=/|$)')
def py_dom_xpath_compat(expr):
expr = RE_PARENT0.sub(r"\1*[local-name()='parent' and ", expr)
expr = RE_PARENT1.sub(r"\1*[local-name()='parent']", expr)
return expr
def run_xpathtool():
from optparse import OptionParser
OP = OptionParser(usage=u"\n\t%prog -g XPATH [INPUT [OUTPUT]]\n\t%prog -s XPATH VALUE [INPUT [OUTPUT]]", description=__doc__)
OP.add_option('-f', '--input', dest='inf',
help=u"Spécifier le fichier en entrée")
OP.add_option('-o', '--output', dest='outf',
help=u"Spécifier le fichier en sortie")
OP.add_option('-g', '--get', dest='mode', action='store_const', const='get',
help=u"Forcer l'affichage de la valeur. "
+ u"Par défaut, ce mode est sélectionné s'il n'y a aucun argument après XPATH")
OP.add_option('-t', '--exist', dest='mode', action='store_const', const='exist',
help=u"Tester l'existence du chemin spécifié.")
OP.add_option('-s', '--set', dest='mode', action='store_const', const='set',
help=u"Forcer la modification de la valeur. "
+ u"Par défaut, ce mode est sélectionné s'il y a un argument VALUE après XPATH")
OP.add_option('-x', '--as-xml', dest='as_xml', action='store_true',
help=u"Retourner le résultat de l'expression en XML")
OP.add_option('--no-compat', dest='compat', action='store_false', default=True,
help=u"Ne pas transfomer certaines expression en un équivalent compatible avec py-dom-xpath. "
+ u"Par exemple, par défaut \"/parent\" est transformé en \"/*[local-name='parent']\". "
+ u"Cette option désactive ce comportement.")
o, args = OP.parse_args()
inf = o.inf
outf = o.outf
mode = o.mode
as_xml = o.as_xml
compat = o.compat
count = len(args)
if count == 0: raise ValueError("Vous devez spécifier l'expression XPATH")
expr = args[0]
if compat: expr = py_dom_xpath_compat(expr)
value = None
args = args[1:]
count = len(args)
if mode is None:
if count == 0: # xpathtool.py XPATH
mode = 'get'
elif inf is None:
if count == 1: # xpathtool.py XPATH INPUT
mode = 'get'
inf = args[0]
elif count >= 2: # xpathtool.py XPATH VALUE INPUT [OUTPUT]
mode = 'set'
value = args[0]
inf = args[1]
if count > 2: outf = args[2]
elif inf is not None: # xpathtool.py XPATH VALUE -f INPUT
mode = 'set'
value = args[0]
elif mode == 'get':
if inf is None: # xpathtool.py -g XPATH [INPUT]
if count > 0: inf = args[0]
elif mode == 'set':
if count > 0: value = args[0]
if inf is None: # xpathtool.py -s XPATH VALUE [INPUT [OUTPUT]]
if count > 1: inf = args[1]
if count > 2: outf = args[2]
if inf == '-': inf = None
if outf == '-': outf = sys.stdout
if inf is None:
inf = sys.stdin
if outf is None: outf = sys.stdout
elif outf is None:
outf = inf
doc = minidom.parse(inf)
if mode == 'get' or mode == 'exist':
#print "search %r from %r" % (expr, inf) #DEBUG
nodes = xpath.find(expr, doc)
if mode == 'get':
for node in nodes:
print get_text(node, as_xml)
if nodes: r = 0
else: r = 1
sys.exit(r)
elif mode == 'set':
if value is not None and not type(value) is types.UnicodeType:
value = unicode(value, "utf-8")
#print "search %r in %r, replace with %r then write in %r" % (expr, inf, value, outf) #DEBUG
for node in xpath.find(expr, doc):
set_text(node, value, doc)
#print "writing to %r" % outf #DEBUG
if type(outf) in types.StringTypes:
fd, tmpf = tempfile.mkstemp()
try:
os.close(fd)
out = codecs.open(tmpf, "w", "utf-8")
doc.writexml(out, encoding="utf-8")
out.close()
shutil.copyfile(tmpf, outf)
finally:
os.remove(tmpf)
else:
doc.writexml(outf, encoding="utf-8")
if __name__ == '__main__':
run_xpathtool()

View File

@ -610,7 +610,7 @@ __VCS_GIT_ADVANCED=(
git_is_merged
)
function git_check_gitvcs() {
[ "$(_vcs_get_type "$(_vcs_get_dir)")" == git ]
git rev-parse --show-toplevel >&/dev/null
}
function git_ensure_gitvcs() {
git_check_gitvcs || die "Ce n'est pas un dépôt git"

8
lib/ulib/xmlsupport Normal file
View File

@ -0,0 +1,8 @@
##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Fonction de support pour gérer des fichiers XML
##@cooked nocomments
##@require ulib
uprovide xmlsupport
urequire ulib
function xpathtool() { "$ULIBDIR/support/xpathtool.py" "$@"; }

61
prel
View File

@ -94,7 +94,7 @@ OPTIONS
-m, --merge
Si la branche actuelle est une branche de release, ou s'il existe une
branche de release, la merger dans master, puis dans develop, puis la
supprimer. Puis basculer sur la branche master.
supprimer. A l'issu de cette opération, rester sur la branche develop.
S'il n'existe pas de branche de release, proposer de fusionner les
modifications de la branche develop dans la branche master, sans
préparer de branche de release au préalable.
@ -114,13 +114,18 @@ OPTIONS
release par rapport à develop.
-d, --diff
Afficher les modifications actuellement effectuée dans la branche de
release par rapport à develop, sous forme de diff."
release par rapport à develop, sous forme de diff.
OPTIONS AVANCEES
--uc, --upgrade-changes
Convertir un fichier CHANGES.txt en CHANGES.md"
}
function show_summary() {
git log --oneline --graph "$@" |
grep -vF '|\' | grep -vF '|/' | sed 's/\* //; s/^ /+ /' |
grep -v "Intégration de la branche release-"
grep -v "Intégration de la branche release-" |
grep -v "Branche develop en version .*-SNAPSHOT"
}
function format_md() {
@ -143,6 +148,21 @@ $1 == "|" {
'
}
function __get_newver_from_release() {
local relver filever
is_release_branch "$release" && relver="${release#release-}"
setx filever=pver --sw "" --allow-empty
if [ -n "$relver" -a "$filever" != "$relver" ]; then
newver="$filever"
ewarn "La version de la branche de release est différente de la version dans le fichier"
enote "La version effectivement sélectionnée est $newver"
elif [ -n "$filever" ]; then
newver="$filever"
elif [ -n "$relver" ]; then
newver="$relver"
fi
}
projdir=
origin=origin
action=auto
@ -208,6 +228,9 @@ fi
git_ensure_gitvcs
setx vertype=pver --sw "" --show-source
[[ "$vertype" == pom* ]] && maven_update=1 || maven_update=
push_branches=()
push_tags=()
push_deferred=
@ -216,14 +239,20 @@ push_deferred=
setx branch=git_get_branch
update_opt=
if [ "$action" == update ]; then
setx oldver=pver -g ""
if [ -n "$maven_update" ]; then
setx oldver=pver --gw develop:
enote "La version de la branche develop est $oldver"
else
setx oldver=pver --gw master:
fi
newver=
if [ "$incversion" == auto ]; then
if [ ${#pver_opts[*]} -gt 0 ]; then
# des options ont été spécifiées, les honorer
setx newver=pver -s "$oldver" "${pver_opts[@]}"
setx newver=pver -s "$oldver" ${maven_update:+--maven-update -R} "${pver_opts[@]}"
release="release-$newver"
else
# sinon, prendre une décision en fonction des branches de release
@ -246,6 +275,7 @@ if [ "$action" == update ]; then
fi
fi
fi
pver_opts=(${maven_update:+--maven-update -R} "${pver_opts[@]}")
case "$incversion" in
menu)
setx majorv=pver -s "$oldver" -ux "${pver_opts[@]}"
@ -257,18 +287,28 @@ if [ "$action" == update ]; then
-t "Basculer vers une nouvelle branche de release" \
-m "Veuillez choisir la branche à créer"
[ "$release" != master ] && newver="${release#release-}"
if [ "$release" == "release-$majorv" ]; then
update_opt=-x
elif [ "$release" == "release-$minorv" ]; then
update_opt=-z
elif [ "$release" == "release-$patchlevelv" ]; then
update_opt=-p
fi
;;
major)
setx newver=pver -s "$oldver" -ux "${pver_opts[@]}"
release="release-$newver"
update_opt=-x
;;
minor)
setx newver=pver -s "$oldver" -uz "${pver_opts[@]}"
release="release-$newver"
update_opt=-z
;;
patchlevel)
setx newver=pver -s "$oldver" -up "${pver_opts[@]}"
release="release-$newver"
update_opt=-p
;;
esac
@ -331,7 +371,7 @@ Vous allez créer la nouvelle branche de release ${COULEUR_VERTE}$release${COULE
if [ -z "$newver" ]; then
# le cas échéant, tenter de calculer la version en fonction de la release
is_release_branch "$release" && newver="${release#release-}"
__get_newver_from_release
fi
if [ "$r" -eq 0 -a -n "$write" ]; then
@ -445,7 +485,7 @@ if [ "$action" == merge ]; then
if [ -z "$newver" ]; then
# le cas échéant, tenter de calculer la version en fonction de la release
is_release_branch "$release" && newver="${release#release-}"
__get_newver_from_release
fi
if [ -n "$newver" ]; then
@ -469,6 +509,13 @@ ou celle-ci pour pour pousser TOUS les tags:
estepn "Intégration ${COULEUR_VERTE}$release${COULEUR_NORMALE} --> ${COULEUR_BLEUE}develop${COULEUR_NORMALE}"
git checkout develop
git merge "$release" -m "Intégration de la branche $release" --no-ff || die
if [ -n "$maven_update" ]; then
[ -n "$update_opt" ] || update_opt=-z
pver -u $update_opt -S
git add -A
git commit -m "Branche develop en version $(pver --show)" || die
fi
fi
# mettre à jour la branche sur laquelle on se trouve

159
pver
View File

@ -1,7 +1,7 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS ptools
urequire DEFAULTS ptools xmlsupport
function pver_display_help() {
uecho "$scriptname: gérer des numéros de version selon les règles du versionage sémantique v2.0.0 (http://semver.org/)
@ -10,38 +10,55 @@ USAGE
$scriptname [options]
OPTIONS
-w, --auto-file DIR
Gérer le numéro de version du répertoire spécifié. Si un fichier pom.xml
existe dans ce répertoire, alors c'est l'option '-e DIR/pom.xml' qui est
activé. Si un fichier VERSION.txt existe dans ce répertoire, alors c'est
l'option '-f DIR/VERSION.txt' qui est activée. Sinon, un nouveau fichier
VERSION.txt est créé dans ce répertoire. C'est l'option par défaut.
--sw, --auto-string DIR
Prendre pour valeur de départ la version du répertoire spécifié. Si un
fichier pom.xml existe dans ce répertoire, alors c'est l'option -E qui
est activé. Si un fichier VERSION.txt existe dans ce répertoire, alors
c'est l'option -F qui est activée.
--gw, --auto-git-string [BRANCH:]DIR
Prendre pour valeur de départ la version du répertoire spécifié dans la
branche BRANCH (qui vaut par défaut master) du dépôt git. Si un fichier
pom.xml existe dans ce répertoire, alors c'est l'option -g qui est
activé. Si un fichier VERSION.txt existe dans ce répertoire, alors c'est
l'option -G qui est activée.
-e, --pom POMFILE
Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié.
Le fichier DOIT exister. Implique --maven-update
-E, --pom-string POMFILE
Prendre pour valeur de départ la version contenue dans le fichier
pom.xml spécifié. Le fichier DOIT exister. Implique --maven-update
-f, --file VERSIONFILE
Gérer le numéro de version se trouvant dans le fichier spécifié. Le
fichier est créé si nécessaire. C'est l'option par défaut si un fichier
nommé VERSION.txt se trouve dans le répertoire courant.
-e, --maven POMFILE
Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié.
Le fichier DOIT exister. C'est l'option par défaut si un fichier nommé
pom.xml se trouve dans le répertoire courant.
fichier est créé si nécessaire.
-F, --file-string VERSIONFILE
Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui
vaut par défaut VERSION.txt)
-g, --git-string [branch:]VERSIONFILE
Prendre pour valeur de départ le contenu du fichier VERSIONFILE.
-g, --git-file-string [BRANCH:]VERSIONFILE
Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui
vaut par défaut VERSION.txt) dans la branche BRANCH (qui vaut par défaut
master) du dépôt git situé dans le répertoire courant.
master) du dépôt git. Retourner 2 si on n'est pas situé dans un dépôt
git.
-G, --git-pom-string [BRANCH:]POMFILE
Prendre pour valeur de départ la version du fichier POMFILE (qui vaut
par défaut pom.xml) dans la branche BRANCH (qui vaut par défaut master)
du dépôt git. Retourner 2 si on n'est pas situé dans un dépôt git.
--git-prel-string
Prendre pour valeur de départ le numéro de version correspondant à la
branche de release courante. Retourner 1 si la branche courante n'est
pas une branche de release, 2 si on n'est pas situé dans un dépôt git.
-s, --string VERSION
Prendre pour valeur de départ le numéro de version spécifié
--show
Afficher le numéro de version. C'est l'action par défaut
--allow-empty
Supporter que la version puisse ne pas être spécifiée ni trouvée. Dans
ce cas, ne pas assumer que la version effective est 0.0.0
Avec --show et --update, ne rien afficher si la version est vide.
--check
Vérifier que le numéro de version est conforme aux règles du versionage
sémantique
--convert
--no-convert
Activer (resp. désactiver) la conversion automatique. Par défaut, si la
version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est
convertie automatiquement au format sémantique x.z.p+rYYYYMMDD
--eq VERSION
--ne VERSION
--lt VERSION
@ -54,6 +71,10 @@ OPTIONS
--le, --gt, et --ge ignorent l'identifiant de build (comme le demande la
règle du versionage sémantique). Les opérateurs --same et --diff
comparent aussi les identifiants de build.
-u, --update
Mettre à jour le numéro de version.
Les options suivantes impliquent --update:
-v, --set-version VERSION
Spécifier un nouveau numéro de version qui écrase la valeur actuelle.
Cette option ne devrait pas être utilisée en temps normal parce que cela
@ -62,18 +83,20 @@ OPTIONS
Spécifier un nouveau numéro de version qui écrase la valeur actuelle. Le
numéro de version est obtenu à partir du nom de la branche git courante,
qui doit être de la forme release-VERSION
-u, --update
Mettre à jour le numéro de version.
--menu
Afficher un menu permettant de choisir le composant de la version à
incrémenter
incrémenter.
-x, --major
Augmenter le numéro de version majeure
Augmenter le numéro de version majeure.
-z, --minor
Augmenter le numéro de version mineure. C'est la valeur par défaut.
-p, --patchlevel
Augmenter le numéro de patch
Augmenter le numéro de patch.
-k, --keep
Ne pas augmenter le numéro de version. Cette option est surtout utile
pour *convertir* un numéro de version existant et mettre à jour le
fichier correspondant. Elle est assumée si aucune option -[xzp] n'est
spécifiée et qu'une des options -[labrSRmM] est utilisée.
-l, --prelease ID
Spécifier un identifiant de pré-release, à ajouter au numéro de version.
@ -85,17 +108,95 @@ OPTIONS
de l'identifiant, e.g. alpha deviant alpha.1, beta-1.2 devient beta-1.3,
rc1 devient rc2
XXX ces fonctions ne sont pas encore implémentées
-S, --snapshot
Ajouter l'identifiant SNAPSHOT, utilisé par Maven
-R, --final, --release
Supprimer l'identifiant de prérelease
Supprimer l'identifiant de prérelease, utilisé par Maven
-m, --metadata ID
Spécifier un identifiant de build, à ajouter au numéro de version.
-M, --vcs-metadata
Spécifier l'identifiant à partir de la révision actuelle dans le
Calculer l'identifiant de build à partir de la révision actuelle dans le
gestionnaire de version. Note: pour le moment, seul git est supporté.
--add-metadata ID
Ajouter l'identifiant spécifié à la valeur actuelle, au lieu de la
remplacer. Séparer l'identifiant de la valeur précédente avec un '.'"
remplacer. Séparer l'identifiant de la valeur précédente avec un '.'
OPTIONS AVANCEES
--show-source
Afficher le type de source qui sera traité, i.e. pom, file, pom-string,
file-string, git-pom-string, git-file-string
--vpath VPATH
Pour les options -e et -E, spécifier le chemin XPATH du tag qui contient
le numéro de version.
--map MAPFILE
Cette option permet de spécifier un fichier de règles qui indique les
fichiers pom.xml et VERSION.txt qui doivent être mis à jour dans un
projet multi-modules pour lequel les versions doivent être mises à jour
en même temps.
Par défaut, si un fichier nommé .pver-map existe dans le répertoire de
{POM,VERSION}FILE, cette option est automatiquement activée. Ainsi, on
n'aura besoin d'utiliser cette option que si l'on désire charger un
fichier alternatif ou ignorer le fichier par défaut.
Si une valeur vide est fournie, seul le fichier {POM,VERSION}FILE est
traité. Sinon, {POM,VERSION}FILE est utilisé uniquement pour chercher le
fichier .pver-map et seuls les fichiers mentionnés dans MAPFILE sont
traités.
Le fichier MAPFILE est constitué d'un ensemble de lignes de la forme
FILESPEC:VPATH
FILESPEC est requis et prend la forme d'une spécification de chemin
relatif au répertoire de MAPFILE et identifiant un ensemble de fichiers
de version. Si FILESPEC contient des wildcards, alors les fichiers
identifiés par ce chemin sont ignorés s'ils ont déjà été traités par une
règle précédente. Si FILESPEC ne contient pas de wildcards, alors le
fichier est systématiquement traité.
VPATH désigne le chemin XPATH vers le numéro de version qu'il faut
mettre à jour dans les fichiers pom.xml. Si VPATH vaut -, alors le
fichier pom.xml correspondant n'est pas modifié (il est ignoré). Si
VPATH est vide alors le chemin par défaut est utilisé pour ce
fichier. Pour les fichiers VERSION.txt, VPATH doit être vide
Le fichier de version correspondant au premier fichier de la première
ligne de MAPFILE contient la version de référence, qui est dupliquée
dans tous les autres fichiers.
--allow-empty
Supporter que la version puisse ne pas être spécifiée ni trouvée. Sans
cette option, on assume que la version effective est 0.0.0 si elle n'est
pas spécifiée ni trouvée.
Avec --show et --update, ne rien afficher si la version est vide.
--convert
--no-convert
Activer (resp. désactiver) la conversion automatique. Par défaut, si la
version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est
convertie automatiquement au format sémantique x.z.p+rYYYYMMDD
-t, --maven-update
Mettre à jour le numéro de version selons les règles de Maven. Cette
option est automatiquement activée si -e est sélectionné. Elle n'est
prise en compte qu'avec l'option -u
Si les options -R et -S ne sont pas spécifiée, alors une release est
transformée en snapshot et une snapshot en release. Avec -R, une
snapshot est transformée en release, et une release est traitée /
incrémentée normalement. Avec -S une release est transformée en
snapshot, et un snapshot est traité / incrémentée normalement.
Si l'une des options -x, -z, -p est utilisée, alors la partie spécifiée
est incrémentée selon les règles suivantes. Dans les exemples suivants,
A est un nombre quelconque, B = A + 1, et x, y et z sont des nombres
quelconques.
Avec l'option -x:
A.0.0-SNAPSHOT --> A.0.0
A.x.y-SNAPSHOT --> B.0.0
x.A.y --> x.B.0-SNAPSHOT
Avec l'option -z:
x.A.0-SNAPSHOT --> x.A.0
x.A.y-SNAPSHOT --> x.B.0
x.A.y --> x.B.0-SNAPSHOT
Avec l'option -p:
x.y.z-SNAPSHOT --> x.y.z
x.y.A --> x.y.B-SNAPSHOT
Si aucune des options -x, -z, -p n'est utilisée, alors les parties sont
incrémentées selon les règles suivantes:
x.y.z-SNAPSHOT --> x.y.z (comme -p)
x.A.0 --> x.B.0-SNAPSHOT (comme -z)
x.y.A --> x.y.B-SNAPSHOT (comme -p)"
}
pver "$@"