Skip to content

Commit ed32d34

Browse files
authored
fix(config): parse flags placed after input paths (#264) (#270)
* fix(config): parse flags placed after input paths (#264) * build(lint): pin go-arch-lint to v1.15.0 and exclude work scratch dir
1 parent 6deac9e commit ed32d34

9 files changed

Lines changed: 96 additions & 4 deletions

File tree

.github/workflows/arch.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
- name: Install tools
2727
run: |
28-
go install github.com/fe3dback/go-arch-lint@v1.12.0
28+
go install github.com/fe3dback/go-arch-lint@v1.15.0
2929
3030
- name: Lint architecture
3131
run: |

.go-arch-lint.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ workdir: .
44
excludeFiles:
55
- "^.*_test\\.go$"
66
- "^.*\/test\/.*$"
7+
# work/ is a gitignored local scratch directory (see /work in .gitignore) used
8+
# for replace-based library checkouts. go-arch-lint v1.13+ walks the whole
9+
# project tree, including gitignored dirs, so exclude it to avoid flagging
10+
# those files as "not attached to any component".
11+
- "^.*\/work\/.*$"
712

813
vendors:
914
color: { in: github.com/fatih/color }

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
### Bug Fixes
6+
* Flags After Input Paths: Flags placed after file or directory arguments (e.g. `sqly --sql ... data.csv --output out.json`) are now parsed as flags instead of being silently treated as import paths that fail with "path does not exist". An unknown flag in any position fails fast with a clear parse error.
67
* History Storage Tolerance: Non-interactive runs (`--sql` and batch mode) no longer fail when the history database cannot be created or written (for example, a read-only config directory in CI or containers). History is disabled for the session with a warning, and the requested command still runs. Point `SQLY_HISTORY_DB_PATH` at a writable path to re-enable it.
78

89
### New Features

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ tools: ## Install dependency tools
5151
$(GO_INSTALL) github.com/nikolaydubina/go-cover-treemap@latest
5252
$(GO_INSTALL) github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
5353
$(GO_INSTALL) go.uber.org/mock/mockgen@latest
54-
$(GO_INSTALL) github.com/fe3dback/go-arch-lint@latest
54+
$(GO_INSTALL) github.com/fe3dback/go-arch-lint@v1.15.0
5555

5656
lint: ## Lint code
5757
golangci-lint run --config .golangci.yml

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,11 @@ $ sqly --sql "SELECT * FROM user" --csv testdata/user.csv > test.csv
278278

279279
#### For windows user
280280

281-
The sqly can save SQL execution results to the file using the --output option. The --output option specifies the destination path for SQL results specified in the --sql option.
281+
The sqly can save SQL execution results to the file using the --output option. The --output option specifies the destination path for SQL results specified in the --sql option. Flags may come before or after the file arguments, so `--output` also works at the end of the command.
282282

283283
```shell
284-
$ sqly --sql "SELECT * FROM user" --output=test.csv testdata/user.csv
284+
$ sqly --sql "SELECT * FROM user" --output=test.csv testdata/user.csv
285+
$ sqly --sql "SELECT * FROM user" testdata/user.csv --output=test.csv
285286
```
286287

287288
### Key Binding for sqly-shell

config/argument.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ func NewArg(args []string) (*Arg, error) {
7878
arg := &Arg{}
7979

8080
flag := pflag.FlagSet{}
81+
// Parse flags even when they appear after file/directory arguments. A
82+
// zero-value pflag.FlagSet disables this, which silently turns a misplaced
83+
// flag (e.g. "sqly data.csv --output out") and its value into import paths
84+
// that fail with "path does not exist". Interspersed parsing instead applies
85+
// the flag, and an unknown flag fails fast with a clear parse error. Ref #264.
86+
flag.SetInterspersed(true)
8187
flag.BoolVarP(&oFlag.csv, "csv", "c", false, "change output format to csv (default: table)")
8288
flag.BoolVarP(&oFlag.excel, "excel", "e", false, "change output format to excel (default: table)")
8389
flag.BoolVarP(&oFlag.ltsv, "ltsv", "l", false, "change output format to ltsv (default: table)")

config/argument_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,56 @@ func TestNewArg(t *testing.T) {
151151
}
152152
})
153153

154+
t.Run("--output after file path sets output destination, not an import path (#264)", func(t *testing.T) {
155+
testFile := filepath.Join(t.TempDir(), "result.csv")
156+
arg, err := NewArg([]string{"sqly", "--sql", "SELECT * FROM user", "testdata/user.csv", "--output", testFile})
157+
if err != nil {
158+
t.Fatal(err)
159+
}
160+
if arg.Output.FilePath != testFile {
161+
t.Errorf("Output.FilePath = %q, want %q", arg.Output.FilePath, testFile)
162+
}
163+
if diff := cmp.Diff([]string{"testdata/user.csv"}, arg.FilePaths); diff != "" {
164+
t.Errorf("FilePaths mismatch (-want +got):\n%s", diff)
165+
}
166+
})
167+
168+
t.Run("output-mode flag after file path sets mode, not an import path (#264)", func(t *testing.T) {
169+
arg, err := NewArg([]string{"sqly", "--sql", "SELECT * FROM user", "testdata/user.csv", "--csv"})
170+
if err != nil {
171+
t.Fatal(err)
172+
}
173+
if arg.Output.Mode != model.PrintModeCSV {
174+
t.Errorf("Output.Mode = %v, want %v", arg.Output.Mode, model.PrintModeCSV)
175+
}
176+
if diff := cmp.Diff([]string{"testdata/user.csv"}, arg.FilePaths); diff != "" {
177+
t.Errorf("FilePaths mismatch (-want +got):\n%s", diff)
178+
}
179+
})
180+
181+
t.Run("flags interspersed among file paths are not imported as paths (#264)", func(t *testing.T) {
182+
arg, err := NewArg([]string{"sqly", "testdata/user.csv", "--json", "testdata/identifier.csv", "--sql", "SELECT 1"})
183+
if err != nil {
184+
t.Fatal(err)
185+
}
186+
if arg.Output.Mode != model.PrintModeJSON {
187+
t.Errorf("Output.Mode = %v, want %v", arg.Output.Mode, model.PrintModeJSON)
188+
}
189+
if arg.Query != "SELECT 1" {
190+
t.Errorf("Query = %q, want %q", arg.Query, "SELECT 1")
191+
}
192+
if diff := cmp.Diff([]string{"testdata/user.csv", "testdata/identifier.csv"}, arg.FilePaths); diff != "" {
193+
t.Errorf("FilePaths mismatch (-want +got):\n%s", diff)
194+
}
195+
})
196+
197+
t.Run("unknown flag after file path returns a parse error (#264)", func(t *testing.T) {
198+
_, err := NewArg([]string{"sqly", "testdata/user.csv", "--nope"})
199+
if err == nil {
200+
t.Fatal("expected a parse error for an unknown flag, got nil")
201+
}
202+
})
203+
154204
t.Run("default print mode", func(t *testing.T) {
155205
arg, err := NewArg([]string{"sqly"})
156206
if err != nil {

doc/pages/markdown/how_to_use.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
If no SQL query is specified with the `--sql` option, sqly will start the sqly shell. sqly determines the file type to be loaded from the extension when the shell starts and automatically begins importing it into the SQLite3 in-memory database. Multiple files can be loaded simultaneously. The table names will be the file names (without extensions) or the Excel sheet names. If an SQL query is specified with the `--sql` option, the SQL query result will be displayed in the terminal and sqly will exit without starting the sqly shell.
44

5+
Flags may appear before or after the file and directory arguments; `sqly --csv data.csv` and `sqly data.csv --csv` are equivalent. A misplaced unknown flag fails with a parse error instead of being read as a file path.
6+
57
sqly allows you to change the display mode of SQL results with options. By default, the output is in table format. The output format can be changed to csv (`--csv`), tsv (`--tsv`), ltsv (`--ltsv`), markdown (`--markdown`), json (`--json`), or ndjson (`--ndjson`). Excel (`--excel`) and Parquet (`--parquet`) are export-only: they render as csv on screen and write a file only through `.dump` or `--output`. Since the output mode can be changed while the sqly shell is running, it is easy to execute `sqly sample.csv` and then change settings or execute SQL queries within the sqly shell.
68

79

spec/cli_spec.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,31 @@ Describe 'sqly CLI surface'
5858
rm -rf "$out_dir"
5959
End
6060
End
61+
62+
Describe 'flags after input paths (#264)'
63+
It 'applies --output placed after the file path instead of importing it'
64+
out_dir=$(mktemp -d)
65+
export out_dir
66+
When run sqly --json --sql "SELECT user_name FROM user ORDER BY identifier LIMIT 1" testdata/user.csv --output "$out_dir/result.json"
67+
The status should be success
68+
The output should not include 'path does not exist'
69+
The path "$out_dir/result.json" should be file
70+
The contents of file "$out_dir/result.json" should include '"user_name":"booker12"'
71+
rm -rf "$out_dir"
72+
End
73+
74+
It 'applies an output-mode flag placed after the file path'
75+
When run sqly --sql "SELECT user_name FROM user ORDER BY identifier LIMIT 1" testdata/user.csv --csv
76+
The status should be success
77+
The line 1 should equal 'user_name'
78+
The output should include 'booker12'
79+
The output should not include 'path does not exist'
80+
End
81+
82+
It 'fails fast on an unknown flag after the file path'
83+
When run sqly testdata/user.csv --nope
84+
The status should be failure
85+
The stderr should include 'unknown flag'
86+
End
87+
End
6188
End

0 commit comments

Comments
 (0)