InteractiveCommand::read()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 5
eloc 18
c 1
b 0
f 1
nc 8
nop 3
dl 0
loc 24
rs 9.3554
1
<?php namespace Tarsana\Command\Commands;
2
3
use Tarsana\Command\Helpers\SyntaxHelper;
4
use Tarsana\Command\SubCommand;
5
use Tarsana\Syntax\ArraySyntax;
6
use Tarsana\Syntax\Factory as S;
7
use Tarsana\Syntax\ObjectSyntax;
8
use Tarsana\Syntax\OptionalSyntax;
9
use Tarsana\Syntax\Syntax;
10
11
class InteractiveCommand extends SubCommand {
12
13
    const KEYS = [
14
        10  => 'enter',
15
        127 => 'backspace',
16
        65  => 'up',
17
        66  => 'down',
18
        67  => 'right',
19
        68  => 'left',
20
        9   => 'tab'
21
    ];
22
23
    protected $helper;
24
    protected $confirmSyntax;
25
26
    protected function init()
27
    {
28
        $this->name('Interactive')
29
             ->description('Reads the command arguments and options interactively.');
30
        $this->helper = SyntaxHelper::instance();
31
        $this->confirmSyntax = S::optional(S::boolean(), false);
32
    }
33
34
    protected function setupSubCommands()
35
    {
36
        return $this;
37
    }
38
39
    protected function execute()
40
    {
41
        $parent = $this->parent;
42
        $syntax = $parent->syntax();
43
        $this->console->out('<save>');
44
45
        if ($syntax) {
46
            $args = $this->read($syntax);
47
            $parent->args($args);
48
        }
49
50
        $options = array_keys($parent->options());
51
        $chosen = [];
52
        foreach($options as $option) {
53
            $bool = $this->read($this->confirmSyntax, $option, true);
54
            $parent->options[$option] = $bool;
55
            if ($bool) {
56
                $chosen[] = $option;
57
            }
58
        }
59
60
        $options = implode(' ', $chosen) . ' ';
61
        $args = $syntax ? $syntax->dump($args) : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $args does not seem to be defined for all execution paths leading up to this point.
Loading history...
62
63
        $this->console->out('<load><clearAfter>');
64
        $this->console->line("> {$options}{$args}<br>");
65
66
        return $this->parent->fire();
67
    }
68
69
    protected function read(Syntax $syntax, string $prefix = '', bool $display = false)
70
    {
71
        if ($display) {
72
            $this->display($syntax, $prefix);
73
        }
74
75
        $type = $this->helper->type($syntax);
76
        $result = null;
77
        switch ($type) {
78
            case 'object':
79
                $result = $this->readObject($syntax, $prefix);
80
            break;
81
            case 'array':
82
                $result = $this->readArray($syntax, $prefix);
83
            break;
84
            case 'optional':
85
                $result = $this->readOptional($syntax, $prefix);
86
            break;
87
            default:
88
                $result = $this->readSimple($syntax);
89
            break;
90
        }
91
92
        return $result;
93
    }
94
95
    protected function display(Syntax $syntax, string $name)
96
    {
97
        $text = $this->helper->asString($syntax);
98
        $default = '';
99
        if ($syntax instanceof OptionalSyntax) {
100
            $default = '(default: ' . json_encode($syntax->getDefault()) . ')';
101
        }
102
        $description = $this->parent->describe($name);
103
        $this->console->out(
104
            "<success>{$name}</success> <warn>{$text}</warn>"
105
          . " {$description} <warn>{$default}</warn><br>"
106
        );
107
    }
108
109
    protected function readObject(ObjectSyntax $syntax, string $prefix)
110
    {
111
        $result = [];
112
        if ($prefix != '')
113
            $prefix .= '.';
114
        foreach ($syntax->fields() as $name => $s) {
115
            $fullname = $prefix . $name;
116
            $result[$name] = $this->read($s, $fullname, true);
117
        }
118
        return (object) $result;
119
    }
120
121
    protected function readArray(ArraySyntax $syntax, string $prefix)
122
    {
123
        $result = [];
124
        $repeat = true;
125
        while ($repeat) {
126
            $result[] = $this->read($syntax->syntax(), $prefix);
127
            $this->console->out("Add new item to <success>{$prefix}</success>?<br>");
128
            $repeat = $this->readOptional($this->confirmSyntax, '');
129
            $this->clearLines(3);
130
        }
131
        return $result;
132
    }
133
134
    protected function readOptional(OptionalSyntax $syntax, string $prefix)
135
    {
136
        $default = $syntax->syntax()->dump($syntax->getDefault());
137
        $this->console->out("<color:252>{$default}<column:1><reset>");
138
        $n = ord($this->console->char());
139
        $this->console->out('<column:1><clearLine>');
140
        if (array_key_exists($n, static::KEYS) && static::KEYS[$n] == 'enter')
141
            return $syntax->getDefault();
142
        return $this->read($syntax->syntax(), $prefix);
143
    }
144
145
    protected function readSimple(Syntax $syntax)
146
    {
147
        $this->console->out('<column:1>> ');
148
        $done = false;
149
        $text = '';
150
        $result = null;
151
        while (! $done) {
152
            $c = $this->readChar();
153
            switch($c) {
154
                case 'enter':
155
                    $done = true;
156
                break;
157
                case 'backspace':
158
                    $text = substr($text, 0, -1);
159
                break;
160
                default:
161
                    $text .= $c;
162
                break;
163
            }
164
165
            try {
166
                $result = $syntax->parse($text);
167
                $this->clearLines(1);
168
                $this->console->out("> {$text}");
169
            } catch (\Exception $e) {
170
                $this->clearLines(1);
171
                $this->console->out("> <warn>{$text}</warn>");
172
                $done = false;
173
            }
174
        }
175
        $this->console->out('<br>');
176
177
        return $result;
178
    }
179
180
    protected function readChar() : string
181
    {
182
        $c = $this->console->char();
183
        if (ctype_print($c))
184
            return $c;
185
        $n = ord($c);
186
        if (
187
            array_key_exists($n, static::KEYS)
188
            && in_array(static::KEYS[$n], ['enter', 'backspace'])
189
        ) {
190
            return static::KEYS[$n];
191
        }
192
        return '';
193
    }
194
195
    protected function clearLines(int $number)
196
    {
197
        $text = '<clearLine>'
198
            . str_repeat('<prevLine><clearLine>', $number - 1)
199
            . '<column:1>';
200
        $this->console->out($text);
201
    }
202
}
203