NumberValidator.validate()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 7
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 7
nop 2
dl 0
loc 7
ccs 0
cts 0
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2 1
import os
3 1
import sys
4
5 1
from questionary import ValidationError, Validator, prompt
6
7
8
class InputFactory(object):
9
    __instance = None
10 1
11 1
    def __new__(cls, *args, **kwargs):
12
        if not cls.__instance:
13 1
            cls.__instance = super(InputFactory, cls).__new__(cls)
14 1
            if sys.version_info.major == 2:
15 1
                raise RuntimeError("Music Album Creator does not support legacy Python 2")
16 1
                cls.__instance._input = raw_input  # NOQA
17
            else:
18
                cls.__instance._input = input
19 1
        return cls.__instance
20 1
21
    def __call__(self, *args):
22 1
        return self._input(*args)
23
24
25
ask_input = InputFactory()
26 1
27
28
class DialogCommander:
29 1
    @classmethod
30
    def logo(cls):
31 1
        print(
32
            "\
33
╔═╗╦  ╔╗ ╦ ╦╔╦╗  ╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗╦═╗\n\
34
╠═╣║  ╠╩╗║ ║║║║  ║  ╠╦╝║╣ ╠═╣ ║ ║ ║╠╦╝\n\
35
╩ ╩╩═╝╚═╝╚═╝╩ ╩  ╚═╝╩╚═╚═╝╩ ╩ ╩ ╚═╝╩╚═\
36
            "
37
        )
38
39
    @classmethod
40 1
    def input_youtube_url_dialog(cls):
41
        return ask_input(
42
            'Please input a url corresponding to a music album uploaded as a youtube video.\n   video url: '
43
        )
44
45 1
    ## HANDLE Token Error with update youtube-dl and retry download same url dialog
46
    @classmethod
47
    def update_and_retry_dialog(cls):
48
        questions = [
49
            {
50
                'type': 'confirm',
51
                'name': 'update-youtube-dl',
52
                'message': "Update 'youtube-dl' backend?)",
53
                'default': True,
54
            }
55
        ]
56
        answer = prompt(questions)
57
        return answer
58
59
    ##### MULTILINE INPUT TRACK NAMES AND TIMESTAMPS (hh:mm:ss)
60 1
    @classmethod
61 1
    def track_information_type_dialog(cls):
62
        """Returns a parser of track hh:mm:ss multiline string.
63
64
        Type of format (types: "Durations", "Timestamps") you prefer to input
65
        for providing the necessary information to segment an album
66
        """
67
        choices = ['Timestamps', 'Durations']
68
        questions = [
69
            {
70
                'type': 'list',  # navigate with arrows through choices
71
                'name': 'how-to-input-tracks',
72
                'message': 'What does the expected "hh:mm:ss" input represent?',
73
                'choices': choices,
74
            }
75
        ]
76
        answers = prompt(questions)
77
        return answers['how-to-input-tracks']
78
79
    @classmethod
80
    def interactive_track_info_input_dialog(cls):
81 1
        print(
82
            "Enter/Paste your 'track_name - hh:mm:ss' pairs. Each line should represent a single track with format 'trackname - hh:mm:ss'. "
83
            "The assumption is that each track is defined either in terms of a timestamp correspoding to the starting point within the full album, or in terms of its actuall playtime length. Then navigate one line below your last track and press Ctrl-D (or Ctrl-Z on windows) to save it.\n"
84
        )
85
86
        def input_lines(prompt_=None):
87
            """Yields input lines from user until EOFError is raised."""
88
            while True:
89
                try:
90
                    yield ask_input() if prompt_ is None else ask_input(prompt_)
91
                except EOFError:
92
                    break
93
                else:
94
                    prompt_ = None  # Only display prompt while reading first line.
95
96
        def multiline_input(prompt_=None):
97
            """Reads a multi-line input from the user."""
98
            return os.linesep.join(input_lines(prompt_=prompt_))
99
100
        return multiline_input()  # '\n' separable string
101
102
    ####################################################################
103
104 1
    @classmethod
105 1
    def album_directory_path_dialog(cls, music_lib, artist='', album='', year=''):
106
        if year:
107
            album = '{} ({})'.format(album, year)
108
        else:
109
            album = album
110
        return prompt(
111
            [
112
                {
113
                    'type': 'input',
114
                    'name': 'create-album-dir',
115
                    'message': 'Please give album directory path',
116 1
                    'default': os.path.join(music_lib, artist, album),
117
                }
118
            ]
119
        )['create-album-dir']
120
121
    @classmethod
122
    def confirm_copy_tracks_dialog(cls, destination_directory):
123 1
        return prompt(
124 1
            [
125
                {
126
                    'type': 'confirm',
127
                    'name': 'copy-in-existant-dir',
128
                    'message': "Directory '{}' exists. Copy the tracks there?".format(
129
                        destination_directory
130
                    ),
131
                    'default': True,
132
                }
133
            ]
134
        )['copy-in-existant-dir']
135
136
    @classmethod
137
    def interactive_metadata_dialogs(cls, artist='', album='', year=''):
138
        questions = [
139
            {
140
                'type': 'confirm',
141
                'name': 'add-metadata',
142
                'message': 'Do you want to add metadata, such as artist, track names, to the audio files?',
143
                'default': True,
144
            },
145
            {
146
                'type': 'checkbox',
147
                'name': 'automatic-metadata',
148
                'message': 'Infer from audio files',
149
                'when': lambda answers: bool(answers['add-metadata']),
150
                'choices': [
151
                    {'name': 'track numbers', 'checked': True},
152
                    {'name': 'track names', 'checked': True},
153
                ],
154
            },
155
            {
156
                'type': 'input',
157
                'name': 'artist',
158
                'default': artist,
159
                'message': "'artist' tag",
160
            },
161
            {
162
                'type': 'input',
163
                'name': 'album-artist',
164
                'message': "'album artist' tag",
165
                'default': lambda x: x['artist'],
166
            },
167
            {
168
                'type': 'input',
169
                'name': 'album',
170
                'default': album,
171
                'message': "'album' tag",
172
            },
173
            {
174
                'type': 'input',
175
                'name': 'year',
176
                'message': "'year' tag",
177
                'default': year,  # trick to allow empty value
178 1
                'validate': NumberValidator,
179 1
                # 'filter': lambda val: int(val)
180
            },
181
        ]
182
        return prompt(questions)
183
184
185
class NumberValidator(Validator):
186
    def validate(self, document):
187
        if document.text != '':  # trick to allow empty value
188
            try:
189
                int(document.text)
190
            except ValueError:
191
                raise ValidationError(
192
                    message='Please enter a number', cursor_position=len(document.text)
193
                )  # Move cursor to end
194