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