Passed
Push — master ( 9edbb3...aa10e3 )
by Daimona
02:01
created

CLI::checkRequiredOpts()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 16
rs 9.2222
1
<?php declare( strict_types=1 );
2
3
namespace BotRiconferme;
4
5
/**
6
 * CLI helper
7
 *
8
 * Example options:
9
 *
10
 * 'username' => 'BotRiconferme'
11
 * 'list-title' => 'Utente:BotRiconferme/List.json',
12
 * 'config-title' => 'Utente:BotRiconferme/Config.json',
13
 * 'msg-title' => 'Utente:BotRiconferme/Messages.json"
14
 *
15
 * --password=(BotPassword)
16
 * OR
17
 * --use-password-file
18
 * which will look for a PASSWORD_FILE file in the current directory containing only the plain password
19
 *
20
 * --private-password=(BotPassword)
21
 * OR
22
 * --use-private-password-file
23
 * which will look for a PRIVATE_PASSWORD_FILE file in the current directory containing only the plain password
24
 *
25
 * --task=update-list
26
 * OR
27
 * --subtask=user-notice
28
 */
29
class CLI {
30
	public const SHORT_OPTS = '';
31
32
	public const LONG_OPTS = [
33
		'username:',
34
		'list-title:',
35
		'config-title:',
36
		'msg-title:',
37
38
		'force-url:',
39
40
		'password:',
41
		'use-password-file',
42
		'private-password:',
43
		'use-private-password-file',
44
45
		'error-title:',
46
47
		'task:',
48
		'subtask:'
49
	];
50
51
	public const REQUIRED_OPTS = [
52
		'username',
53
		'list-title',
54
		'config-title',
55
		'msg-title',
56
	];
57
58
	/** @todo Make it customizable? */
59
	public const PASSWORD_FILE = __DIR__ . '/../password.txt';
60
	public const PRIVATE_PASSWORD_FILE = __DIR__ . '/../private-password.txt';
61
62
	/** @var array */
63
	private $opts;
64
65
	/**
66
	 * @return bool
67
	 */
68
	public static function isCLI() : bool {
69
		return PHP_SAPI === 'cli';
70
	}
71
72
	/**
73
	 * Populate options and check for required ones
74
	 */
75
	public function __construct() {
76
		$opts = getopt( self::SHORT_OPTS, self::LONG_OPTS );
77
		$this->checkRequiredOpts( $opts );
78
		$this->checkConflictingOpts( $opts );
79
		$this->canonicalize( $opts );
80
		$this->opts = $opts;
81
	}
82
83
	/**
84
	 * @param array $opts
85
	 */
86
	private function checkRequiredOpts( array $opts ) : void {
87
		$missingOpts = array_diff( self::REQUIRED_OPTS, array_keys( $opts ) );
88
		if ( $missingOpts ) {
89
			exit( 'Required options missing: ' . implode( ', ', $missingOpts ) );
90
		}
91
92
		$hasPw = array_key_exists( 'password', $opts );
93
		$hasPwFile = array_key_exists( 'use-password-file', $opts );
94
		if ( !$hasPw && !$hasPwFile ) {
95
			exit( 'Please provide a password or use a password file' );
96
		}
97
98
		$hasPrivatePw = array_key_exists( 'private-password', $opts );
99
		$hasPrivatePwFile = array_key_exists( 'use-private-password-file', $opts );
100
		if ( !$hasPrivatePw && !$hasPrivatePwFile ) {
101
			exit( 'Please provide a private password or use a private-password file' );
102
		}
103
	}
104
105
	/**
106
	 * @param array $opts
107
	 */
108
	private function checkConflictingOpts( array $opts ) : void {
109
		$hasPw = array_key_exists( 'password', $opts );
110
		$hasPwFile = array_key_exists( 'use-password-file', $opts );
111
		if ( $hasPw && $hasPwFile ) {
112
			exit( 'Can only use one of "password" and "use-password-file"' );
113
		} elseif ( $hasPwFile && !file_exists( self::PASSWORD_FILE ) ) {
114
			exit( 'Please create the password file (' . self::PASSWORD_FILE . ')' );
115
		}
116
117
		$hasPrivatePw = array_key_exists( 'private-password', $opts );
118
		$hasPrivatePwFile = array_key_exists( 'use-private-password-file', $opts );
119
		if ( $hasPrivatePw && $hasPrivatePwFile ) {
120
			exit( 'Can only use one of "private-password" and "use-private-password-file"' );
121
		} elseif ( $hasPrivatePwFile && !file_exists( self::PRIVATE_PASSWORD_FILE ) ) {
122
			exit( 'Please create the private-password file (' . self::PRIVATE_PASSWORD_FILE . ')' );
123
		}
124
125
		if ( count( array_intersect_key( $opts, [ 'task' => 1, 'subtask' => 1 ] ) ) === 2 ) {
126
			exit( 'Cannot specify both task and subtask.' );
127
		}
128
	}
129
130
	/**
131
	 * @param array &$opts
132
	 */
133
	private function canonicalize( array &$opts ) : void {
134
		if ( array_key_exists( 'use-password-file', $opts ) ) {
135
			$pw = trim( file_get_contents( self::PASSWORD_FILE ) );
136
			$opts['password'] = $pw;
137
			unset( $opts['use-password-file'] );
138
		}
139
		if ( array_key_exists( 'use-private-password-file', $opts ) ) {
140
			$pw = trim( file_get_contents( self::PRIVATE_PASSWORD_FILE ) );
141
			$opts['private-password'] = $pw;
142
			unset( $opts['use-private-password-file'] );
143
		}
144
	}
145
146
	/**
147
	 * @param string $opt
148
	 * @param mixed|null $default
149
	 * @return mixed
150
	 */
151
	public function getOpt( string $opt, $default = null ) {
152
		return $this->opts[$opt] ?? $default;
153
	}
154
155
	/**
156
	 * @return array Either [ 'task' => taskname ] or [ 'subtask' => subtaskname ]
157
	 */
158
	public function getTaskOpt() : array {
159
		return array_intersect_key(
160
			$this->opts,
161
			[ 'task' => true, 'subtask' => true ]
162
		);
163
	}
164
165
	/**
166
	 * @return string|null
167
	 */
168
	public function getURL() : ?string {
169
		return $this->getOpt( 'force-url' );
170
	}
171
}
172