Passed
Push — master ( 48245f...6bb124 )
by torrua
01:22
created

keyboa.keyboards.merge_keyboards_data()   A

Complexity

Conditions 4

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 12
nop 1
dl 0
loc 14
ccs 10
cts 10
cp 1
crap 4
rs 9.8
c 0
b 0
f 0
1
# -*- coding:utf-8 -*-
2 1
"""
3
This module contains all the necessary functions for
4
creating complex and functional inline keyboards.
5
"""
6
# pylint: disable=R0913
7
8 1
from collections.abc import Iterable
9 1
from typing import Union, Optional, Tuple
10
11 1
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
12
13 1
from keyboa.constants import InlineButtonData, CallbackDataMarker, \
14
    BlockItems, DEFAULT_ITEMS_IN_LINE
15 1
from keyboa.functions_alignment import calculate_items_in_row
16 1
from keyboa.functions_button_data import get_text, get_verified_button_tuple
17 1
from keyboa.functions_callback import get_callback_data, get_callback
18 1
from keyboa.functions_precheck import _keyboa_pre_check
19
20
21 1
def button_maker(
22
        button_data: InlineButtonData,
23
        front_marker: CallbackDataMarker = str(),
24
        back_marker: CallbackDataMarker = str(),
25
        copy_text_to_callback: bool = False,
26
) -> InlineKeyboardButton:
27
    """
28
    This function creates an InlineKeyboardButton object from various data types,
29
    such as str, int, tuple, dict.
30
31
    :param button_data: InlineButtonData - an object from which the button will be created:
32
    • If string or an integer, it will be used for both text and callback.
33
    • If tuple, the zero element [0] will be the text, and the first [1] will be the callback.
34
    • If dictionary, there are two options:
35
        > if there is no "text" key in dictionary and only one key exists,
36
            the key will be the text, and the value will be the callback.
37
            In this case, no verification of the dictionary's contents is performed!
38
        > if "text" key exists, function passes the whole dictionary to InlineKeyboardButton,
39
            where dictionary's keys represent object's parameters
40
            and dictionary's values represent parameters' values accordingly.
41
        In all other cases ValueError will be called.
42
43
    :param front_marker: CallbackDataMarker - a string to be added to the left side of callback.
44
        Optional. The default value is None.
45
46
    :param back_marker: CallbackDataMarker - a string to be added to the right side of callback.
47
        Optional. The default value is None.
48
49
    :param copy_text_to_callback: If enabled and button_data is a string or integer,
50
        function will copy button text to callback data (and add markers if they exist).
51
        Optional. The default value is False.
52
53
    :return: InlineKeyboardButton
54
55
    Covered by tests.
56
    """
57
58 1
    if isinstance(button_data, InlineKeyboardButton):
59 1
        return button_data
60
61 1
    if isinstance(button_data, dict) and button_data.get("text"):
62 1
        return InlineKeyboardButton(**button_data)
63
64 1
    button_tuple = get_verified_button_tuple(button_data, copy_text_to_callback)
65
66 1
    text = get_text(button_tuple)
67 1
    raw_callback = get_callback(button_tuple)
68 1
    callback_data = get_callback_data(raw_callback, front_marker, back_marker)
69
70 1
    prepared_button = {"text": text, "callback_data": callback_data}
71
72 1
    return InlineKeyboardButton(**prepared_button)
73
74
75 1
def keyboa_maker(
76
        items: BlockItems = None,
77
        front_marker: CallbackDataMarker = None,
78
        back_marker: CallbackDataMarker = None,
79
80
        items_in_row: int = None,
81
        auto_alignment: Union[bool, Iterable] = False,
82
        reverse_alignment_range: bool = False,
83
        slice_start: int = None,
84
        slice_stop: int = None,
85
        slice_step: int = None,
86
87
        copy_text_to_callback: bool = False,
88
        add_to_keyboard: InlineKeyboardMarkup = None,
89
) -> InlineKeyboardMarkup:
90
    """
91
    :param items:
92
    :param front_marker:
93
    :param back_marker:
94
    :param items_in_row:
95
    :param auto_alignment:
96
    :param reverse_alignment_range:
97
    :param slice_start:
98
    :param slice_stop:
99
    :param slice_step:
100
    :param copy_text_to_callback:
101
    :param add_to_keyboard:
102
    :return:
103
    """
104 1
    keyboard = add_to_keyboard if add_to_keyboard else InlineKeyboardMarkup()
105
106 1
    if items is None:
107 1
        return keyboard
108
109 1
    if items and not isinstance(items, list):
110 1
        items = [items, ]
111
112 1
    items = items[slice_start:slice_stop:slice_step] if items else items
113
114 1
    _keyboa_pre_check(items=items, items_in_row=items_in_row, keyboard=keyboard)
115
116 1
    if items_in_row or auto_alignment:
117 1
        return get_generated_keyboard(
118
            items, front_marker, back_marker, items_in_row, auto_alignment,
119
            reverse_alignment_range, copy_text_to_callback, keyboard)
120
121 1
    return get_preformatted_keyboard(
122
        items, front_marker, back_marker,
123
        copy_text_to_callback, keyboard)
124
125
126 1
def get_preformatted_keyboard(
127
        items, front_marker, back_marker,
128
        copy_text_to_callback, keyboard):
129
    """
130
    :param items:
131
    :param front_marker:
132
    :param back_marker:
133
    :param copy_text_to_callback:
134
    :param keyboard:
135
    :return:
136
    """
137 1
    for index, item in enumerate(items):
138 1
        if not isinstance(item, list):
139 1
            items[index] = [item, ]
140 1
    for row in items:
141 1
        buttons = [button_maker(
142
            button_data=item,
143
            front_marker=front_marker,
144
            back_marker=back_marker,
145
            copy_text_to_callback=copy_text_to_callback
146
        ) for item in row]
147 1
        keyboard.row(*buttons)
148 1
    return keyboard
149
150
151 1
def get_generated_keyboard(
152
        items, front_marker, back_marker, items_in_row,
153
        auto_alignment, reverse_alignment_range,
154
        copy_text_to_callback, keyboard):
155
    """
156
    :param items:
157
    :param front_marker:
158
    :param back_marker:
159
    :param items_in_row:
160
    :param auto_alignment:
161
    :param reverse_alignment_range:
162
    :param copy_text_to_callback:
163
    :param keyboard:
164
    :return:
165
    """
166
167 1
    if auto_alignment:
168 1
        items_in_row = calculate_items_in_row(items, auto_alignment, reverse_alignment_range)
169
170 1
    if not items_in_row:
171 1
        items_in_row = DEFAULT_ITEMS_IN_LINE
172
173 1
    rows_in_keyboard = (len(items) // items_in_row)
174 1
    buttons = [button_maker(
175
        button_data=item,
176
        front_marker=front_marker,
177
        back_marker=back_marker,
178
        copy_text_to_callback=copy_text_to_callback,
179
    ) for item in items]
180 1
    for _row in range(rows_in_keyboard):
181 1
        keyboard.row(*[buttons.pop(0) for _button in range(items_in_row)])
182 1
    keyboard.row(*buttons)
183 1
    return keyboard
184
185
186 1
def keyboa_combiner(
187
        keyboards: Optional[Union[Tuple[InlineKeyboardMarkup, ...], InlineKeyboardMarkup]] = None
188
) -> InlineKeyboardMarkup:
189
    """
190
    This function combines multiple InlineKeyboardMarkup objects into one.
191
192
    :param keyboards: Sequence of InlineKeyboardMarkup objects.
193
        Also could be presented as a standalone InlineKeyboardMarkup.
194
195
    :return: InlineKeyboardMarkup
196
    """
197
198 1
    if keyboards is None:
199 1
        return InlineKeyboardMarkup()
200
201 1
    if isinstance(keyboards, InlineKeyboardMarkup):
202 1
        keyboards = (keyboards, )
203
204 1
    data = merge_keyboards_data(keyboards)
205
206 1
    return keyboa_maker(data)
207
208
209 1
def merge_keyboards_data(keyboards):
210 1
    data = []
211 1
    for keyboard in keyboards:
212 1
        if keyboard is None:
213 1
            continue
214
215 1
        if not isinstance(keyboard, InlineKeyboardMarkup):
216 1
            type_error_message = \
217
                "Keyboard element cannot be %s. Only InlineKeyboardMarkup allowed." \
218
                % type(keyboard)
219 1
            raise TypeError(type_error_message)
220
221 1
        data.extend(keyboard.keyboard)
222
    return data
223