Skip to content

Commit d6e39da

Browse files
committed
add
1 parent 72e72ed commit d6e39da

File tree

2 files changed

+160
-15
lines changed

2 files changed

+160
-15
lines changed

Linux/extraxtor/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
### ℹ️ About
2+
Archive Extractor with Intelligent Directory Flattening.<br>
3+
4+
### 🧰 Usage
5+
```mathematica
6+
❯ extraxtor --help
7+
8+
NAME:
9+
extraxtor - Archive Extractor with Intelligent Directory Flattening
10+
11+
USAGE:
12+
extraxtor [global options] [command [command options]]
13+
14+
VERSION:
15+
0.0.1
16+
17+
COMMANDS:
18+
inspect, ls, list Inspect archive contents without extraction
19+
help, h Shows a list of commands or help for one command
20+
21+
GLOBAL OPTIONS:
22+
--input string, -i string Input archive file
23+
--output string, -o string Output directory (default: current directory)
24+
--force, -f Force extraction, overwrite existing files (default: false)
25+
--quiet, -q Suppress all output except errors (default: false)
26+
--debug, -d Enable debug output (default: false)
27+
--no-flatten, -n Don't flatten nested single directories (default: false)
28+
--tree, -t Show tree output after extraction (default: false)
29+
--help, -h show help
30+
--version, -v print the version
31+
```
32+
33+
### 🛠️ Building
34+
```bash
35+
curl -qfsSL 'https://github.com/pkgforge/devscripts/raw/refs/heads/main/Linux/extraxtor/main.go'
36+
go mod init "github.com/pkgforge/devscripts/extraxtor"
37+
go mod tidy -v
38+
39+
export CGO_ENABLED="0"
40+
export GOARCH="amd64"
41+
export GOOS="linux"
42+
43+
go build -a -v -x -trimpath \
44+
-buildvcs="false" \
45+
-ldflags="-s -w -buildid= -extldflags '-s -w -Wl,--build-id=none'" \
46+
-o "./extraxtor"
47+
48+
"./extraxtor" --help
49+
```
Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
)
2222

2323
const (
24-
Version = "2.0.1"
24+
Version = "0.0.1"
2525
MaxConcurrency = 4
2626
BufferSize = 64 * 1024 // 64KB buffer for file operations
2727
)
@@ -156,7 +156,7 @@ func (e *Extractor) cyan(text string) string { return e.logger.cyan(text) }
156156
func (e *Extractor) yellow(text string) string { return e.logger.yellow(text) }
157157
func (e *Extractor) blue(text string) string { return e.logger.blue(text) }
158158

159-
// ValidateInputs performs comprehensive input validation with better error handling
159+
// Input validation
160160
func (e *Extractor) ValidateInputs() error {
161161
if e.config.InputFile == "" {
162162
return fmt.Errorf("input file is required")
@@ -188,7 +188,7 @@ func (e *Extractor) ValidateInputs() error {
188188
return fmt.Errorf("input file is empty: %s", e.config.InputFile)
189189
}
190190

191-
// Handle output directory with better validation
191+
// Handle output directory
192192
if e.config.OutputDir == "" {
193193
e.config.OutputDir = "."
194194
}
@@ -224,7 +224,7 @@ func (e *Extractor) ValidateInputs() error {
224224
return nil
225225
}
226226

227-
// isDirEmpty checks if a directory is empty with better error handling
227+
// isDirEmpty checks if a directory is empty
228228
func (e *Extractor) isDirEmpty(dir string) (bool, error) {
229229
f, err := os.Open(dir)
230230
if err != nil {
@@ -272,7 +272,7 @@ func (e *Extractor) DetectAndValidateArchive(ctx context.Context) (archives.Form
272272
return format, nil
273273
}
274274

275-
// ExtractArchive performs extraction with improved error handling and progress tracking
275+
// ExtractArchive performs extraction with progress tracking
276276
func (e *Extractor) ExtractArchive(ctx context.Context, format archives.Format) error {
277277
if err := os.MkdirAll(e.config.OutputDir, 0755); err != nil {
278278
return fmt.Errorf("failed to create output directory: %w", err)
@@ -831,19 +831,18 @@ func main() {
831831
Name: "extraxtor",
832832
Usage: "Archive Extractor with Intelligent Directory Flattening",
833833
Version: Version,
834-
Authors: []any{"Rewritten in Go"},
835834
Flags: []cli.Flag{
836835
&cli.StringFlag{Name: "input", Aliases: []string{"i"}, Usage: "Input archive file"},
837836
&cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "Output directory (default: current directory)"},
838837
&cli.BoolFlag{Name: "force", Aliases: []string{"f"}, Usage: "Force extraction, overwrite existing files"},
839838
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, Usage: "Suppress all output except errors"},
840-
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, Usage: "Enable debug output"}, // Changed from verbose to debug
839+
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, Usage: "Enable debug output"},
841840
&cli.BoolFlag{Name: "no-flatten", Aliases: []string{"n"}, Usage: "Don't flatten nested single directories"},
842841
&cli.BoolFlag{Name: "tree", Aliases: []string{"t"}, Usage: "Show tree output after extraction"},
843842
},
844843
Action: func(ctx context.Context, c *cli.Command) error {
845844
config := &Config{
846-
Verbose: c.Bool("debug"), // Use debug instead of verbose
845+
Verbose: c.Bool("debug"),
847846
Quiet: c.Bool("quiet"),
848847
Force: c.Bool("force"),
849848
Flatten: !c.Bool("no-flatten"),
@@ -912,6 +911,41 @@ func inspectArchive(ctx context.Context, archivePath string, jsonOutput, treeOut
912911
return fmt.Errorf("unsupported archive format for inspection")
913912
}
914913

914+
if err := func() error {
915+
defer func() {
916+
if r := recover(); r != nil {
917+
// Silently ignore archives that don't support listing
918+
}
919+
}()
920+
921+
// Test if we can actually list the archive
922+
testHandler := func(ctx context.Context, f archives.FileInfo) error {
923+
return nil
924+
}
925+
926+
return extractor.Extract(ctx, input, testHandler)
927+
}(); err != nil {
928+
if treeOutput {
929+
fmt.Printf("Tree view not supported for this archive format\n")
930+
return nil
931+
}
932+
return fmt.Errorf("archive format does not support content listing: %w", err)
933+
}
934+
935+
// Reset file pointer for actual processing
936+
file.Close()
937+
file, err = os.Open(archivePath)
938+
if err != nil {
939+
return fmt.Errorf("failed to reopen archive: %w", err)
940+
}
941+
defer file.Close()
942+
943+
format, input, err = archives.Identify(ctx, archivePath, file)
944+
if err != nil {
945+
return fmt.Errorf("failed to re-identify archive format: %w", err)
946+
}
947+
extractor = format.(archives.Extractor)
948+
915949
var entries []FileEntry
916950
var totalSize int64
917951
var fileCount, dirCount int
@@ -958,13 +992,75 @@ func inspectArchive(ctx context.Context, archivePath string, jsonOutput, treeOut
958992
})
959993
}
960994

961-
if treeOutput {
962-
for _, entry := range entries {
963-
prefix := map[bool]string{true: "📁 ", false: "📄 "}[entry.IsDir]
964-
fmt.Printf("%s%s\n", prefix, entry.Name)
965-
}
966-
return nil
967-
}
995+
if treeOutput {
996+
fmt.Printf("Archive: %s\n", filepath.Base(archivePath))
997+
998+
// Build tree structure
999+
tree := make(map[string][]string)
1000+
dirs := make(map[string]bool)
1001+
1002+
for _, entry := range entries {
1003+
parts := strings.Split(entry.Name, string(filepath.Separator))
1004+
for i := 0; i < len(parts); i++ {
1005+
path := strings.Join(parts[:i+1], string(filepath.Separator))
1006+
parent := ""
1007+
if i > 0 {
1008+
parent = strings.Join(parts[:i], string(filepath.Separator))
1009+
}
1010+
1011+
if entry.IsDir || i < len(parts)-1 {
1012+
dirs[path] = true
1013+
}
1014+
1015+
if parent != "" {
1016+
tree[parent] = append(tree[parent], parts[i])
1017+
} else if i == 0 {
1018+
tree[""] = append(tree[""], parts[i])
1019+
}
1020+
}
1021+
}
1022+
1023+
// Remove duplicates and sort
1024+
for k := range tree {
1025+
unique := make(map[string]bool)
1026+
for _, v := range tree[k] {
1027+
unique[v] = true
1028+
}
1029+
tree[k] = make([]string, 0, len(unique))
1030+
for v := range unique {
1031+
tree[k] = append(tree[k], v)
1032+
}
1033+
sort.Strings(tree[k])
1034+
}
1035+
1036+
// Print tree
1037+
var printTree func(path string, depth int)
1038+
printTree = func(path string, depth int) {
1039+
children := tree[path]
1040+
for i, child := range children {
1041+
indent := strings.Repeat(" ", depth)
1042+
connector := "├── "
1043+
if i == len(children)-1 {
1044+
connector = "└── "
1045+
}
1046+
1047+
childPath := child
1048+
if path != "" {
1049+
childPath = path + string(filepath.Separator) + child
1050+
}
1051+
1052+
if dirs[childPath] {
1053+
fmt.Printf("%s%s📁 %s/\n", indent, connector, child)
1054+
printTree(childPath, depth+1)
1055+
} else {
1056+
fmt.Printf("%s%s📄 %s\n", indent, connector, child)
1057+
}
1058+
}
1059+
}
1060+
1061+
printTree("", 0)
1062+
return nil
1063+
}
9681064

9691065
if verbose {
9701066
fmt.Printf("Archive: %s (%d files, %d directories, %s total)\n\n",

0 commit comments

Comments
 (0)