• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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