-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BREAKING CHANGE(sh): only run rbmk & built-in commands (#44)
This diff modifies how `rbmk sh` executes commands by only allowing internal commands (`pwd`, `cd`) and `rbmk` commands, which are also exported as internal commands. The main motivation for implementing this change is ensuring that a script running on, say, Linux, would also work on, say, Windows, without issues caused by missing commands. Figuring out whether the decision to implement this change was motivated by theoretical concerns or hands on experience is left as an exercise for the reader 😬. To preserve backward compatibility, we set `$RBMK_EXE` to `rbmk` thus allowing previous script to work as long as they were using the `rbmk` command only. Scripts using external commands would stop working, hence this is still a breaking change. See the updated documentation in pkg/cli/sh/README.md for more details about this change and guidance on writing portable scripts.
- Loading branch information
1 parent
699abaf
commit 93815ff
Showing
7 changed files
with
270 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
/* | ||
Package rootcmd contains shared code to | ||
implement the `rbmk` command. | ||
This code is shared between the following packages: | ||
1. [github.com/rbmk-project/rbmk/pkg/cli/sh]. | ||
2. [github.com/rbmk-project/rbmk/pkg/cli]. | ||
The former package implements the `rbmk sh` command, | ||
while the latter implements the `rbmk` command. | ||
Both packages need to have access to the list of internal | ||
commands as well as to the text printed on `--help`. | ||
*/ | ||
package rootcmd | ||
|
||
import ( | ||
_ "embed" | ||
|
||
"github.com/rbmk-project/common/cliutils" | ||
"github.com/rbmk-project/rbmk/pkg/cli/cat" | ||
"github.com/rbmk-project/rbmk/pkg/cli/curl" | ||
"github.com/rbmk-project/rbmk/pkg/cli/dig" | ||
"github.com/rbmk-project/rbmk/pkg/cli/intro" | ||
"github.com/rbmk-project/rbmk/pkg/cli/ipuniq" | ||
"github.com/rbmk-project/rbmk/pkg/cli/mkdir" | ||
"github.com/rbmk-project/rbmk/pkg/cli/mv" | ||
"github.com/rbmk-project/rbmk/pkg/cli/nc" | ||
"github.com/rbmk-project/rbmk/pkg/cli/pipe" | ||
"github.com/rbmk-project/rbmk/pkg/cli/rm" | ||
"github.com/rbmk-project/rbmk/pkg/cli/stun" | ||
"github.com/rbmk-project/rbmk/pkg/cli/tar" | ||
"github.com/rbmk-project/rbmk/pkg/cli/timestamp" | ||
"github.com/rbmk-project/rbmk/pkg/cli/tutorial" | ||
"github.com/rbmk-project/rbmk/pkg/cli/version" | ||
) | ||
|
||
//go:embed README.md | ||
var readme string | ||
|
||
// HelpText returns the text to be printed when the `--help` | ||
// flag is passed to the `rbmk` command. The text may be rendered | ||
// using the markdown package, depending on the build tags. | ||
func HelpText() string { | ||
return readme | ||
} | ||
|
||
// CommandsWithoutSh returns a new map containing the mapping | ||
// between the name of a command and the command itself. | ||
// | ||
// The returned map DOES NOT include the `sh` command, which | ||
// is a special case, which the packages using this func could | ||
// choose to implement differently (and how they choose to | ||
// implement it is not this function's concern anyway). | ||
func CommandsWithoutSh() map[string]cliutils.Command { | ||
return map[string]cliutils.Command{ | ||
"cat": cat.NewCommand(), | ||
"curl": curl.NewCommand(), | ||
"dig": dig.NewCommand(), | ||
"intro": intro.NewCommand(), | ||
"ipuniq": ipuniq.NewCommand(), | ||
"mkdir": mkdir.NewCommand(), | ||
"mv": mv.NewCommand(), | ||
"nc": nc.NewCommand(), | ||
"pipe": pipe.NewCommand(), | ||
"rm": rm.NewCommand(), | ||
"stun": stun.NewCommand(), | ||
"tar": tar.NewCommand(), | ||
"timestamp": timestamp.NewCommand(), | ||
"tutorial": tutorial.NewCommand(), | ||
"version": version.NewCommand(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
package sh | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/rbmk-project/common/cliutils" | ||
"github.com/rbmk-project/common/fsx" | ||
"github.com/rbmk-project/rbmk/internal/markdown" | ||
"github.com/rbmk-project/rbmk/internal/rootcmd" | ||
"mvdan.cc/sh/v3/interp" | ||
) | ||
|
||
// builtInMiddleware is the middleware to execute built-in commands. | ||
type builtInMiddleware func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc | ||
|
||
// newBuiltInMiddleware creates a new built-in middleware for | ||
// executing built-in commands with the shell. | ||
func newBuiltInMiddleware() builtInMiddleware { | ||
return func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc { | ||
return func(ctx context.Context, args []string) error { | ||
// 1. ensure we have a command to run and that such a | ||
// command is indeed the "rbmk" internal command. | ||
if len(args) < 1 { | ||
return errors.New("no command to run") | ||
} | ||
if args[0] != "rbmk" { | ||
return fmt.Errorf("%s: command not found", args[0]) | ||
} | ||
|
||
// 2. construct the subcommand environment. | ||
env := newBuiltInEnvironment(interp.HandlerCtx(ctx)) | ||
|
||
// 3. construct the root command to switch depending on the | ||
// actual `rbmk` subcommand being invoked. | ||
directory := rootcmd.CommandsWithoutSh() | ||
directory["sh"] = builtInShCommand{} | ||
root := cliutils.NewCommandWithSubCommands( | ||
"rbmk", markdown.LazyMaybeRender(rootcmd.HelpText()), directory) | ||
|
||
// 4. execute the root command and return the result | ||
return root.Main(ctx, env, args...) | ||
} | ||
} | ||
} | ||
|
||
// builtInEnvironment contains the environment for executing built-in commands. | ||
type builtInEnvironment struct { | ||
// fs is the file system to use. | ||
fs fsx.FS | ||
|
||
// stdin is the standard input. | ||
stdin io.Reader | ||
|
||
// stdout is the standard output. | ||
stdout io.Writer | ||
|
||
// stderr is the standard error. | ||
stderr io.Writer | ||
} | ||
|
||
// newBuiltInEnvironment creates a new built-in environment. | ||
// | ||
// Uses: | ||
// | ||
// 1. [fsx.NewChdirFS] to simulate chdir into the current directory. | ||
// | ||
// 2. the shells's current stdin, stdout, and stderr. | ||
// | ||
// We ignore the shell environment since we don't actually use it. | ||
func newBuiltInEnvironment(shCtx interp.HandlerContext) *builtInEnvironment { | ||
return &builtInEnvironment{ | ||
fs: fsx.NewChdirFS(fsx.OsFS{}, shCtx.Dir), | ||
stdin: shCtx.Stdin, | ||
stdout: shCtx.Stdout, | ||
stderr: shCtx.Stderr, | ||
} | ||
} | ||
|
||
// Ensure that builtInEnvironment implements [cliutils.Environment]. | ||
var _ cliutils.Environment = &builtInEnvironment{} | ||
|
||
// FS implements [cliutils.Environment]. | ||
func (env *builtInEnvironment) FS() fsx.FS { | ||
return env.fs | ||
} | ||
|
||
// Stderr implements [cliutils.Environment]. | ||
func (env *builtInEnvironment) Stderr() io.Writer { | ||
return env.stderr | ||
} | ||
|
||
// Stdin implements [cliutils.Environment]. | ||
func (env *builtInEnvironment) Stdin() io.Reader { | ||
return env.stdin | ||
} | ||
|
||
// Stdout implements [cliutils.Environment]. | ||
func (env *builtInEnvironment) Stdout() io.Writer { | ||
return env.stdout | ||
} | ||
|
||
// builtInShCommand is the built-in `sh` command executed | ||
// from inside the `rbmk sh` environment. We do not permit | ||
// executing the shell inside the shell because that has | ||
// not been tested, and it would probably not WAI. | ||
type builtInShCommand struct{} | ||
|
||
// Ensure that [builtInShCommand] implements [cliutils.Command]. | ||
var _ cliutils.Command = builtInShCommand{} | ||
|
||
// Help implements [cliutils.Command]. | ||
func (cmd builtInShCommand) Help(env cliutils.Environment, argv ...string) error { | ||
return NewCommand().Help(env, argv...) | ||
} | ||
|
||
// Main implements [cliutils.Command]. | ||
func (cmd builtInShCommand) Main(ctx context.Context, env cliutils.Environment, argv ...string) error { | ||
// 1. Honour requests for printing the help. | ||
if cliutils.HelpRequested(argv...) { | ||
return cmd.Help(env, argv...) | ||
} | ||
|
||
// 2. otherwise prevent re-execution of the shell | ||
err := errors.New("cannot execute `rbmk sh` inside `rbmk sh`") | ||
fmt.Fprintf(env.Stderr(), "rbmk sh: %s\n", err.Error()) | ||
return err | ||
} |
Oops, something went wrong.