1 Commits

Author SHA1 Message Date
15f7db8d3e ignore files 2025-04-02 16:23:15 +02:00
11 changed files with 93 additions and 282 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
**/__debug_bin*
main
/pat

2
.vscode/launch.json vendored
View File

@ -13,7 +13,7 @@
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "${workspaceFolder}/cmd/main.go", "program": "${workspaceFolder}/cmd/main.go",
"args": [ "."] "args": [ ".."]
} }
] ]
} }

View File

@ -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.

View File

@ -3,55 +3,36 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"os"
"git.schreifuchs.ch/schreifuchs/pat/pkg/cat" "git.schreifuchs.ch/schreifuchs/pat/pkg/cat"
"git.schreifuchs.ch/schreifuchs/pat/pkg/clip"
"git.schreifuchs.ch/schreifuchs/pat/pkg/ignore" "git.schreifuchs.ch/schreifuchs/pat/pkg/ignore"
) )
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()...)
if err != nil { if err != nil {
fmt.Println(err.Error()) panic(err)
os.Exit(1)
} }
if *ignorePath != "" { if *ignorePath != "" {
i, err := ignore.FindGitignore(*ignorePath) i, err := ignore.FindGitignore(*ignorePath)
if err != nil { if err != nil {
fmt.Printf("can't get gitignore: %v", err) panic(err)
os.Exit(1)
} }
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 fmt.Print(cats.ToString(DELEMITTER))
if *markdown {
out = cats.ToMarkdown()
} else if *typst {
out = cats.ToTypst()
} else {
out = cats.ToString(*delemitter)
}
fmt.Print(out)
if err = clip.Copy(out); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
} }

View File

@ -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()

View File

@ -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
View 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
}

View File

@ -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()
}

View File

@ -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]
}

View File

@ -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()
}

View File

@ -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], ".")
} }