# 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_)