Command 構造体・ツリートポロジ・コマンドグループ

cobra.Command 構造体(command.go)は Cobra の中核となるデータ構造です。ルートバイナリ、中間カテゴリコマンド、末端のアクションコマンド、追加ヘルプトピックのすべてがメモリ上の N 分木における *cobra.Command ノードとして表現されます。

01_command_struct_1
cobra.Command 構造体の全体像 — 識別・フラグ解析・5段階フックを構成する全 35 公開フィールド
Source Code
from drawlib.canvas import save, setup
from drawlib.icons import phosphor
from drawlib.smartarts import CardList, Panel
from drawlib.styles import Colors, Styles

setup(width=188, height=78)

_ = Colors

col_specs = [
    (
        4.0,
        "1. 識別・ツリー・グループ",
        Styles.PrimaryNeutral,
        Styles.PrimaryFlat,
        Styles.WhiteBold.patch(text_size=11.0),
        phosphor.tree_structure,
        Styles.WhiteBold,
        [
            ("Use, Short, Long, Example", "構文シグネチャとヘルプ本文", phosphor.file_text, True),
            ("Aliases, SuggestFor, GroupID", "別名解決とグループ表示制御", phosphor.tag, False),
            ("Version & Annotations", "--version 自動生成とメタデータ", phosphor.bookmarks, False),
        ],
    ),
    (
        65.5,
        "2. フラグ・引数・解析制御",
        Styles.Neutral,
        Styles.SecondaryNeutral,
        Styles.DarkBold.patch(text_size=11.0),
        phosphor.sliders_horizontal,
        Styles.PrimaryBold,
        [
            ("Args & ValidArgs[Function]", "位置引数検証と動的補完関数", phosphor.list_checks, False),
            ("SilenceErrors / SilenceUsage", "エラー・Usage 出力の抑止", phosphor.speaker_slash, False),
            ("Disable* & TraverseChildren", "フラグ解析と親走査の制御", phosphor.gear, False),
        ],
    ),
    (
        127.0,
        "3. 5段階ライフサイクルフック",
        Styles.Neutral,
        Styles.SecondaryNeutral,
        Styles.DarkBold.patch(text_size=11.0),
        phosphor.play_circle,
        Styles.PrimaryBold,
        [
            ("PersistentPreRun[E]", "親から継承される事前フック", phosphor.arrow_bend_down_right, False),
            ("PreRun[E] & Run[E]", "ローカル事前処理とメイン処理", phosphor.play, False),
            ("PostRun & PersistentPostRun", "ローカル・継承事後フック", phosphor.check_circle, False),
        ],
    ),
]

for x0, title, body_style, hdr_style, hdr_text_style, hdr_icon, hdr_icon_style, items in col_specs:
    panel = Panel(
        title,
        "",
        style=body_style,
        header_style=hdr_style,
        text_style=hdr_text_style,
        icon=hdr_icon,
        icon_style=hdr_icon_style,
        header_variant="filled",
        header_height=8.5,
    )
    panel.draw((x0, 4.0), width=57.0, height=70.0, align="bottom_left")
    inner_xy, inner_w, inner_h = panel.get_content_box(
        (x0, 4.0),
        width=57.0,
        height=70.0,
        padding=(2.8, 2.8),
        align="bottom_left",
    )

    cards = CardList(
        style=Styles.White,
        text_style=Styles.DarkBold.patch(text_size=10.5),
        description_style=Styles.Muted.patch(text_size=10.0),
        badge_shape="rounded_rect",
        badge_style=Styles.PrimaryNeutral,
        icon_style=Styles.PrimaryBold,
        direction="vertical",
        spacing=2.6,
    )
    for mod_name, mod_desc, mod_icon, is_focal in items:
        if is_focal:
            cards.add(
                mod_name,
                mod_desc,
                icon=mod_icon,
                badge_style=Styles.PrimaryFlat,
                icon_style=Styles.WhiteBold,
            )
        else:
            cards.add(mod_name, mod_desc, icon=mod_icon)
    cards.draw(inner_xy, width=inner_w, height=inner_h, align="bottom_left")

save()

structcobra.Command#

CLI コマンドノードを定義する構造体です(command.go:L54-L260)。通常は構造体リテラル(&cobra.Command{...})として初期化し、AddCommand() で親子ツリーへ組み込みます。

type Command struct {
	// 1. 識別情報・ドキュメント・グループ分類
	Use         string
	Aliases     []string
	SuggestFor  []string
	Short       string
	GroupID     string
	Long        string
	Example     string
	Annotations map[string]string
	Version     string

	// 2. 引数バリデーション・シェル補完設定
	ValidArgs              []Completion
	ValidArgsFunction      CompletionFunc
	Args                   PositionalArgs
	ArgAliases             []string
	BashCompletionFunction string
	CompletionOptions      CompletionOptions

	// 3. 実行ライフサイクルフック(実行順: PersistentPreRun -> PreRun -> Run -> PostRun -> PersistentPostRun)
	PersistentPreRun   func(cmd *Command, args []string)
	PersistentPreRunE  func(cmd *Command, args []string) error
	PreRun             func(cmd *Command, args []string)
	PreRunE            func(cmd *Command, args []string) error
	Run                func(cmd *Command, args []string)
	RunE               func(cmd *Command, args []string) error
	PostRun            func(cmd *Command, args []string)
	PostRunE           func(cmd *Command, args []string) error
	PersistentPostRun  func(cmd *Command, args []string)
	PersistentPostRunE func(cmd *Command, args []string) error

	// 4. フラグ解析・エラー出力・サジェスト制御フラグ
	FParseErrWhitelist         FParseErrWhitelist
	TraverseChildren           bool
	Hidden                     bool
	Deprecated                 string
	SilenceErrors              bool
	SilenceUsage               bool
	DisableFlagParsing         bool
	DisableAutoGenTag          bool
	DisableFlagsInUseLine      bool
	DisableSuggestions         bool
	SuggestionsMinimumDistance int
}

1. 識別情報・ドキュメント・グループ分類フィールド#

フィールド名 型 説明
Use string 1 行の Usage シグネチャです。最初の空白までの先頭トークンが cmd.Name() になります(例:"deploy [flags] <env>" の場合は Name() == "deploy")。推奨記法では任意引数に [ ]、繰り返しに ...、排他選択に |、必須排他セットに { } を用います。
Aliases []string Name() の代わりに入力可能な別名スライスです。findNext() によるコマンド解決で照合され、ヘルプの Aliases: 欄に表示されます。
SuggestFor []string 実行可能なエイリアスとしては扱わず、誤入力された際に "Did you mean this?" の候補としてこのコマンドを提案させる文字列リストです(例:delete コマンドに対する "remove", "rm")。
Short string 親コマンドの Available Commands: 一覧およびシェル補完の説明文に表示される 1 行要約です。
GroupID string 親コマンドに parent.AddGroup() で登録された cobra.Group の ID です。親に未登録の GroupID を指定すると、ExecuteC() 内の checkCommandGroups() で panic が発生します。
Long string help <command> や <command> --help、および cobra/doc ドキュメント生成時に出力される詳細説明文です。未設定時は Short が使われます。
Example string ヘルプおよび生成ドキュメントの Examples: セクションに出力されるコマンド実行例です。
Annotations map[string]string アプリケーション固有のメタデータを保持するキー・バリューマップです。Annotations[cobra.CommandDisplayNameAnnotation] を設定すると cmd.DisplayName() の表示名を上書きできます。
Version string コマンドのバージョン文字列です。空でない場合、InitDefaultVersionFlag() が --version(および未使用なら -v)フラグを自動追加し、指定時に versionTemplate を出力します。

2. フラグ・引数・補完・動作制御フィールド#

フィールド名 型 説明
ValidArgs []Completion シェル補完および OnlyValidArgs バリデータで許可される位置引数の静的リストです。CompletionWithDesc() により \t 区切りで説明文を付与できます。ValidArgsFunction とは排他利用です。
ValidArgsFunction CompletionFunc __complete 実行時に位置引数の補完候補を動的に返すコールバック func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) です。
Args PositionalArgs 位置引数バリデータ関数 func(cmd *Command, args []string) error です(詳細は 04. 位置引数バリデータ 参照)。nil の場合、Find() 時には legacyArgs、ValidateArgs() 時には ArbitraryArgs が適用されます。
ArgAliases []string ValidArgs の別名リストです。補完候補メニューには表示されませんが、手入力された際に有効な補完対象として受理されます。
BashCompletionFunction string レガシーな Bash 補完 V1(GenBashCompletion)スクリプトに注入するカスタム Bash 関数文字列です。マルチシェル対応には ValidArgsFunction の使用が推奨されます。
CompletionOptions CompletionOptions 自動生成される completion サブコマンドの挙動(DisableDefaultCmd, DisableNoDescFlag, DisableDescriptions, HiddenDefaultCmd, DefaultShellCompDirective)を制御します。
FParseErrWhitelist FParseErrWhitelist pflag.ParseErrorsAllowlist(struct{ UnknownFlags bool })の型エイリアスです。UnknownFlags: true に設定すると、未定義フラグが渡されても ParseFlags() がエラーを返さなくなります。
TraverseChildren bool ルートコマンドで true に設定すると、ExecuteC() が Find() の代わりに Traverse() を呼び出し、経路上のすべての親コマンドでローカルフラグを順次解析してから子コマンドを実行します。
Deprecated string 非推奨メッセージです。空でない場合、Available Commands: や補完候補から除外(IsAvailableCommand() == false)され、実行時に "Command <name> is deprecated, <msg>" を出力します。
Hidden bool true に設定すると、ヘルプ一覧・シェル補完・cobra/doc 生成結果から非表示になりますが、明示的にコマンド名を指定すれば実行可能です。
SilenceErrors bool true に設定すると(対象コマンドまたはルートコマンドのいずれか)、execute() がエラーを返した際に ExecuteC() が自動で Error: <err> を標準エラーへ出力するのを抑止します。
SilenceUsage bool true に設定すると、RunE やフラグ・引数検証でエラーが発生した際に ExecuteC() が自動で UsageString() を表示するのを抑止します。
DisableFlagParsing bool true に設定すると、pflag によるフラグ解析を完全にスキップし、-f や --flag を含むすべてのトークンをそのまま args []string として Run / RunE に渡します。
DisableAutoGenTag bool true に設定すると、cobra/doc が生成する Markdown / Man / ReST ファイル末尾の "Auto generated by spf13/cobra..." フッター出力を無効化します。
DisableFlagsInUseLine bool true に設定すると、利用可能なフラグが存在する場合でも UseLine() 末尾への " [flags]" 自動付与を抑止します。
DisableSuggestions bool true に設定すると、未知のサブコマンド入力時にレーベンシュタイン距離に基づく "Did you mean this?" サジェスト表示を無効化します。
SuggestionsMinimumDistance int タイポ候補として提案する最大レーベンシュタイン編集距離です(<= 0 の場合はデフォルト値 2 に設定されます)。

3. 実行ライフサイクルフックフィールド(全 10 フィールド)#

全 10 個のフック関数は (cmd *Command, args []string) を受け取ります(args はフラグを除いた位置引数スライス)。フックは cmd.Runnable() == true(Run != nil || RunE != nil)のコマンドでのみ実行されます。同一コマンドに *E(エラー返却あり)と非 E の両方が設定されている場合、常に *E 側が優先されます。

フックフィールド シグネチャ 継承仕様と実行順序
PersistentPreRunE / PersistentPreRun func(cmd *Command, args []string) [error] 第 1 段階: 子コマンドへ継承されます。デフォルト(EnableTraverseRunHooks == false)では c から親を辿って最初に見つかった 1 つだけを実行します。EnableTraverseRunHooks == true の場合は Root() から c へ向かって定義済みの全フックを順番に実行します。
PreRunE / PreRun func(cmd *Command, args []string) [error] 第 2 段階: c 固有の事前フック(子へは継承されません)。PersistentPreRun[E] の直後、かつ ValidateRequiredFlags() および ValidateFlagGroups() の前に実行されます。
RunE / Run func(cmd *Command, args []string) [error] 第 3 段階: コマンドのメイン処理です。必須フラグおよびフラググループ検証を通過した後に実行されます。
PostRunE / PostRun func(cmd *Command, args []string) [error] 第 4 段階: c 固有の事後フックです。Run / RunE が正常終了した直後に実行されます(エラー時はスキップ)。
PersistentPostRunE / PersistentPostRun func(cmd *Command, args []string) [error] 第 5 段階: 子コマンドへ継承される事後フックです。EnableTraverseRunHooks == true の場合は c から Root() へボトムアップ順に実行されます。

structcobra.Group & type cobra.FParseErrWhitelist#

command.go 冒頭(L41-L48)で定義されている補助型です。

// FParseErrWhitelist configures Flag parse errors to be ignored
type FParseErrWhitelist flag.ParseErrorsAllowlist

// Group Structure to manage groups for commands
type Group struct {
	ID    string
	Title string
}
型 / フィールド 実体型 説明
FParseErrWhitelist.UnknownFlags bool true の場合、c.ParseFlags(args) 実行時に未知の --flag が含まれていてもエラーを無視します。Parse() 直前に c.Flags().ParseErrorsAllowlist へコピーされます。
Group.ID string 子コマンドの Command.GroupID、SetHelpCommandGroupID()、SetCompletionCommandGroupID() と照合されるグループ識別子です。
Group.Title string UsageString() 出力内で該当グループのサブコマンド一覧の見出しとして表示されるタイトル文字列です。
01_command_struct_2
コマンドツリートポロジとサブコマンドグループ階層 (AddCommand, AddGroup, GroupID)
Source Code
from drawlib.canvas import save, setup
from drawlib.diagrams.architecture import ArchitectureDiagram, Node, NodeGroup, PhosphorIcon
from drawlib.styles import Colors, Styles

setup(width=184, height=84)

_ = Colors

d = ArchitectureDiagram(
    node_style=Styles.PrimaryBold,
    node_text_style=Styles.DarkBold.patch(text_size=10.2),
    edge_style=Styles.DarkBold.patch(line_width=1.5),
    edge_text_style=Styles.Dark.patch(text_size=10.0),
    node_card_style=Styles.White,
)

root_node = d.add(
    Node(
        "Root *Command (Use: \"ctl\")\nAddCommand() & AddGroup()",
        icon=PhosphorIcon.TERMINAL_WINDOW,
        icon_size=7.0,
        width=48,
        height=20,
        style=Styles.White,
        text_style=Styles.WhiteBold.patch(text_size=10.5),
        card_style=Styles.PrimaryFlat.patch(shape_r=2.5),
    ),
    xy=(28.0, 42.0),
)

core_grp = d.add(
    NodeGroup(
        "Group{ID: \"work\", Title: \"Workload Commands:\"}",
        width=112,
        height=34,
        style=Styles.PrimaryNeutral.patch(shape_r=2.0),
        text_style=Styles.DarkBold.patch(text_size=10.8, halign="left", valign="top"),
    ),
    xy=(68.0, 45.0),
)

deploy_node = core_grp.add(
    Node(
        "deploy *Command\nGroupID: \"work\"",
        icon=PhosphorIcon.ROCKET_LAUNCH,
        icon_size=6.0,
        width=46,
        height=16,
        card_style=Styles.White.patch(shape_r=2.0),
    ),
    xy=(29.0, 13.0),
)

roll_node = core_grp.add(
    Node(
        "rollback *Command\nGroupID: \"work\"",
        icon=PhosphorIcon.ARROW_COUNTER_CLOCKWISE,
        icon_size=6.0,
        width=46,
        height=16,
        card_style=Styles.White.patch(shape_r=2.0),
    ),
    xy=(83.0, 13.0),
)

sys_grp = d.add(
    NodeGroup(
        "組み込みコマンドとグループ割り当て",
        width=112,
        height=34,
        style=Styles.Neutral.patch(shape_r=2.0),
        text_style=Styles.DarkBold.patch(text_size=10.8, halign="left", valign="top"),
    ),
    xy=(68.0, 5.0),
)

help_node = sys_grp.add(
    Node(
        "help *Command\nSetHelpCommandGroupID()",
        icon=PhosphorIcon.QUESTION,
        icon_size=6.0,
        width=46,
        height=16,
        card_style=Styles.SecondaryNeutral.patch(shape_r=2.0),
    ),
    xy=(29.0, 13.0),
)

comp_node = sys_grp.add(
    Node(
        "completion *Command\nSetCompletionCommandGroupID()",
        icon=PhosphorIcon.SPARKLE,
        icon_size=6.0,
        width=46,
        height=16,
        card_style=Styles.SecondaryNeutral.patch(shape_r=2.0),
    ),
    xy=(83.0, 13.0),
)

d.connect(root_node, deploy_node)
d.connect(root_node, help_node)
d.connect(deploy_node, roll_node)
d.connect(help_node, comp_node)

d.draw((0.0, 0.0))
save()

methodツリー構築・走査メソッド#

コマンドツリーの構築・削除・親方向への走査を行う *Command メソッド群です(command.go)。

func (c *Command) AddCommand(cmds ...*Command)
func (c *Command) RemoveCommand(cmds ...*Command)
func (c *Command) ResetCommands()
func (c *Command) Commands() []*Command
func (c *Command) Parent() *Command
func (c *Command) Root() *Command
func (c *Command) VisitParents(fn func(*Command))
メソッド シグネチャ 説明
AddCommand func (c *Command) AddCommand(cmds ...*Command) 1 つ以上の子コマンドを c に追加し、child.parent = c の設定、パディング最大長(commandsMaxUseLen 等)の更新、c.globNormFunc の伝播、c.commandsAreSorted = false の設定を行います。cmd == c の場合は "Command can't be a child of itself" で panic します。
RemoveCommand func (c *Command) RemoveCommand(cmds ...*Command) 指定された子コマンドを c.commands から切り離し(command.parent = nil)、残りの子コマンドからパディング最大長を再計算します。
ResetCommands func (c *Command) ResetCommands() c.parent、c.commands、c.helpCommand、c.parentsPflags をすべて nil にリセットします(主にテスト用途)。
Commands func (c *Command) Commands() []*Command 直下の子コマンドスライスを返します。cobra.EnableCommandSorting == true の場合、未ソート状態であれば Name() の辞書順で遅延ソートを実行します。
Parent func (c *Command) Parent() *Command 直近の親 *Command を返します(ルートコマンドの場合は nil)。
Root func (c *Command) Root() *Command c.Parent() を再帰的に辿り、ツリー最上位のルート *Command を返します。
VisitParents func (c *Command) VisitParents(fn func(*Command)) c.Parent() から Root() まで全ての親コマンドに対して順番に fn を呼び出します(c 自身には呼び出されません)。

methodツリー状態・利用可否判定メソッド#

ExecuteC()、Usage テンプレート、cobra/doc 生成器がコマンドの種別を判定するための真偽値メソッド群です。

func (c *Command) HasParent() bool
func (c *Command) HasSubCommands() bool
func (c *Command) HasAvailableSubCommands() bool
func (c *Command) HasHelpSubCommands() bool
func (c *Command) IsAvailableCommand() bool
func (c *Command) IsAdditionalHelpTopicCommand() bool
func (c *Command) Runnable() bool
func (c *Command) HasExample() bool
メソッド 真となる条件 説明
HasParent() c.parent != nil 親コマンドを持つ子コマンドである場合に true を返します。
HasSubCommands() len(c.commands) > 0 隠し・非推奨を含め、1 つ以上の子コマンドを持つ場合に true を返します。
HasAvailableSubCommands() いずれかの子が IsAvailableCommand() Available Commands: に表示すべき有効な子コマンドが 1 つ以上ある場合に true を返します。
HasHelpSubCommands() いずれかの子が IsAdditionalHelpTopicCommand() Additional help topics: に表示すべき追加ヘルプトピック子コマンドがある場合に true を返します。
IsAvailableCommand() Deprecated == "" && !Hidden && c != parent.helpCommand && (Runnable() || HasAvailableSubCommands()) 通常の利用可能コマンドとしてヘルプ一覧やドキュメントに掲載対象かどうかを判定します。
IsAdditionalHelpTopicCommand() !Runnable() && Deprecated == "" && !Hidden かつ全子コマンドもヘルプトピック Run / RunE を持たないドキュメント専用トピックコマンド(例:myapp environment)であるかを判定します。
Runnable() c.Run != nil || c.RunE != nil コマンド自身が実行関数を持つ場合に true を返します。false の親コマンドが単体実行されると flag.ErrHelp を返してヘルプを表示します。
HasExample() len(c.Example) > 0 c.Example が設定されている場合に true を返します。

methodコマンド名・パス・エイリアス・パディング取得メソッド#

コマンド名、完全パス、呼び出し時のエイリアス名、およびヘルプ整形用の列幅を取得するメソッド群です。

func (c *Command) Name() string
func (c *Command) DisplayName() string
func (c *Command) CommandPath() string
func (c *Command) UseLine() string
func (c *Command) CalledAs() string
func (c *Command) HasAlias(s string) bool
func (c *Command) NameAndAliases() string
func (c *Command) UsagePadding() int
func (c *Command) CommandPathPadding() int
func (c *Command) NamePadding() int
メソッド 戻り値型 説明
Name() string c.Use の最初のスペース " " より前の先頭トークンを返します。
DisplayName() string c.Annotations[CommandDisplayNameAnnotation] が設定されていればその値を返し、未設定時は c.Name() を返します。
CommandPath() string ルートから c までのスペース区切りの完全コマンドパスを返します(ルートのみ DisplayName() を使用)。
UseLine() string parent.CommandPath() + c.Use(先頭名を DisplayName() に置換)に、利用可能なフラグがあり DisableFlagsInUseLine == false なら " [flags]" を付与した完全 Usage 行を返します。
CalledAs() string Find() / Traverse() 解決時にユーザーが実際に入力したコマンド名またはエイリアス名を返します(未呼び出し時は "")。
HasAlias(s string) bool s が c.Aliases に含まれるか判定します(cobra.EnableCaseInsensitive の設定に従います)。
NameAndAliases() string c.Name() と全 c.Aliases を ", " で連結した文字列を返します(例:"deploy, dep, d")。
NamePadding() int Available Commands: のコマンド名列パディング幅(max(11, c.parent.commandsMaxNameLen))を返します。
CommandPathPadding() int Additional help topics: のコマンドパス列パディング幅(max(11, c.parent.commandsMaxCommandPathLen))を返します。
UsagePadding() int Use 文字列のパディング幅(max(25, c.parent.commandsMaxUseLen))を返します。

methodコマンドグループ管理メソッド#

サブコマンドをヘルプ出力内で論理的なグループ見出しごとに分類するメソッド群です(command.go)。

func (c *Command) AddGroup(groups ...*Group)
func (c *Command) Groups() []*Group
func (c *Command) ContainsGroup(groupID string) bool
func (c *Command) AllChildCommandsHaveGroup() bool
func (c *Command) SetHelpCommandGroupID(groupID string)
func (c *Command) SetCompletionCommandGroupID(groupID string)
メソッド シグネチャ 説明
AddGroup func (c *Command) AddGroup(groups ...*Group) 1 つ以上の *Group 定義を c.commandgroups に追加します。ヘルプ出力では追加順にグループ見出しが並びます。
Groups func (c *Command) Groups() []*Group c に登録されている *Group スライスを返します。
ContainsGroup func (c *Command) ContainsGroup(groupID string) bool ID == groupID のグループが c.commandgroups に存在するかを返します。ExecuteC() の checkCommandGroups() で検証に使われます。
AllChildCommandsHaveGroup func (c *Command) AllChildCommandsHaveGroup() bool 利用可能なすべての子コマンド(および helpCommand)に非空の GroupID が設定されていれば true を返します。false の場合、未分類コマンドは Additional Commands: 見出しの下に表示されます。
SetHelpCommandGroupID func (c *Command) SetHelpCommandGroupID(groupID string) 自動生成される help サブコマンドの所属グループ ID を設定します。
SetCompletionCommandGroupID func (c *Command) SetCompletionCommandGroupID(groupID string) c.Root() 上で自動生成される completion サブコマンドの所属グループ ID を設定します。

使用例:サブコマンドのグループ分け#

rootCmd := &cobra.Command{Use: "ctl", Short: "Cluster control CLI"}

rootCmd.AddGroup(
	&cobra.Group{ID: "workload", Title: "Workload Commands:"},
	&cobra.Group{ID: "system", Title: "System & Shell Commands:"},
)

deployCmd := &cobra.Command{
	Use:     "deploy <service>",
	Aliases: []string{"dep"},
	Short:   "Deploy a service workload",
	GroupID: "workload",
	RunE: func(cmd *cobra.Command, args []string) error {
		cmd.Printf("Deploying %s (called as %s)\n", args[0], cmd.CalledAs())
		return nil
	},
}

rootCmd.AddCommand(deployCmd)
rootCmd.SetHelpCommandGroupID("system")
rootCmd.SetCompletionCommandGroupID("system")