1 /* 2 * Copyright (C) 2023 The Android Open Source Project 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 */ 16 17 package com.android.systemui.statusbar.commandline 18 19 import android.util.IndentingPrintWriter 20 import kotlin.properties.ReadOnlyProperty 21 import kotlin.reflect.KProperty 22 23 /** 24 * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line 25 * tokens that accept a fixed number of arguments and convert them to a parsed type. 26 * 27 * Example: 28 * ``` 29 * my_command --single-arg-param arg 30 * ``` 31 * 32 * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter, 33 * and `arg` is the value parsed by that parameter into its eventual type. 34 * 35 * Note on generics: The intended usage for parameters is to be able to return the parsed type from 36 * the given command as a `val` via property delegation. For example, let's say we have a command 37 * that has one optional and one required parameter: 38 * ``` 39 * class MyCommand : ParseableCommand { 40 * val requiredParam: Int by parser.param(...).required() 41 * val optionalParam: Int? by parser.param(...) 42 * } 43 * ``` 44 * 45 * In order to make the simple `param` method return the correct type, we need to do two things: 46 * 1. Break out the generic type into 2 pieces (TParsed and T) 47 * 2. Create two different underlying Parameter subclasses to handle the property delegation. One 48 * handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null 49 * since the value parsed from the argument will throw an exception if missing or if it cannot be 50 * parsed. 51 */ 52 53 /** A param type knows the number of arguments it expects */ 54 sealed interface Param : Describable { 55 val numArgs: Int 56 57 /** 58 * Consume [numArgs] items from the iterator and relay the result into its corresponding 59 * delegated type. 60 */ parseArgsFromIternull61 fun parseArgsFromIter(iterator: Iterator<String>) 62 } 63 64 /** 65 * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is 66 * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing 67 * in a single place, and yet on the client side we can unwrap the underlying list of params 68 * automatically. 69 */ 70 abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) : 71 Param, ReadOnlyProperty<Any?, T> { 72 var handled = false 73 74 override fun describe(pw: IndentingPrintWriter) { 75 if (shortName != null) { 76 pw.print("$shortName, ") 77 } 78 pw.print(longName) 79 pw.println(" ${typeDescription()}") 80 if (description != null) { 81 pw.indented { pw.println(description) } 82 } 83 } 84 85 /** 86 * Try to describe the arg type. We can know if it's one of the base types what kind of input it 87 * takes. Otherwise just print "<arg>" and let the clients describe in the help text 88 */ 89 private fun typeDescription() = 90 when (wrapped.valueParser) { 91 Type.Int -> "<int>" 92 Type.Float -> "<float>" 93 Type.String -> "<string>" 94 Type.Boolean -> "<boolean>" 95 else -> "<arg>" 96 } 97 } 98 99 /** Required single-arg parameter, delegating a non-null type to the client. */ 100 class SingleArgParam<out T : Any>( 101 override val longName: String, 102 override val shortName: String? = null, 103 override val description: String? = null, 104 val valueParser: ValueParser<T>, 105 ) : 106 UnaryParamBase<T, T>( 107 MultipleArgParam( 108 longName, 109 shortName, 110 1, 111 description, 112 valueParser, 113 ) 114 ) { 115 getValuenull116 override fun getValue(thisRef: Any?, property: KProperty<*>): T = 117 if (handled) { 118 wrapped.getValue(thisRef, property)[0] 119 } else { 120 throw IllegalStateException("Attempt to read property before parse() has executed") 121 } 122 123 override val numArgs: Int = 1 124 parseArgsFromIternull125 override fun parseArgsFromIter(iterator: Iterator<String>) { 126 wrapped.parseArgsFromIter(iterator) 127 handled = true 128 } 129 } 130 131 /** Optional single-argument parameter, delegating a nullable type to the client. */ 132 class SingleArgParamOptional<out T : Any>( 133 override val longName: String, 134 override val shortName: String? = null, 135 override val description: String? = null, 136 val valueParser: ValueParser<T>, 137 ) : 138 UnaryParamBase<T?, T>( 139 MultipleArgParam( 140 longName, 141 shortName, 142 1, 143 description, 144 valueParser, 145 ) 146 ) { getValuenull147 override fun getValue(thisRef: Any?, property: KProperty<*>): T? = 148 wrapped.getValue(thisRef, property).getOrNull(0) 149 150 override val numArgs: Int = 1 151 152 override fun parseArgsFromIter(iterator: Iterator<String>) { 153 wrapped.parseArgsFromIter(iterator) 154 handled = true 155 } 156 } 157 158 /** 159 * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list 160 * of type [TParsed]. 161 * 162 * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a 163 * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the 164 * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going 165 * to be parsed into T rather than T?. 166 */ 167 class MultipleArgParam<out T, out TParsed : T>( 168 override val longName: String, 169 override val shortName: String? = null, 170 override val numArgs: Int = 1, 171 override val description: String? = null, 172 val valueParser: ValueParser<TParsed>, 173 ) : ReadOnlyProperty<Any?, List<TParsed>>, Param { 174 private val inner: MutableList<TParsed> = mutableListOf() 175 getValuenull176 override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner 177 178 /** 179 * Consumes [numArgs] values of the iterator and parses them into [TParsed]. 180 * 181 * @throws ArgParseError on the first failure 182 */ 183 override fun parseArgsFromIter(iterator: Iterator<String>) { 184 if (!iterator.hasNext()) { 185 throw ArgParseError("no argument provided for $shortName") 186 } 187 for (i in 0 until numArgs) { 188 valueParser 189 .parseValue(iterator.next()) 190 .fold(onSuccess = { inner.add(it) }, onFailure = { throw it }) 191 } 192 } 193 } 194 195 data class ArgParseError(override val message: String) : Exception(message) 196