1#  Copyright 2008-2015 Nokia Networks
2#  Copyright 2016-     Robot Framework Foundation
3#
4#  Licensed under the Apache License, Version 2.0 (the "License");
5#  you may not use this file except in compliance with the License.
6#  You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10#  Unless required by applicable law or agreed to in writing, software
11#  distributed under the License is distributed on an "AS IS" BASIS,
12#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#  See the License for the specific language governing permissions and
14#  limitations under the License.
15
16from __future__ import division
17
18from operator import add, sub
19
20from .platform import PY2
21from .robottypes import is_integer
22from .unic import unic
23
24
25def roundup(number, ndigits=0, return_type=None):
26    """Rounds number to the given number of digits.
27
28    Numbers equally close to a certain precision are always rounded away from
29    zero. By default return value is float when ``ndigits`` is positive and
30    int otherwise, but that can be controlled with ``return_type``.
31
32    With the built-in ``round()`` rounding equally close numbers as well as
33    the return type depends on the Python version.
34    """
35    result = _roundup(number, ndigits)
36    if not return_type:
37        return_type = float if ndigits > 0 else int
38    return return_type(result)
39
40
41# Python 2 rounds half away from zero (as taught in school) but Python 3
42# uses "bankers' rounding" that rounds half towards the even number. We want
43# consistent rounding and expect Python 2 style to be more familiar for users.
44if PY2:
45    _roundup = round
46else:
47    def _roundup(number, ndigits):
48        precision = 10 ** (-1 * ndigits)
49        if number % (0.5 * precision) == 0 and number % precision != 0:
50            operator = add if number > 0 else sub
51            number = operator(number, 0.1 * precision)
52        return round(number, ndigits)
53
54
55def printable_name(string, code_style=False):
56    """Generates and returns printable name from the given string.
57
58    Examples:
59    'simple'           -> 'Simple'
60    'name with spaces' -> 'Name With Spaces'
61    'more   spaces'    -> 'More Spaces'
62    'Cases AND spaces' -> 'Cases AND Spaces'
63    ''                 -> ''
64
65    If 'code_style' is True:
66
67    'mixedCAPSCamel'   -> 'Mixed CAPS Camel'
68    'camelCaseName'    -> 'Camel Case Name'
69    'under_score_name' -> 'Under Score Name'
70    'under_and space'  -> 'Under And Space'
71    'miXed_CAPS_nAMe'  -> 'MiXed CAPS NAMe'
72    ''                 -> ''
73    """
74    if code_style and '_' in string:
75        string = string.replace('_', ' ')
76    parts = string.split()
77    if code_style and len(parts) == 1 \
78            and not (string.isalpha() and string.islower()):
79        parts = _split_camel_case(parts[0])
80    return ' '.join(part[0].upper() + part[1:] for part in parts)
81
82
83def _split_camel_case(string):
84    tokens = []
85    token = []
86    for prev, char, next in zip(' ' + string, string, string[1:] + ' '):
87        if _is_camel_case_boundary(prev, char, next):
88            if token:
89                tokens.append(''.join(token))
90            token = [char]
91        else:
92            token.append(char)
93    if token:
94        tokens.append(''.join(token))
95    return tokens
96
97
98def _is_camel_case_boundary(prev, char, next):
99    if prev.isdigit():
100        return not char.isdigit()
101    if char.isupper():
102        return next.islower() or prev.isalpha() and not prev.isupper()
103    return char.isdigit()
104
105
106def plural_or_not(item):
107    count = item if is_integer(item) else len(item)
108    return '' if count == 1 else 's'
109
110
111def seq2str(sequence, quote="'", sep=', ', lastsep=' and '):
112    """Returns sequence in format `'item 1', 'item 2' and 'item 3'`."""
113    sequence = [quote + unic(item) + quote for item in sequence]
114    if not sequence:
115        return ''
116    if len(sequence) == 1:
117        return sequence[0]
118    last_two = lastsep.join(sequence[-2:])
119    return sep.join(sequence[:-2] + [last_two])
120
121
122def seq2str2(sequence):
123    """Returns sequence in format `[ item 1 | item 2 | ... ]`."""
124    if not sequence:
125        return '[ ]'
126    return '[ %s ]' % ' | '.join(unic(item) for item in sequence)
127