Say in --help where the database is and what each report prints (closes #177)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The help of sfdupes and of scan names the database, its default path and SFDUPES_DATABASE. The help of report and trees lists the columns, the order of the rows and the four escapes. The help of info lists its lines. The help of dwim says that it scans /, should run as root and keeps the scan in the database. Model: opus-5-5
This commit is contained in:
1 parent
6c529fcc6d
commit
e210fcde97
4 files changed
+146
-9
No files matched your search
@@ -300,9 +300,11 @@ The reference below is also the program's specification.
|
||||
- `--version`, `-v` prints `sfdupes VERSION` (see "Install") to stdout and
|
||||
exits 0. It writes nothing to stderr.
|
||||
- `--help`, `-h`, alone or after a subcommand, prints the help text to stderr
|
||||
and exits 0. It writes nothing to stdout. `sfdupes scan --help` also lists the
|
||||
default excludes of the system it runs on, and the filesystem types the walk
|
||||
never records anything on.
|
||||
and exits 0. It writes nothing to stdout. The help of `sfdupes` and of `scan`
|
||||
names the database. The help of `report`, `trees`, `info` and `dwim` says what
|
||||
each prints. `sfdupes scan --help` also lists the default excludes of the
|
||||
system it runs on, and the filesystem types the walk never records anything
|
||||
on.
|
||||
|
||||
### Duplicate detection
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
Newest first. An entry ends with its date and its issue or branch, where those
|
||||
are known.
|
||||
|
||||
- The help of `sfdupes` and of `scan` names the database, its default path and
|
||||
`SFDUPES_DATABASE`. The help of `report` and `trees` lists the columns, the
|
||||
order of the rows and the escapes. The help of `info` lists its lines. The
|
||||
help of `dwim` says that it scans `/`, should run as root and keeps the scan
|
||||
in the database (2026-11-11, https://git.eeqj.de/sneak/sfdupes/issues/177)
|
||||
- `report` and `trees` leave out empty files. `report` writes no group of size
|
||||
0, and `trees` writes no group whose trees hold 0 bytes. An empty file still
|
||||
counts toward a tree's digest (2026-11-11,
|
||||
|
||||
+70
-5
@@ -31,6 +31,64 @@ const (
|
||||
cmdDwim = "dwim"
|
||||
)
|
||||
|
||||
// databaseHelp is the paragraph of the root and scan help that names
|
||||
// the database (README "Location").
|
||||
const databaseHelp = "scan stores what it finds in a database file. " +
|
||||
"report, trees, info and dwim\nread it. The file is $" + databaseEnv +
|
||||
" when that is set and not empty,\nand otherwise " +
|
||||
defaultDatabasePath + ", where creating it needs root.\n"
|
||||
|
||||
// escapesHelp is the paragraph of the report and trees help on how
|
||||
// paths are escaped (README "Report output format").
|
||||
const escapesHelp = `Four characters in a path are escaped, so that each
|
||||
row stays one line: a backslash as \\, a tab as \t, a newline as \n
|
||||
and a carriage return as \r.
|
||||
`
|
||||
|
||||
// reportHelp, treesHelp, infoHelp and dwimHelp follow the one-line
|
||||
// summary in the help of their subcommands.
|
||||
const reportHelp = `The report is tab-separated text on stdout: a header
|
||||
line, then one row per duplicate file. The columns are:
|
||||
|
||||
first the path that sorts first, byte by byte, in its group of copies
|
||||
dupe another copy of that file
|
||||
size the file's size in bytes
|
||||
|
||||
Rows are ordered by size, largest first, then by path.
|
||||
|
||||
` + escapesHelp
|
||||
|
||||
const treesHelp = `The report is tab-separated text on stdout: a header
|
||||
line, then one row per duplicate directory tree. The columns are:
|
||||
|
||||
first the path that sorts first, byte by byte, in its group of copies
|
||||
dupe another copy of that tree
|
||||
files the number of regular files in one copy
|
||||
size their total size in bytes
|
||||
disk_size the bytes the dupe copy takes on disk
|
||||
|
||||
Rows are ordered by size, largest first, then by path.
|
||||
|
||||
` + escapesHelp
|
||||
|
||||
const infoHelp = `The details go to stdout, one per line. Each line holds a name and its
|
||||
value, separated by a tab:
|
||||
|
||||
database the path, and where it came from
|
||||
database file size in bytes
|
||||
-wal file size in bytes, only while a -wal file exists
|
||||
modified when the database file last changed, in UTC
|
||||
schema version the version of the database's layout
|
||||
records the number of records
|
||||
size of recorded files the total size of the recorded files, in bytes
|
||||
`
|
||||
|
||||
const dwimHelp = `dwim runs what "sfdupes scan /" runs, then what "sfdupes trees" runs.
|
||||
Run it as root, so that the scan can read every file. The scan is stored
|
||||
in the database, as scan stores it, so "sfdupes trees" prints the report
|
||||
again later.
|
||||
`
|
||||
|
||||
// errNoSubcommand is returned by the root command when it is invoked
|
||||
// without a subcommand. That is a usage error, and the usage text
|
||||
// cobra prints for it is the whole message.
|
||||
@@ -196,15 +254,21 @@ func newRootCommand(stdout, stderr io.Writer) *cobra.Command {
|
||||
}),
|
||||
}
|
||||
|
||||
root.Long = root.Short + ".\n\n" + databaseHelp
|
||||
reportCmd.Long = reportCmd.Short + ".\n\n" + reportHelp
|
||||
treesCmd.Long = treesCmd.Short + ".\n\n" + treesHelp
|
||||
infoCmd.Long = infoCmd.Short + ".\n\n" + infoHelp
|
||||
dwimCmd.Long = dwimCmd.Short + ".\n\n" + dwimHelp
|
||||
|
||||
root.AddCommand(newScanCommand(), reportCmd, treesCmd, infoCmd, dwimCmd)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
// newScanCommand builds the scan subcommand, with its --workers, -x and
|
||||
// --no-default-excludes flags. Its help lists the default excludes of
|
||||
// the system it runs on, and the filesystem types it never records
|
||||
// anything on.
|
||||
// --no-default-excludes flags. Its help names the database, and lists
|
||||
// the default excludes of the system it runs on and the filesystem
|
||||
// types it never records anything on.
|
||||
func newScanCommand() *cobra.Command {
|
||||
var (
|
||||
scanWorkers int
|
||||
@@ -217,8 +281,9 @@ func newScanCommand() *cobra.Command {
|
||||
scanCmd := &cobra.Command{
|
||||
Use: cmdScan + " [--workers N] [-x] [--no-default-excludes] PATH...",
|
||||
Short: short,
|
||||
Long: short + ".\n\n" + defaultExcludesHelp(runtime.GOOS),
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Long: short + ".\n\n" + databaseHelp + "\n" +
|
||||
defaultExcludesHelp(runtime.GOOS),
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return checkScanWorkers(cmd, scanWorkers)
|
||||
},
|
||||
|
||||
@@ -22,6 +22,9 @@ import (
|
||||
// error prints and a runtime failure must not.
|
||||
const usageMarker = "Usage:"
|
||||
|
||||
// helpFlag asks for the help text.
|
||||
const helpFlag = "--help"
|
||||
|
||||
// TestMain stops the tests at once, saying why, when their temporary
|
||||
// directory is not on a persistent filesystem: scan records nothing
|
||||
// there (README "Rules for the walk"), so most scan tests would fail
|
||||
@@ -692,7 +695,7 @@ func TestRunHelp(t *testing.T) {
|
||||
|
||||
// README §Settings: help goes to stderr, exits 0, and leaves stdout
|
||||
// empty.
|
||||
cases := [][]string{{"--help"}, {"-h"}, {cmdScan, "--help"}}
|
||||
cases := [][]string{{helpFlag}, {"-h"}, {cmdScan, helpFlag}}
|
||||
|
||||
for _, args := range cases {
|
||||
var stdout, stderr bytes.Buffer
|
||||
@@ -714,6 +717,68 @@ func TestRunHelp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunHelpDescribesDatabaseAndOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// README §Settings: the help names the database, and says what
|
||||
// report, trees, info and dwim print.
|
||||
database := []string{databaseEnv, defaultDatabasePath, "needs root"}
|
||||
escapes := []string{`\\`, `\t`, `\n`, `\r`}
|
||||
|
||||
// The subcommand whose help is printed; none for the root.
|
||||
cases := []struct {
|
||||
subcommand []string
|
||||
want []string
|
||||
}{
|
||||
{nil, database},
|
||||
{[]string{cmdScan}, database},
|
||||
{
|
||||
[]string{cmdReport},
|
||||
append([]string{
|
||||
"tab-separated", "\n first ", "\n dupe ", "\n size ",
|
||||
}, escapes...),
|
||||
},
|
||||
{
|
||||
[]string{cmdTrees},
|
||||
append([]string{
|
||||
"tab-separated", "\n first ", "\n dupe ", "\n files ",
|
||||
"\n size ", "\n disk_size ",
|
||||
}, escapes...),
|
||||
},
|
||||
{
|
||||
[]string{cmdInfo},
|
||||
[]string{
|
||||
"\n database ", "\n database file size ",
|
||||
"\n -wal file size ", "\n modified ",
|
||||
"\n schema version ", "\n records ",
|
||||
"\n size of recorded files ",
|
||||
},
|
||||
},
|
||||
{
|
||||
[]string{cmdDwim},
|
||||
[]string{"sfdupes scan /", "as root", "sfdupes trees"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
args := slices.Concat(tc.subcommand, []string{helpFlag})
|
||||
|
||||
code := run(args, &stdout, &stderr)
|
||||
if code != exitOK {
|
||||
t.Fatalf("run(%v) = %d, want %d", args, code, exitOK)
|
||||
}
|
||||
|
||||
for _, want := range tc.want {
|
||||
if !strings.Contains(stderr.String(), want) {
|
||||
t.Errorf("run(%v) help lacks %q:\n%s",
|
||||
args, want, stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in new issue
Block a user