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  * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such
25  * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's
26  * parser will consume the iterator created by the parent, reversing the index when it reaches an
27  * unknown token.
28  *
29  * In order to keep subcommands relatively simple and not have to do complicated validation, sub
30  * commands will return control to the parent parser as soon as they discover a token that they do
31  * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments
32  * for a required parameter.
33  */
34 sealed interface SubCommand : Describable {
35     val cmd: ParseableCommand
36 
37     /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */
38     var validationStatus: Boolean
39 
40     /**
41      * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind
42      * the iterator when it yields control upwards
43      */
parseSubCommandArgsnull44     fun parseSubCommandArgs(iterator: ListIterator<String>)
45 }
46 
47 /**
48  * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are
49  * created via adding a fully-formed [ParseableCommand] to parent command.
50  *
51  * At this point in time, I don't recommend nesting subcommands.
52  */
53 class OptionalSubCommand<T : ParseableCommand>(
54     override val cmd: T,
55 ) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> {
56     override val shortName: String? = null
57     override val longName: String = cmd.name
58     override val description: String? = cmd.description
59     override var validationStatus = true
60 
61     private var isPresent = false
62 
63     /** Consume tokens from the iterator and pass them to the wrapped command */
64     override fun parseSubCommandArgs(iterator: ListIterator<String>) {
65         validationStatus = cmd.parser.parseAsSubCommand(iterator)
66         isPresent = true
67     }
68 
69     override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
70         if (isPresent) {
71             cmd
72         } else {
73             null
74         }
75 
76     override fun describe(pw: IndentingPrintWriter) {
77         cmd.help(pw)
78     }
79 }
80 
81 /**
82  * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token
83  * is not present in the incoming command
84  */
85 class RequiredSubCommand<T : ParseableCommand>(
86     override val cmd: T,
87 ) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> {
88     override val shortName: String? = null
89     override val longName: String = cmd.name
90     override val description: String? = cmd.description
91     override var validationStatus = true
92 
93     /** Unhandled, required subcommands are an error */
94     var handled = false
95 
parseSubCommandArgsnull96     override fun parseSubCommandArgs(iterator: ListIterator<String>) {
97         validationStatus = cmd.parser.parseAsSubCommand(iterator)
98         handled = true
99     }
100 
getValuenull101     override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd
102 
103     override fun describe(pw: IndentingPrintWriter) {
104         cmd.help(pw)
105     }
106 }
107