<commit_before>// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package cli

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/alecthomas/kong"
	"github.com/flynn-archive/go-shlex"
	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
	"github.com/syncthing/syncthing/lib/config"
)

type preCli struct {
	GUIAddress string `name:"gui-address"`
	GUIAPIKey  string `name:"gui-apikey"`
	HomeDir    string `name:"home"`
	ConfDir    string `name:"config"`
	DataDir    string `name:"data"`
}

func Run() error {
	// This is somewhat a hack around a chicken and egg problem. We need to set
	// the home directory and potentially other flags to know where the
	// syncthing instance is running in order to get it's config ... which we
	// then use to construct the actual CLI ... at which point it's too late to
	// add flags there...
	c := preCli{}
	parseFlags(&c)

	// Not set as default above because the strings can be really long.
	err := cmdutil.SetConfigDataLocationsFromFlags(c.HomeDir, c.ConfDir, c.DataDir)
	if err != nil {
		return errors.Wrap(err, "Command line options:")
	}
	clientFactory := &apiClientFactory{
		cfg: config.GUIConfiguration{
			RawAddress: c.GUIAddress,
			APIKey:     c.GUIAPIKey,
		},
	}

	configCommand, err := getConfigCommand(clientFactory)
	if err != nil {
		return err
	}

	// Implement the same flags at the upper CLI, but do nothing with them.
	// This is so that the usage text is the same
	fakeFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "gui-address",
			Usage: "Override GUI address to `URL` (e.g. \"http://192.0.2.42:8443\")",
		},
		cli.StringFlag{
			Name:  "gui-apikey",
			Usage: "Override GUI API key to `API-KEY`",
		},
		cli.StringFlag{
			Name:  "home",
			Usage: "Set configuration and data directory to `PATH`",
		},
		cli.StringFlag{
			Name:  "config",
			Usage: "Set configuration directory (config and keys) to `PATH`",
		},
		cli.StringFlag{
			Name:  "data",
			Usage: "Set data directory (database and logs) to `PATH`",
		},
	}

	// Construct the actual CLI
	app := cli.NewApp()
	app.Author = "The Syncthing Authors"
	app.Metadata = map[string]interface{}{
		"clientFactory": clientFactory,
	}
	app.Commands = []cli.Command{{
		Name:  "cli",
		Usage: "Syncthing command line interface",
		Flags: fakeFlags,
		Subcommands: []cli.Command{
			configCommand,
			showCommand,
			operationCommand,
			errorsCommand,
			debugCommand,
			{
				Name:     "-",
				HideHelp: true,
				Usage:    "Read commands from stdin",
				Action: func(ctx *cli.Context) error {
					if ctx.NArg() > 0 {
						return errors.New("command does not expect any arguments")
					}

					// Drop the `-` not to recurse into self.
					args := make([]string, len(os.Args)-1)
					copy(args, os.Args)

					fmt.Println("Reading commands from stdin...", args)
					scanner := bufio.NewScanner(os.Stdin)
					for scanner.Scan() {
						input, err := shlex.Split(scanner.Text())
						if err != nil {
							return errors.Wrap(err, "parsing input")
						}
						if len(input) == 0 {
							continue
						}
						err = app.Run(append(args, input...))
						if err != nil {
							return err
						}
					}
					return scanner.Err()
				},
			},
		},
	}}

	return app.Run(os.Args)
}

func parseFlags(c *preCli) error {
	// kong only needs to parse the global arguments after "cli" and before the
	// subcommand (if any).
	if len(os.Args) <= 2 {
		return nil
	}
	args := os.Args[2:]
	for i := 0; i < len(args); i++ {<commit_msg>cmd/syncthing: Refactor CLI code to allow passing CLI arguments via function parameter (#8485)<commit_after>// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package cli

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/alecthomas/kong"
	"github.com/flynn-archive/go-shlex"
	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
	"github.com/syncthing/syncthing/lib/config"
)

type preCli struct {
	GUIAddress string `name:"gui-address"`
	GUIAPIKey  string `name:"gui-apikey"`
	HomeDir    string `name:"home"`
	ConfDir    string `name:"config"`
	DataDir    string `name:"data"`
}

func Run() error {
	// This is somewhat a hack around a chicken and egg problem. We need to set
	// the home directory and potentially other flags to know where the
	// syncthing instance is running in order to get it's config ... which we
	// then use to construct the actual CLI ... at which point it's too late to
	// add flags there...
	c := preCli{}
	parseFlags(&c)
	return runInternal(c, os.Args)
}

func RunWithArgs(cliArgs []string) error {
	c := preCli{}
	parseFlagsWithArgs(cliArgs, &c)
	return runInternal(c, cliArgs)
}

func runInternal(c preCli, cliArgs []string) error {
	// Not set as default above because the strings can be really long.
	err := cmdutil.SetConfigDataLocationsFromFlags(c.HomeDir, c.ConfDir, c.DataDir)
	if err != nil {
		return errors.Wrap(err, "Command line options:")
	}
	clientFactory := &apiClientFactory{
		cfg: config.GUIConfiguration{
			RawAddress: c.GUIAddress,
			APIKey:     c.GUIAPIKey,
		},
	}

	configCommand, err := getConfigCommand(clientFactory)
	if err != nil {
		return err
	}

	// Implement the same flags at the upper CLI, but do nothing with them.
	// This is so that the usage text is the same
	fakeFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "gui-address",
			Usage: "Override GUI address to `URL` (e.g. \"http://192.0.2.42:8443\")",
		},
		cli.StringFlag{
			Name:  "gui-apikey",
			Usage: "Override GUI API key to `API-KEY`",
		},
		cli.StringFlag{
			Name:  "home",
			Usage: "Set configuration and data directory to `PATH`",
		},
		cli.StringFlag{
			Name:  "config",
			Usage: "Set configuration directory (config and keys) to `PATH`",
		},
		cli.StringFlag{
			Name:  "data",
			Usage: "Set data directory (database and logs) to `PATH`",
		},
	}

	// Construct the actual CLI
	app := cli.NewApp()
	app.Author = "The Syncthing Authors"
	app.Metadata = map[string]interface{}{
		"clientFactory": clientFactory,
	}
	app.Commands = []cli.Command{{
		Name:  "cli",
		Usage: "Syncthing command line interface",
		Flags: fakeFlags,
		Subcommands: []cli.Command{
			configCommand,
			showCommand,
			operationCommand,
			errorsCommand,
			debugCommand,
			{
				Name:     "-",
				HideHelp: true,
				Usage:    "Read commands from stdin",
				Action: func(ctx *cli.Context) error {
					if ctx.NArg() > 0 {
						return errors.New("command does not expect any arguments")
					}

					// Drop the `-` not to recurse into self.
					args := make([]string, len(cliArgs)-1)
					copy(args, cliArgs)

					fmt.Println("Reading commands from stdin...", args)
					scanner := bufio.NewScanner(os.Stdin)
					for scanner.Scan() {
						input, err := shlex.Split(scanner.Text())
						if err != nil {
							return errors.Wrap(err, "parsing input")
						}
						if len(input) == 0 {
							continue
						}
						err = app.Run(append(args, input...))
						if err != nil {
							return err
						}
					}
					return scanner.Err()
				},
			},
		},
	}}

	return app.Run(cliArgs)
}

func parseFlags(c *preCli) error {
	// kong only needs to parse the global arguments after "cli" and before the
	// subcommand (if any).
	if len(os.Args) <= 2 {
		return nil
	}
	return parseFlagsWithArgs(os.Args[2:], c)
}

func parseFlagsWithArgs(args []string, c *preCli) error {
	for i := 0; i < len(args); i++ {
{'fraction_non_alphanumeric': 0.1341603785650339, 'fraction_numerical': 0.006906253996674766, 'mean_word_length': 3.004096262160778, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2727340', 'n_tokens_mistral': 3080, 'n_tokens_neox': 2638, 'n_words': 817}