Compare commits
	
		
			3 Commits
		
	
	
		
			021ea61c9b
			...
			version2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f2976c66e3 | ||
|   | 0562cc0877 | ||
|   | 15f7db8d3e | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,2 @@ | |||||||
| **/__debug_bin* | **/__debug_bin* | ||||||
| main | main | ||||||
| /pat |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,7 @@ | |||||||
|             "request": "launch", |             "request": "launch", | ||||||
|             "mode": "auto", |             "mode": "auto", | ||||||
|             "program": "${workspaceFolder}/cmd/main.go", |             "program": "${workspaceFolder}/cmd/main.go", | ||||||
|             "args": [ "."] |             "args": [ ".."] | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,22 +1,18 @@ | |||||||
| # PAT | # PAT | ||||||
|  |  | ||||||
|  |  | ||||||
| ## What it Does | ## What it Does | ||||||
|  |  | ||||||
| `pat` is a command-line tool for concatenating and displaying the contents of files and directories. It: | `pat` is a command-line tool for concatenating and displaying the contents of files and directories. It: | ||||||
|  |  | ||||||
| 1. Processes the specified files and directories recursively. | 1. Processes the specified files and directories recursively. | ||||||
| 2. Appends content to a structured output with file paths and a delimiter for clarity. | 2. Appends content to a structured output with file paths and a delimiter for clarity. | ||||||
| 3. Copies the resulting output to the system clipboard for convenient sharing or reuse. | 3. Copies the resulting output to the system clipboard for convenient sharing or reuse. | ||||||
|  |  | ||||||
| Example use case: | Example use case: | ||||||
|  |  | ||||||
| - Aggregate and view the contents of multiple files or directories in one command. | - Aggregate and view the contents of multiple files or directories in one command. | ||||||
| - Automatically copy the aggregated result to the clipboard for seamless integration with other tools or platforms. | - Automatically copy the aggregated result to the clipboard for seamless integration with other tools or platforms. | ||||||
| - Export your codebase to a markdown document. |  | ||||||
| - Export your codebase to a typst document. |  | ||||||
|  |  | ||||||
| ## Dependencies | ## Dependencies | ||||||
|  |  | ||||||
| 1. **Golang** (only to install / build): | 1. **Golang** (only to install / build): | ||||||
|    - The application requires the Go programming language (`>= 1.18`) to compile and run. |    - The application requires the Go programming language (`>= 1.18`) to compile and run. | ||||||
|    - Dependency: `golang.design/x/clipboard` for clipboard interaction. |    - Dependency: `golang.design/x/clipboard` for clipboard interaction. | ||||||
| @@ -25,43 +21,31 @@ Example use case: | |||||||
|  |  | ||||||
| ## Installation | ## Installation | ||||||
|  |  | ||||||
| Install pat: | Install go-cat directly using go install: | ||||||
|  |  | ||||||
| ``` sh | ``` sh | ||||||
| git clone --depth 1 https://git.schreifuchs.ch/schreifuchs/pat.git | go install git.schreifuchs.ch/schreifuchs/pat@latest | ||||||
| cd pat |  | ||||||
| go build -o=pat cmd/main.go |  | ||||||
| mv pat $GOPATH/bin/ |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| In one go: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| git clone --depth 1 https://git.schreifuchs.ch/schreifuchs/pat.git && cd pat && go build -o=pat cmd/main.go && mv pat $GOPATH/bin/ |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| The binary will be placed in your $GOPATH/bin directory. Ensure $GOPATH/bin is in your system's PATH to run it directly. | The binary will be placed in your $GOPATH/bin directory. Ensure $GOPATH/bin is in your system's PATH to run it directly. | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Example Usage | ## Example Usage | ||||||
|  | Concatenate files and directories: | ||||||
| ### Concatenate files and directories: |  | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| pat file1.txt folder/ | ./pat file1.txt folder/ | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Output is printed to the terminal and copied to the clipboard, allowing you to paste it elsewhere. | Output is printed to the terminal and copied to the clipboard, allowing you to paste it elsewhere. | ||||||
|  |  | ||||||
| ### Create printed |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
|  |  | ||||||
| pat -t -i .gitignore . | typst compile /dev/stdin pat.pdf |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| #### Notes | #### Notes | ||||||
|  |  | ||||||
| - The tool uses `---------------------------------------------------------------------------` as a delimiter to separate file contents for readability. | - The tool uses `---------------------------------------------------------------------------` as a delimiter to separate file contents for readability. | ||||||
| - If clipboard functionality fails (e.g., unsupported environment), the application will still display the result in the terminal. | - If clipboard functionality fails (e.g., unsupported environment), the application will still display the result in the terminal. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								cmd/main.go
									
									
									
									
									
								
							| @@ -13,11 +13,9 @@ import ( | |||||||
| const DELEMITTER = "-(%s)--------------------------------------------------------------------------\n" | const DELEMITTER = "-(%s)--------------------------------------------------------------------------\n" | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
|  |  | ||||||
| 	ignorePath := flag.String("i", "", "set path to gitignore, if no gitignore parent dirs will be searched") | 	ignorePath := flag.String("i", "", "set path to gitignore, if no gitignore parent dirs will be searched") | ||||||
| 	hiddenFiles := flag.Bool("h", false, "show hidden files") | 	hiddenFiles := flag.Bool("h", false, "show hidden files") | ||||||
| 	delemitter := flag.String("d", DELEMITTER, "delemitter to use to split files when not in markdown mode must contain %s for filename") |  | ||||||
| 	markdown := flag.Bool("m", false, "markdown mode, outputs files in markdown") |  | ||||||
| 	typst := flag.Bool("t", false, "typst mode, outputs files in typst") |  | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
|  |  | ||||||
| 	cats, err := cat.Path(flag.Args()...) | 	cats, err := cat.Path(flag.Args()...) | ||||||
| @@ -35,19 +33,11 @@ func main() { | |||||||
|  |  | ||||||
| 		cats = cats.Ignored(i) | 		cats = cats.Ignored(i) | ||||||
| 	} | 	} | ||||||
|  | 	if *hiddenFiles == false { | ||||||
| 	if !*hiddenFiles { |  | ||||||
| 		cats = cats.Ignored(ignore.Filesystem{}) | 		cats = cats.Ignored(ignore.Filesystem{}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var out string | 	out := cats.ToString(DELEMITTER) | ||||||
| 	if *markdown { |  | ||||||
| 		out = cats.ToMarkdown() |  | ||||||
| 	} else if *typst { |  | ||||||
| 		out = cats.ToTypst() |  | ||||||
| 	} else { |  | ||||||
| 		out = cats.ToString(*delemitter) |  | ||||||
| 	} |  | ||||||
| 	fmt.Print(out) | 	fmt.Print(out) | ||||||
|  |  | ||||||
| 	if err = clip.Copy(out); err != nil { | 	if err = clip.Copy(out); err != nil { | ||||||
|   | |||||||
| @@ -6,10 +6,10 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Cater []entry | type Cater map[string]string | ||||||
|  |  | ||||||
| func Path(paths ...string) (c Cater, err error) { | func Path(paths ...string) (c Cater, err error) { | ||||||
| 	c = make(Cater, 0, 10) | 	c = make(Cater) | ||||||
| 	var p os.FileInfo | 	var p os.FileInfo | ||||||
|  |  | ||||||
| 	for _, path := range paths { | 	for _, path := range paths { | ||||||
| @@ -17,29 +17,28 @@ func Path(paths ...string) (c Cater, err error) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		var e entry |  | ||||||
| 		if p.IsDir() { | 		if p.IsDir() { | ||||||
| 			e, err = c.dir(path) | 			err = c.dir(path) | ||||||
| 		} else { | 		} else { | ||||||
| 			e, err = c.file(path) | 			err = c.file(path) | ||||||
| 		} | 		} | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		c = append(c, e) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c Cater) Ignored(ignore ignorer) Cater { | func (c Cater) Ignored(ignore ignorer) Cater { | ||||||
| 	cat := make(Cater, 0, len(c)) | 	cat := make(Cater) | ||||||
| 	ok := func(e entry) bool { |  | ||||||
| 		return !ignore.Ignore(e.fqname) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, entry := range c { | 	for name, content := range c { | ||||||
| 		cat = append(cat, entry.filter(ok)) | 		if ignore.Ignore(name) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		cat[name] = content | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return cat | 	return cat | ||||||
| @@ -48,20 +47,9 @@ func (c Cater) Ignored(ignore ignorer) Cater { | |||||||
| func (c Cater) ToString(delemiter string) string { | func (c Cater) ToString(delemiter string) string { | ||||||
| 	var sb strings.Builder | 	var sb strings.Builder | ||||||
|  |  | ||||||
| 	var entries []entry | 	for name, content := range c { | ||||||
| 	entries = c | 		sb.WriteString(fmt.Sprintf(delemiter, name)) | ||||||
|  | 		sb.WriteString(content) | ||||||
| 	for len(entries) > 0 { |  | ||||||
| 		n := make([]entry, 0, len(entries)) |  | ||||||
| 		for _, e := range entries { |  | ||||||
| 			if len(e.children) > 0 { |  | ||||||
| 				n = append(n, e.children...) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			sb.WriteString(fmt.Sprintf(delemiter, e.fqname)) |  | ||||||
| 			sb.WriteString(e.content) |  | ||||||
| 		} |  | ||||||
| 		entries = n |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return sb.String() | 	return sb.String() | ||||||
|   | |||||||
| @@ -1,32 +0,0 @@ | |||||||
| package cat |  | ||||||
|  |  | ||||||
| type entry struct { |  | ||||||
| 	name     string |  | ||||||
| 	fqname   string |  | ||||||
| 	content  string |  | ||||||
| 	children []entry |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (e entry) filter(ok func(e entry) bool) entry { |  | ||||||
| 	children := make([]entry, 0, len(e.children)) |  | ||||||
| 	for _, entry := range e.children { |  | ||||||
| 		if !ok(entry) { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		children = append(children, entry.filter(ok)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return entry{ |  | ||||||
| 		name:     e.name, |  | ||||||
| 		fqname:   e.fqname, |  | ||||||
| 		content:  e.content, |  | ||||||
| 		children: children, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (e entry) traverse(lvl int, do func(e entry, lvl int)) { |  | ||||||
| 	do(e, lvl) |  | ||||||
| 	for _, entry := range e.children { |  | ||||||
| 		entry.traverse(lvl+1, do) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										58
									
								
								pkg/cat/internal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								pkg/cat/internal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | package cat | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (c Cater) dir(dir string) error { | ||||||
|  | 	files, err := os.ReadDir(dir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		i, err := file.Info() | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		path := path.Join(dir, i.Name()) | ||||||
|  |  | ||||||
|  | 		if !file.IsDir() { | ||||||
|  | 			c.file(path) | ||||||
|  | 		} else { | ||||||
|  | 			c.dir(path) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c Cater) file(filePath string) error { | ||||||
|  | 	file, err := os.Open(filePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer file.Close() | ||||||
|  |  | ||||||
|  | 	// read file into strings.Builder | ||||||
|  | 	var sb strings.Builder | ||||||
|  | 	reader := bufio.NewReader(file) | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		line, err := reader.ReadString('\n') | ||||||
|  | 		if err != nil && err != io.EOF { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		sb.WriteString(line) | ||||||
|  | 		if err == io.EOF { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c[filePath] = sb.String() | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| package cat |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func (c Cater) ToMarkdown() string { |  | ||||||
| 	var sb strings.Builder |  | ||||||
|  |  | ||||||
| 	write := func(e entry, lvl int) { |  | ||||||
| 		for range lvl { |  | ||||||
| 			sb.WriteString("#") |  | ||||||
| 		} |  | ||||||
| 		sb.WriteString(fmt.Sprintf(" %s (`%s`)\n", e.name, e.fqname)) |  | ||||||
|  |  | ||||||
| 		if len(e.content) > 0 { |  | ||||||
| 			prts := strings.Split(e.name, ".") |  | ||||||
| 			sb.WriteString( |  | ||||||
| 				fmt.Sprintf( |  | ||||||
| 					"```%s\n%s\n```\n\n", |  | ||||||
|  |  | ||||||
| 					prts[len(prts)-1], |  | ||||||
| 					strings.ReplaceAll( |  | ||||||
| 						e.content, |  | ||||||
| 						"```", |  | ||||||
| 						"\\`\\`\\`", |  | ||||||
| 					), |  | ||||||
| 				), |  | ||||||
| 			) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, e := range c { |  | ||||||
| 		e.traverse(1, write) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return sb.String() |  | ||||||
| } |  | ||||||
| @@ -1,81 +0,0 @@ | |||||||
| package cat |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 	"strings" |  | ||||||
| 	"unicode/utf8" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func (c Cater) dir(dir string) (e entry, err error) { |  | ||||||
| 	files, err := os.ReadDir(dir) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	e.fqname = dir |  | ||||||
| 	e.name = name(dir) |  | ||||||
| 	e.children = []entry{} |  | ||||||
|  |  | ||||||
| 	for _, file := range files { |  | ||||||
| 		i, err := file.Info() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return e, err |  | ||||||
| 		} |  | ||||||
| 		path := path.Join(dir, i.Name()) |  | ||||||
|  |  | ||||||
| 		var ent entry |  | ||||||
| 		if !file.IsDir() { |  | ||||||
| 			ent, err = c.file(path) |  | ||||||
| 		} else { |  | ||||||
| 			ent, err = c.dir(path) |  | ||||||
| 		} |  | ||||||
| 		if err != nil { |  | ||||||
| 			return e, err |  | ||||||
| 		} |  | ||||||
| 		e.children = append(e.children, ent) |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c Cater) file(filePath string) (e entry, err error) { |  | ||||||
| 	file, err := os.Open(filePath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	defer file.Close() |  | ||||||
|  |  | ||||||
| 	e.fqname = filePath |  | ||||||
| 	e.name = name(filePath) |  | ||||||
| 	e.children = []entry{} |  | ||||||
|  |  | ||||||
| 	// read file into strings.Builder |  | ||||||
| 	var sb strings.Builder |  | ||||||
| 	reader := bufio.NewReader(file) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		line, err := reader.ReadString('\n') |  | ||||||
| 		if err != nil && err != io.EOF { |  | ||||||
| 			return e, err |  | ||||||
| 		} |  | ||||||
| 		if !utf8.Valid([]byte(line)) { |  | ||||||
| 			e.content = "blob\n" |  | ||||||
| 			return e, nil |  | ||||||
| 		} |  | ||||||
| 		sb.WriteString(line) |  | ||||||
| 		if err == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	e.content = sb.String() |  | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func name(name string) string { |  | ||||||
| 	ps := strings.Split(name, "/") |  | ||||||
| 	return ps[len(ps)-1] |  | ||||||
| } |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| package cat |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func (c Cater) ToTypst() string { |  | ||||||
| 	var sb strings.Builder |  | ||||||
|  |  | ||||||
| 	write := func(e entry, lvl int) { |  | ||||||
| 		for range lvl { |  | ||||||
| 			sb.WriteString("=") |  | ||||||
| 		} |  | ||||||
| 		sb.WriteString(fmt.Sprintf(" %s (`%s`)\n", e.name, e.fqname)) |  | ||||||
|  |  | ||||||
| 		if len(e.content) > 0 { |  | ||||||
| 			prts := strings.Split(e.name, ".") |  | ||||||
| 			sb.WriteString( |  | ||||||
| 				fmt.Sprintf( |  | ||||||
| 					"```%s\n%s\n```\n\n", |  | ||||||
|  |  | ||||||
| 					prts[len(prts)-1], |  | ||||||
| 					strings.ReplaceAll( |  | ||||||
| 						e.content, |  | ||||||
| 						"```", |  | ||||||
| 						"\\`\\`\\`", |  | ||||||
| 					), |  | ||||||
| 				), |  | ||||||
| 			) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, e := range c { |  | ||||||
|  |  | ||||||
| 		sb.WriteString("= Export\n") |  | ||||||
| 		sb.WriteString("#outline()\n") |  | ||||||
| 		e.traverse(1, write) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return sb.String() |  | ||||||
| } |  | ||||||
| @@ -1,12 +1,9 @@ | |||||||
| package ignore | package ignore | ||||||
|  |  | ||||||
| import ( | import "strings" | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Filesystem struct{} | type Filesystem struct{} | ||||||
|  |  | ||||||
| func (f Filesystem) Ignore(name string) bool { | func (f Filesystem) Ignore(name string) bool { | ||||||
| 	parts := strings.Split(name, "/") | 	return strings.Contains(name, "/.") || strings.HasPrefix(name, ".") | ||||||
| 	return strings.HasPrefix(parts[len(parts)-1], ".") |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user