Skip to content

Commit

Permalink
Merge pull request #2 from szkiba/feature/run-command
Browse files Browse the repository at this point in the history
feat: run subcommand
  • Loading branch information
szkiba authored Jan 11, 2024
2 parents e3b8c8a + a54ab0a commit 1426bec
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ linters-settings:
- github.com/spf13/cobra
- github.com/gobwas/glob
- github.com/liamg/memoryfs
- mvdan.cc/sh/v3/interp
- mvdan.cc/sh/v3/syntax
- github.com/szkiba/mdcode/internal
deny:
- pkg: io/ioutil
Expand Down
76 changes: 69 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ This document includes the necessary code for testing within invisible code bloc

Code blocks embedded in this document can be saved to files using the [`mdcode extract`](#mdcode-extract) command. A `README_test.go` and a `README.test.js` file will be created in the current directory. After modification, the code blocks can be updated from these files to the document using the [`mdcode update`](#mdcode-update) command.

After the modification, it is advisable to test the above examples using the following commands:

```sh name=test
go test ./...
node --test
```

Since the above code block has a name (`test`), it can also be run with the [`mdcode run`](#mdcode-run) command:

```
mdcode run -n test
```

More examples can be found in the [examples](examples/) directory and in the [tutorial](docs/testable-markdown-code-blocks.md).

### Features
Expand Down Expand Up @@ -239,11 +252,11 @@ Or if only block comments can be used (CSS):

/* #endregion */

Regions marked this way are used by IDEs to collapse parts of the source code.
Regions marked in this way are used by IDEs to collapse parts of the source code.

In the case of `mdcode`, regions can be referenced with the `region` metadata. If a region is specified for a code block, the subcommand (update or extract) applies only to the specified region of the file. That is, the update command only embeds the specified region from the file to the markdown document, and the extract command overwrites only the specified region in the file.

`mdcode` can handle regions in any programming language, the only requirement is that the comments indicating the beginning and end of the region are placed in separate lines containing only the given comment.
`mdcode` can handle regions in any programming language, the only requirement is that the comment indicating the beginning and end of the region is placed in a separate line containing only the given comment.
<!-- #endregion regions -->

### Invisible
Expand Down Expand Up @@ -435,7 +448,7 @@ The optional argument of the `mdcode` command is the name of the markdown file.


```
mdcode [filename] [flags]
mdcode [flags] [filename]
```

### Flags
Expand All @@ -453,6 +466,7 @@ mdcode [filename] [flags]

* [mdcode dump](#mdcode-dump) - Dump markdown code blocks
* [mdcode extract](#mdcode-extract) - Extract markdown code blocks to the file system
* [mdcode run](#mdcode-run) - Run shell commands on markdown code blocks
* [mdcode update](#mdcode-update) - Update markdown code blocks from the file system

---
Expand All @@ -472,7 +486,7 @@ The optional argument of the `mdcode dump` command is the name of the markdown f


```
mdcode dump [filename] [flags]
mdcode dump [flags] [filename]
```

### Flags
Expand Down Expand Up @@ -513,7 +527,7 @@ The optional argument of the `mdcode extract` command is the name of the markdow


```
mdcode extract [filename] [flags]
mdcode extract [flags] [filename]
```

### Flags
Expand All @@ -536,6 +550,54 @@ mdcode extract [filename] [flags]

* [mdcode](#mdcode) - Markdown code block authoring tool

---
## mdcode run

Run shell commands on markdown code blocks

### Synopsis

Extract code blocks to the file system and run shell commands on them

The code blocks are written to the file named in the `file` metadata.

The code block may include `region` metadata, which contains the name of the region. In this case, the code block is written to the appropriate part of the file marked with the `#region` comment.

The optional argument of the `mdcode run` command is the name of the markdown file. If it is missing, the `README.md` file in the current directory (if it exists) is processed.

This can be followed by a double dash (`--`) and then the shell command line to be executed (even a complex command, such as `for`).

Alternatively, the commands to be executed can be embedded in a code block in the document. In this case, the language must be `sh` and it is necessary to name the code block with the metadata `name`. The name of the code block containing the commands can be specified with the `--name` flag (if not, the first code block containing the `sh` language and `name` metadata will be executed).

Code blocks are extracted to a temporary directory. This directory will be the current directory when running the commands. The temporary directory is deleted after executing the commands (deletion can be prevented by using the `--keep` flag). Instead of a temporary directory, the name of the directory to be used can be specified with the `--dir` flag. In this case, of course, the directory is not deleted after executing the commands.


```
mdcode run [flags] [filename] [-- commands]
```

### Flags

```
-d, --dir string base directory name (default ".")
-h, --help help for run
-k, --keep don't remove temporary directory
-n, --name string code block name contains commands
-q, --quiet suppress the status output
```

### Global Flags

```
-f, --file strings file filter (default [?*])
-l, --lang strings language filter (default [?*])
-m, --meta stringToString metadata filter (default [])
```

### SEE ALSO

* [mdcode](#mdcode) - Markdown code block authoring tool

---
## mdcode update

Expand All @@ -553,7 +615,7 @@ The optional argument of the `mdcode update` command is the name of the markdown


```
mdcode update [filename] [flags]
mdcode update [flags] [filename]
```

### Flags
Expand All @@ -576,4 +638,4 @@ mdcode update [filename] [flags]

* [mdcode](#mdcode) - Markdown code block authoring tool

<!-- #endregion cli -->
<!-- #endregion cli -->
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.7.1
github.com/yuin/goldmark v1.6.0
mvdan.cc/sh/v3 v3.7.0
)

require (
Expand All @@ -20,5 +21,8 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
Expand All @@ -11,6 +15,10 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/liamg/memoryfs v1.6.0 h1:jAFec2HI1PgMTem5gR7UT8zi9u4BfG5jorCRlLH06W8=
github.com/liamg/memoryfs v1.6.0/go.mod h1:z7mfqXFQS8eSeBBsFjYLlxYRMRyiPktytvYCYTb3BSk=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
Expand All @@ -22,6 +30,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
Expand All @@ -34,8 +44,16 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
2 changes: 1 addition & 1 deletion internal/cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var dumpHelp string

func dumpCmd(opts *options) *cobra.Command {
cmd := &cobra.Command{ //nolint:exhaustruct
Use: "dump [filename]",
Use: "dump [flags] [filename]",
Aliases: []string{"d"},
Short: "Dump markdown code blocks",
Long: dumpHelp,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var extractHelp string

func extractCmd(opts *options) *cobra.Command {
cmd := &cobra.Command{ //nolint:exhaustruct
Use: "extract [filename]",
Use: "extract [flags] [filename]",
Aliases: []string{"x"},
Short: "Extract markdown code blocks to the file system",
Long: extractHelp,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/help/filtering.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
By default, `mdcode` work with all code blocks in a markdown document. It is possible to filter code blocks based on programming language or metadata. In this case, `mdcode` ignore code blocks that do not meet the filter criteria.
By default, `mdcode` work with all code blocks in a markdown document. It is possible to filter code blocks based on programming language or metadata. In this case, `mdcode` ignores code blocks that do not meet the filter criteria.

A language filter pattern can be specified using the `--lang` flag. Then only code blocks with a language matching the pattern will be processed. For example, filtering for code blocks containing JavaScript code:

Expand Down
13 changes: 13 additions & 0 deletions internal/cmd/help/run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Extract code blocks to the file system and run shell commands on them

The code blocks are written to the file named in the `file` metadata.

The code block may include `region` metadata, which contains the name of the region. In this case, the code block is written to the appropriate part of the file marked with the `#region` comment.

The optional argument of the `mdcode run` command is the name of the markdown file. If it is missing, the `README.md` file in the current directory (if it exists) is processed.

This can be followed by a double dash (`--`) and then the shell command line to be executed (even a complex command, such as `for`).

Alternatively, the commands to be executed can be embedded in a code block in the document. In this case, the language must be `sh` and it is necessary to name the code block with the metadata `name`. The name of the code block containing the commands can be specified with the `--name` flag (if not, the first code block containing the `sh` language and `name` metadata will be executed).

Code blocks are extracted to a temporary directory. This directory will be the current directory when running the commands. The temporary directory is deleted after executing the commands (deletion can be prevented by using the `--keep` flag). Instead of a temporary directory, the name of the directory to be used can be specified with the `--dir` flag. In this case, of course, the directory is not deleted after executing the commands.
10 changes: 8 additions & 2 deletions internal/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ func listRun(filename string, out io.Writer, opts *options) error {
return err
}

blocks, err := unfence(src, opts.filter)
blocks, err := unfence(src, func(lang string, meta mdcode.Meta) bool {
if isScript(lang, meta) {
return true
}

return opts.filter(lang, meta)
})
if err != nil {
return err
}
Expand Down Expand Up @@ -99,7 +105,7 @@ func metaKeys(blocks mdcode.Blocks) []string {

special := make(map[string]struct{})

for _, s := range []string{metaFile, metaOutline, metaRegion} {
for _, s := range []string{metaName, metaFile, metaOutline, metaRegion} {
special[s] = struct{}{}

if _, has := keyset[s]; has {
Expand Down
3 changes: 3 additions & 0 deletions internal/cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ const (
metaFile = "file"
metaRegion = "region"
metaOutline = "outline"
metaName = "name"
)

type statusFunc func(format string, args ...any)

type options struct {
lang []string
file []string
name string
meta map[string]string

dir string
Expand All @@ -25,6 +27,7 @@ type options struct {
json bool

quiet bool
keep bool

filter filterFunc
status statusFunc
Expand Down
32 changes: 23 additions & 9 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -34,7 +35,7 @@ func RootCmd() *cobra.Command {
opts := new(options)

cmd := &cobra.Command{ //nolint:exhaustruct
Use: appname + " [filename]",
Use: appname + " [flags] [filename]",
Short: "Markdown code block authoring tool",
Long: rootHelp,
Version: version,
Expand Down Expand Up @@ -73,11 +74,7 @@ func RootCmd() *cobra.Command {
`{{with .Name}}{{printf "%s" .}}{{end}}{{printf " version %s\n" .Version}}`,
)

flags := cmd.PersistentFlags()

flags.StringSliceVarP(&opts.file, "file", "f", []string{"?*"}, "file filter")
flags.StringSliceVarP(&opts.lang, "lang", "l", []string{"?*"}, "language filter")
flags.StringToStringVarP(&opts.meta, "meta", "m", nil, "metadata filter")
globalFlags(cmd, opts)

outputFlag(cmd, opts)

Expand All @@ -86,12 +83,21 @@ func RootCmd() *cobra.Command {
cmd.AddCommand(updateCmd(opts))
cmd.AddCommand(extractCmd(opts))
cmd.AddCommand(dumpCmd(opts))
cmd.AddCommand(runCmd(opts))

cmd.AddCommand(metadataTopic(), filteringTopic(), regionsTopic(), invisibleTopic(), outlineTopic())

return cmd
}

func globalFlags(cmd *cobra.Command, opts *options) {
flags := cmd.PersistentFlags()

flags.StringSliceVarP(&opts.file, "file", "f", []string{"?*"}, "file filter")
flags.StringSliceVarP(&opts.lang, "lang", "l", []string{"?*"}, "language filter")
flags.StringToStringVarP(&opts.meta, "meta", "m", nil, "metadata filter")
}

func outputFlag(cmd *cobra.Command, opts *options) {
cmd.Flags().StringVarP(&opts.out, "output", "o", "", "output file (default: standard output)")

Expand All @@ -106,11 +112,11 @@ func dirFlag(cmd *cobra.Command, opts *options) {

func quietFlag(cmd *cobra.Command, opts *options) {
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "suppress the status output")

cobra.CheckErr(cmd.MarkFlagDirname("dir"))
}

func checkargs(_ *cobra.Command, args []string) error {
func checkargs(cmd *cobra.Command, args []string) error {
_, args = script(cmd, args)

if len(args) > 1 {
return errTooManyArg
}
Expand Down Expand Up @@ -153,6 +159,14 @@ func source(args []string) string {
return args[0]
}

func script(cmd *cobra.Command, args []string) (string, []string) {
if cmd.ArgsLenAtDash() < 0 {
return "", args
}

return strings.Join(args[cmd.ArgsLenAtDash():], " "), args[:cmd.ArgsLenAtDash()]
}

const (
defaultArg = "README.md"

Expand Down
Loading

0 comments on commit 1426bec

Please sign in to comment.