yaql/yaql/standard_library/strings.py

1032 lines
28 KiB
Python

# Copyright (c) 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
The module describes which operations can be done with strings in YAQL.
"""
import string as string_module
from yaql.language import specs
from yaql.language import utils
from yaql.language import yaqltypes
@specs.parameter('left', yaqltypes.String())
@specs.parameter('right', yaqltypes.String())
@specs.name('#operator_>')
def gt(left, right):
""":yaql:operator >
Returns true if the left operand is strictly greater than the right,
ordering lexicographically, otherwise false.
:signature: left > right
:arg left: left operand
:argType left: string
:arg right: right operand
:argType right: string
:returnType: boolean
.. code::
yaql> "abc" > "ab"
true
yaql> "abc" > "abb"
true
yaql> "abc" > "abc"
false
"""
return left > right
@specs.parameter('left', yaqltypes.String())
@specs.parameter('right', yaqltypes.String())
@specs.name('#operator_<')
def lt(left, right):
""":yaql:operator <
Returns true if the left operand is strictly less than the right, ordering
lexicographically, otherwise false.
:signature: left < right
:arg left: left operand
:argType left: string
:arg right: right operand
:argType right: string
:returnType: boolean
.. code::
yaql> "ab" < "abc"
true
yaql> "abb" < "abc"
true
yaql> "abc" < "abc"
false
"""
return left < right
@specs.parameter('left', yaqltypes.String())
@specs.parameter('right', yaqltypes.String())
@specs.name('#operator_>=')
def gte(left, right):
""":yaql:operator >=
Returns true if the left operand is greater or equal to the right, ordering
lexicographically, otherwise false.
:signature: left >= right
:arg left: left operand
:argType left: string
:arg right: right operand
:argType right: string
:returnType: boolean
.. code::
yaql> "abc" >= "ab"
true
yaql> "abc" >= "abc"
true
"""
return left >= right
@specs.parameter('left', yaqltypes.String())
@specs.parameter('right', yaqltypes.String())
@specs.name('#operator_<=')
def lte(left, right):
""":yaql:operator <=
Returns true if the left operand is less or equal to the right, ordering
lexicographically, otherwise false.
:signature: left <= right
:arg left: left operand
:argType left: string
:arg right: right operand
:argType right: string
:returnType: boolean
.. code::
yaql> "ab" <= "abc"
true
yaql> "abc" <= "abc"
true
"""
return left <= right
@specs.parameter('args', yaqltypes.String())
def concat(*args):
""":yaql:concat
Returns concatenated args.
:signature: concat([args])
:arg [args]: values to be joined
:argType [args]: string
:returnType: string
.. code::
yaql> concat("abc", "de", "f")
"abcdef"
"""
return ''.join(args)
@specs.parameter('string', yaqltypes.String())
@specs.method
def to_upper(string):
""":yaql:toUpper
Returns a string with all case-based characters uppercase.
:signature: string.toUpper()
:receiverArg string: value to uppercase
:argType string: string
:returnType: string
.. code::
yaql> "aB1c".toUpper()
"AB1C"
"""
return string.upper()
@specs.parameter('string', yaqltypes.String())
@specs.extension_method
def len_(string):
""":yaql:len
Returns size of the string.
:signature: string.len()
:receiverArg string: input string
:argType string: string
:returnType: integer
.. code::
yaql> "abc".len()
3
"""
return len(string)
@specs.parameter('string', yaqltypes.String())
@specs.method
def to_lower(string):
""":yaql:toLower
Returns a string with all case-based characters lowercase.
:signature: string.toLower()
:receiverArg string: value to lowercase
:argType string: string
:returnType: string
.. code::
yaql> "AB1c".toLower()
"ab1c"
"""
return string.lower()
@specs.parameter('string', yaqltypes.String())
@specs.parameter('separator', yaqltypes.String(nullable=True))
@specs.parameter('max_splits', int)
@specs.method
def split(string, separator=None, max_splits=-1):
""":yaql:split
Returns a list of tokens in the string, using separator as the
delimiter.
:signature: string.split(separator => null, maxSplits => -1)
:receiverArg string: value to be splitted
:argType string: string
:arg separator: delimiter for splitting. null by default, which means
splitting with whitespace characters
:argType separator: string
:arg maxSplits: maximum number of splittings. -1 by default, which means
all possible splits are done
:argType maxSplits: integer
:returnType: list
.. code::
yaql> "abc de f".split()
["abc", "de", "f"]
yaql> "abc de f".split(maxSplits => 1)
["abc", "de f"]
yaql> "abcde".split("c")
["ab", "de"]
"""
return string.split(separator, max_splits)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('separator', yaqltypes.String(nullable=True))
@specs.parameter('max_splits', int)
@specs.method
def right_split(string, separator=None, max_splits=-1):
""":yaql:rightSplit
Returns a list of tokens in the string, using separator as the
delimiter. If maxSplits is given then at most maxSplits splits are done -
the rightmost ones.
:signature: string.rightSplit(separator => null, maxSplits => -1)
:receiverArg string: value to be splitted
:argType string: string
:arg separator: delimiter for splitting. null by default, which means
splitting with whitespace characters
:argType separator: string
:arg maxSplits: number of splits to be done - the rightmost ones.
-1 by default, which means all possible splits are done
:argType maxSplits: integer
:returnType: list
.. code::
yaql> "abc de f".rightSplit()
["abc", "de", "f"]
yaql> "abc de f".rightSplit(maxSplits => 1)
["abc de", "f"]
"""
return string.rsplit(separator, max_splits)
@specs.parameter('sequence', yaqltypes.Iterable())
@specs.parameter('separator', yaqltypes.String())
@specs.inject('str_delegate', yaqltypes.Delegate('str'))
@specs.method
def join(sequence, separator, str_delegate):
""":yaql:join
Returns a string with sequence elements joined by the separator.
:signature: sequence.join(separator)
:receiverArg sequence: chain of values to be joined
:argType sequence: sequence of strings
:arg separator: value to be placed between joined pairs
:argType separator: string
:returnType: string
.. code::
yaql> ["abc", "de", "f"].join("")
"abcdef"
yaql> ["abc", "de", "f"].join("|")
"abc|de|f"
"""
return separator.join(map(str_delegate, sequence))
@specs.parameter('sequence', yaqltypes.Iterable())
@specs.parameter('separator', yaqltypes.String())
@specs.inject('str_delegate', yaqltypes.Delegate('str'))
@specs.method
def join_(separator, sequence, str_delegate):
""":yaql:join
Returns a string with sequence elements joined by the separator.
:signature: separator.join(sequence)
:receiverArg separator: value to be placed between joined pairs
:argType separator: string
:arg sequence: chain of values to be joined
:argType sequence: sequence of strings
:returnType: string
.. code::
yaql> "|".join(["abc", "de", "f"])
"abc|de|f"
"""
return join(sequence, separator, str_delegate)
@specs.parameter('value', nullable=True)
def str_(value):
""":yaql:str
Returns a string representation of the value.
:signature: str(value)
:arg value: value to be evaluated to string
:argType value: any
:returnType: string
.. code::
yaql> str(["abc", "de"])
"(u'abc', u'd')"
yaql> str(123)
"123"
"""
if value is None:
return 'null'
elif value is True:
return 'true'
elif value is False:
return 'false'
else:
return str(value)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('chars', yaqltypes.String(nullable=True))
@specs.method
def trim(string, chars=None):
""":yaql:trim
Returns a string with the leading and trailing chars removed.
:signature: string.trim(chars => null)
:receiverArg string: value to be trimmed
:argType string: string
:arg chars: symbols to be removed from input string. null by default,
which means trim is done with whitespace characters
:argType chars: string
:returnType: string
.. code::
yaql> " abcd ".trim()
"abcd"
yaql> "aababa".trim("a")
"bab"
"""
return string.strip(chars)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('chars', yaqltypes.String(nullable=True))
@specs.method
def trim_left(string, chars=None):
""":yaql:trimLeft
Returns a string with the leading chars removed.
:signature: string.trimLeft(chars => null)
:receiverArg string: value to be trimmed
:argType string: string
:arg chars: symbols to be removed from start of input string. null by
default, which means trim is done with whitespace characters
:argType chars: string
:returnType: string
.. code::
yaql> " abcd ".trimLeft()
"abcd "
yaql> "aababa".trimLeft("a")
"baba"
"""
return string.lstrip(chars)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('chars', yaqltypes.String(nullable=True))
@specs.method
def trim_right(string, chars=None):
""":yaql:trimRight
Returns a string with the trailing chars removed.
:signature: string.trimRight(chars => null)
:receiverArg string: value to be trimmed
:argType string: string
:arg chars: symbols to be removed from end of input string. null by
default, which means trim is done with whitespace characters
:argType chars: string
:returnType: string
.. code::
yaql> " abcd ".trimRight()
" abcd"
yaql> "aababa".trimRight("a")
"aabab"
"""
return string.rstrip(chars)
@specs.parameter('string', yaqltypes.String(nullable=True))
@specs.parameter('chars', yaqltypes.String(nullable=True))
@specs.extension_method
def norm(string, chars=None):
""":yaql:norm
Returns a string with the leading and trailing chars removed.
If the resulting string is empty, returns null.
:signature: string.norm(chars => null)
:receiverArg string: value to be cut with specified chars
:argType string: string
:arg chars: symbols to be removed from the start and the end of input
string. null by default, which means norm is done with whitespace
characters
:argType chars: string
:returnType: string
.. code::
yaql> " abcd ".norm()
"abcd"
yaql> "aaaa".norm("a")
null
"""
if string is None:
return None
value = string.strip(chars)
return None if not value else value
@specs.parameter('string', yaqltypes.String(nullable=True))
@specs.parameter('trim_spaces', bool, alias='trim')
@specs.parameter('chars', yaqltypes.String(nullable=True))
@specs.extension_method
def is_empty(string, trim_spaces=True, chars=None):
""":yaql:isEmpty
Returns true if the string with removed leading and trailing chars is
empty.
:signature: string.isEmpty(trimSpaces => true, chars => null)
:receiverArg string: value to be checked for emptiness after trim
:argType string: string
:arg trimSpaces: true by default, which means string to be trimmed with
chars. false means checking whether input string is empty
:argType trimSpaces: boolean
:arg chars: symbols for trimming. null by default, which means trim is
done with whitespace characters
:argType chars: string
:returnType: boolean
.. code::
yaql> "abaab".isEmpty(chars=>"ab")
true
yaql> "aba".isEmpty(chars=>"a")
false
"""
if string is None:
return True
if trim_spaces:
string = string.strip(chars)
return not string
@specs.parameter('string', yaqltypes.String())
@specs.parameter('old', yaqltypes.String())
@specs.parameter('new', yaqltypes.String())
@specs.parameter('count', int)
@specs.method
def replace(string, old, new, count=-1):
""":yaql:replace
Returns a string with first count occurrences of old replaced with new.
:signature: string.replace(old, new, count => -1)
:receiverArg string: input string
:argType string: string
:arg old: value to be replaced
:argType old: string
:arg new: replacement for old value
:argType new: string
:arg count: how many first replacements to do. -1 by default, which means
to do all replacements
:argType count: integer
:returnType: string
.. code::
yaql> "abaab".replace("ab", "cd")
"cdacd"
"""
return string.replace(old, new, count)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('replacements', utils.MappingType)
@specs.parameter('count', int)
@specs.inject('str_func', yaqltypes.Delegate('str'))
@specs.method
@specs.name('replace')
def replace_with_dict(string, str_func, replacements, count=-1):
""":yaql:replace
Returns a string with all occurrences of replacements' keys replaced
with corresponding replacements' values.
If count is specified, only the first count occurrences of every key
are replaced.
:signature: string.replace(replacements, count => -1)
:receiverArg string: input string
:argType string: string
:arg replacements: dict of replacements in format {old => new ...}
:argType replacements: mapping
:arg count: how many first occurrences of every key are replaced. -1 by
default, which means to do all replacements
:argType count: integer
:returnType: string
.. code::
yaql> "abc ab abc".replace({abc => xx, ab => yy})
"xx yy xx"
yaql> "abc ab abc".replace({ab => yy, abc => xx})
"yyc yy yyc"
yaql> "abc ab abc".replace({ab => yy, abc => xx}, 1)
"yyc ab xx"
"""
for key, value in replacements.items():
string = string.replace(str_func(key), str_func(value), count)
return string
@specs.parameter('left', yaqltypes.String())
@specs.parameter('right', int)
@specs.name('#operator_*')
def string_by_int(left, right, engine):
""":yaql:operator *
Returns string repeated count times.
:signature: left * right
:arg left: left operand
:argType left: string
:arg right: right operator, how many times repeat input string
:argType right: integer
:returnType: string
.. code::
yaql> "ab" * 2
"abab"
"""
utils.limit_memory_usage(engine, (-right + 1, u''), (right, left))
return left * right
@specs.parameter('left', yaqltypes.String())
@specs.parameter('right', yaqltypes.String())
@specs.name('#operator_in')
def in_(left, right):
""":yaql:operator in
Returns true if there is at least one occurrence of left string in right.
:signature: left in right
:arg left: left operand, which occurrence is checked
:argType left: string
:arg right: right operand
:argType right: string
:returnType: boolean
.. code::
yaql> "ab" in "abc"
true
yaql> "ab" in "acb"
false
"""
return left in right
@specs.parameter('left', int)
@specs.parameter('right', yaqltypes.String())
@specs.name('#operator_*')
def int_by_string(left, right, engine):
""":yaql:operator *
Returns string repeated count times.
:signature: left * right
:arg left: left operand, how many times repeat input string
:argType left: integer
:arg right: right operator
:argType right: string
:returnType: string
.. code::
yaql> 2 * "ab"
"abab"
"""
return string_by_int(right, left, engine)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('start', int)
@specs.parameter('length', int)
@specs.method
def substring(string, start, length=-1):
""":yaql:substring
Returns a substring beginning from start index ending with start+end index.
:signature: string.substring(start, length => -1)
:receiverArg string: input string
:argType string: string
:arg start: index for substring to start with
:argType start: integer
:arg length: length of substring. -1 by default, which means end of
substring to be equal to the end of input string
:argType length: integer
:returnType: string
.. code::
yaql> "abcd".substring(1)
"bcd"
yaql> "abcd".substring(1, 2)
"bc"
"""
if length < 0:
length = len(string)
if start < 0:
start += len(string)
return string[start:start + length]
@specs.parameter('string', yaqltypes.String())
@specs.parameter('sub', yaqltypes.String())
@specs.parameter('start', int)
@specs.method
def index_of(string, sub, start=0):
""":yaql:indexOf
Returns an index of first occurrence sub in string beginning from start.
-1 is a return value if there is no any occurrence.
:signature: string.indexOf(sub, start => 0)
:receiverArg string: input string
:argType string: string
:arg sub: substring to find in string
:argType sub: string
:arg start: index to start search with, 0 by default
:argType start: integer
:returnType: integer
.. code::
yaql> "cabcdab".indexOf("ab")
1
yaql> "cabcdab".indexOf("ab", 2)
5
yaql> "cabcdab".indexOf("ab", 6)
-1
"""
return string.find(sub, start)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('sub', yaqltypes.String())
@specs.parameter('start', int)
@specs.parameter('length', int)
@specs.method
def index_of_(string, sub, start, length):
""":yaql:indexOf
Returns an index of first occurrence sub in string beginning from start
ending with start+length.
-1 is a return value if there is no any occurrence.
:signature: string.indexOf(sub, start, length)
:receiverArg string: input string
:argType string: string
:arg sub: substring to find in string
:argType sub: string
:arg start: index to start search with, 0 by default
:argType start: integer
:arg length: length of string to find substring in
:argType length: integer
:returnType: integer
.. code::
yaql> "cabcdab".indexOf("bc", 2, 2)
2
"""
if start < 0:
start += len(string)
if length < 0:
length = len(string) - start
return string.find(sub, start, start + length)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('sub', yaqltypes.String())
@specs.parameter('start', int)
@specs.method
def last_index_of(string, sub, start=0):
""":yaql:lastIndexOf
Returns an index of last occurrence sub in string beginning from start.
-1 is a return value if there is no any occurrence.
:signature: string.lastIndexOf(sub, start => 0)
:receiverArg string: input string
:argType string: string
:arg sub: substring to find in string
:argType sub: string
:arg start: index to start search with, 0 by default
:argType start: integer
:returnType: integer
.. code::
yaql> "cabcdab".lastIndexOf("ab")
5
"""
return string.rfind(sub, start)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('sub', yaqltypes.String())
@specs.parameter('start', int)
@specs.parameter('length', int)
@specs.method
def last_index_of_(string, sub, start, length):
""":yaql:lastIndexOf
Returns an index of last occurrence sub in string beginning from start
ending with start+length.
-1 is a return value if there is no any occurrence.
:signature: string.lastIndexOf(sub, start, length)
:receiverArg string: input string
:argType string: string
:arg sub: substring to find in string
:argType sub: string
:arg start: index to start search with, 0 by default
:argType start: integer
:arg length: length of string to find substring in
:argType length: integer
:returnType: integer
.. code::
yaql> "cabcdbc".lastIndexOf("bc", 2, 5)
5
"""
if start < 0:
start += len(string)
if length < 0:
length = len(string) - start
return string.rfind(sub, start, start + length)
@specs.parameter('string', yaqltypes.String())
@specs.method
def to_char_array(string):
""":yaql:toCharArray
Converts a string to array of one character strings.
:signature: string.toCharArray()
:receiverArg string: input string
:argType string: string
:returnType: list
.. code::
yaql> "abc de".toCharArray()
["a", "b", "c", " ", "d", "e"]
"""
return tuple(string)
def characters(
digits=False, hexdigits=False,
ascii_lowercase=False, ascii_uppercase=False,
ascii_letters=False, letters=False,
octdigits=False, punctuation=False, printable=False,
lowercase=False, uppercase=False, whitespace=False):
""":yaql:characters
Returns a list of all distinct items of specified types.
:signature: characters(digits => false, hexdigits => false,
asciiLowercase => false, asciiUppercase => false,
asciiLetters => false, letters => false,
octdigits => false, punctuation => false,
printable => false, lowercase => false,
uppercase => false, whitespace => false)
:arg digits: include digits in output list if true, false by default
:argType digits: boolean
:arg hexdigits: include hexademical digits in output list if true, false
by default
:argType hexdigits: boolean
:arg asciiLowercase: include ASCII lowercase letters in output list if
true, false by default
:argType asciiLowercase: boolean
:arg asciiUppercase: include ASCII uppercase letters in output list if
true, false by default
:argType asciiUppercase: boolean
:arg asciiLetters: include both ASCII lowercase and uppercase letters
in output list if true, false by default
:argType asciiLetters: boolean
:arg letters: include both lowercase and uppercase letters in output list
if true, false by default
:argType letters: boolean
:arg octdigits: include digits from 0 to 7 in output list if true, false
by default
:argType octdigits: boolean
:arg punctuation: include ASCII characters, which are considered
punctuation, in output list if true, false by default
:argType punctuation: boolean
:arg printable: include digits, letters, punctuation, and whitespace in
output list if true, false by default
:argType printable: boolean
:arg lowercase: include lowercase letters in output list if true, false
by default
:argType lowercase: boolean
:arg uppercase: include uppercase letters in output list if true, false
by default
:argType uppercase: boolean
:arg whitespace: include all characters that are considered whitespace
in output list if true, false by default
:argType whitespace: boolean
:returnType: list
.. code::
yaql> characters(digits => true)
["1", "0", "3", "2", "5", "4", "7", "6", "9", "8"]
"""
string = ''
if digits:
string += string_module.digits
if hexdigits:
string += string_module.hexdigits
if ascii_lowercase:
string += string_module.ascii_lowercase
if ascii_uppercase:
string += string_module.ascii_uppercase
if ascii_letters:
string += string_module.ascii_letters
if letters:
string += string_module.letters
if octdigits:
string += string_module.octdigits
if punctuation:
string += string_module.punctuation
if printable:
string += string_module.printable
if lowercase:
string += string_module.lowercase
if uppercase:
string += string_module.uppercase
if whitespace:
string += string_module.whitespace
return tuple(set(string))
def is_string(arg):
""":yaql:isString
Returns true if arg is a string.
:signature: isString(arg)
:arg arg: input value
:argType arg: any
:returnType: boolean
.. code::
yaql> isString("ab")
true
yaql> isString(1)
false
"""
return isinstance(arg, str)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('prefixes', yaqltypes.String())
@specs.method
def starts_with(string, *prefixes):
""":yaql:startsWith
Returns true if a string starts with any of given args.
:signature: string.startsWith([args])
:receiverArg string: input string
:argType string: string
:arg [args]: chain of strings to check input string with
:argType [args]: strings
:returnType: boolean
.. code::
yaql> "abcd".startsWith("ab", "xx")
true
yaql> "abcd".startsWith("yy", "xx", "zz")
false
"""
return string.startswith(prefixes)
@specs.parameter('string', yaqltypes.String())
@specs.parameter('suffixes', yaqltypes.String())
@specs.method
def ends_with(string, *suffixes):
""":yaql:endsWith
Returns true if a string ends with any of given args.
:signature: string.endsWith([args])
:receiverArg string: input string
:argType string: string
:arg [args]: chain of strings to check input string with
:argType [args]: strings
:returnType: boolean
.. code::
yaql> "abcd".endsWith("cd", "xx")
true
yaql> "abcd".endsWith("yy", "xx", "zz")
false
"""
return string.endswith(suffixes)
@specs.parameter('num', yaqltypes.Number(nullable=True))
def hex_(num):
""":yaql:hex
Returns a string with hexadecimal representation of num.
:signature: hex(num)
:arg num: input number to be converted to hexademical
:argType num: number
:returnType: string
.. code::
yaql> hex(256)
"0x100"
"""
return hex(num)
def register(context):
context.register_function(gt)
context.register_function(lt)
context.register_function(gte)
context.register_function(lte)
context.register_function(len_)
context.register_function(to_lower)
context.register_function(to_upper)
context.register_function(split)
context.register_function(right_split)
context.register_function(join)
context.register_function(join_)
context.register_function(str_)
context.register_function(concat)
context.register_function(concat, name='#operator_+')
context.register_function(trim)
context.register_function(trim_left)
context.register_function(trim_right)
context.register_function(replace)
context.register_function(replace_with_dict)
context.register_function(is_empty)
context.register_function(string_by_int)
context.register_function(int_by_string)
context.register_function(substring)
context.register_function(index_of)
context.register_function(index_of_)
context.register_function(last_index_of)
context.register_function(last_index_of_)
context.register_function(to_char_array)
context.register_function(characters)
context.register_function(is_string)
context.register_function(norm)
context.register_function(in_)
context.register_function(starts_with)
context.register_function(ends_with)
context.register_function(hex_)