Skip to content

Commit

Permalink
feat: add config file, add ollama integration
Browse files Browse the repository at this point in the history
  • Loading branch information
sammcj committed Jul 29, 2024
1 parent eaf972b commit 52e4676
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 44 deletions.
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ It's intended use case is for preparing content to be provided to AI/LLMs.

- Traverse directory structures and generate a tree view
- Include/exclude files based on glob patterns
- Generate git diffs and logs
- Parse output directly to Ollama for processing
- Generate and include git diffs and logs
- Count approximate tokens for LLM compatibility
- Customisable output templates
- Copy output to clipboard (when available)
Expand All @@ -37,7 +38,7 @@ go install github.com/sammcj/ingest@HEAD
Basic usage:

```shell
ingest [flags] <path>
ingest [flags] <paths>
```

ingest will default the current working directory, if no path is provided, e.g:
Expand All @@ -61,12 +62,52 @@ Generate a prompt with git diff and copy to clipboard:
ingest -d /path/to/project
```

Generate a prompt for multiple files/directories:

```shell
ingest /path/to/project /path/to/other/project
```

Generate a prompt and save to a file:

```shell
ingest -o output.md /path/to/project
```

## Ollama Integration

Ingest can pass the generated prompt to [Ollama](https://ollama.com) for processing.

![ingest ollama](ollama-ingest.png)

```shell
ingest --ollama /path/to/project
```

By default this will ask you to enter a prompt:

```shell
./ingest utils.go --ollama
⠋ Traversing directory and building tree... [0s]
[!] Enter Ollama prompt:
explain this code
This is Go code for a file named `utils.go`. It contains various utility functions for
handling terminal output, clipboard operations, and configuration directories.
...
```

## Configuration

Ingest uses a configuration file located at `~/.config/ingest/config.json`.

You can make Ollama processing run without prompting setting `"ollama_auto_run": true` in the config file.

The config file also contains:

- "ollama_model": The model to use for processing the prompt, e.g. "llama3.1:8b-q5_k_m".
- "ollama_prompt_prefix": An optional prefix to prepend to the prompt, e.g. "This is my application."
- "ollama_prompt_suffix": An optional suffix to append to the prompt, e.g. "explain this code"

### Flags

- `-i, --include`: Patterns to include (can be used multiple times)
Expand All @@ -76,6 +117,7 @@ ingest -o output.md /path/to/project
- `--tokens`: Display the token count of the generated prompt
- `-c, --encoding`: Optional tokeniser to use for token count
- `-o, --output`: Optional output file path
- `--ollama`: Send the generated prompt to Ollama for processing
- `-d, --diff`: Include git diff
- `--git-diff-branch`: Generate git diff between two branches
- `--git-log-branch`: Retrieve git log between two branches
Expand Down
74 changes: 74 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package config

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/mitchellh/go-homedir"
)

type OllamaConfig struct {
Model string `json:"ollama_model"`
PromptPrefix string `json:"ollama_prompt_prefix"`
PromptSuffix string `json:"ollama_prompt_suffix"`
AutoRun bool `json:"ollama_auto_run"`
}

type Config struct {
Ollama []OllamaConfig `json:"ollama"`
}

func LoadConfig() (*Config, error) {
home, err := homedir.Dir()
if err != nil {
return nil, fmt.Errorf("failed to get home directory: %w", err)
}

configPath := filepath.Join(home, ".config", "ingest", "ingest.json")
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return createDefaultConfig(configPath)
}

file, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}

var config Config
if err := json.Unmarshal(file, &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}

return &config, nil
}

func createDefaultConfig(configPath string) (*Config, error) {
defaultConfig := Config{
Ollama: []OllamaConfig{
{
Model: "llama3.1:8b-instruct-q6_K",
PromptPrefix: "Code: ",
PromptSuffix: "",
AutoRun: false,
},
},
}

err := os.MkdirAll(filepath.Dir(configPath), 0755)
if err != nil {
return nil, fmt.Errorf("failed to create config directory: %w", err)
}

file, err := json.MarshalIndent(defaultConfig, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal default config: %w", err)
}

if err := os.WriteFile(configPath, file, 0644); err != nil {
return nil, fmt.Errorf("failed to write default config file: %w", err)
}

return &defaultConfig, nil
}
65 changes: 45 additions & 20 deletions filesystem/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,37 +134,62 @@ func WalkDirectory(rootPath string, includePatterns, excludePatterns []string, p
return "", nil, fmt.Errorf("failed to read .gitignore: %w", err)
}

// Generate the tree representation
treeString, err := generateTreeString(rootPath, allExcludePatterns)
// Check if rootPath is a file or directory
fileInfo, err := os.Stat(rootPath)
if err != nil {
return "", nil, fmt.Errorf("failed to generate directory tree: %w", err)
return "", nil, fmt.Errorf("failed to get file info: %w", err)
}

// Process files
err = filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(rootPath, path)
if err != nil {
return err
}
var treeString string

if shouldIncludeFile(relPath, includePatterns, allExcludePatterns, gitignore, includePriority) && !info.IsDir() {
if !fileInfo.IsDir() {
// Handle single file
relPath := filepath.Base(rootPath)
if shouldIncludeFile(relPath, includePatterns, allExcludePatterns, gitignore, includePriority) {
wg.Add(1)
go func(path, relPath string, info os.FileInfo) {
go func() {
defer wg.Done()
processFile(path, relPath, rootPath, lineNumber, relativePaths, noCodeblock, &mu, &files)
}(path, relPath, info)
processFile(rootPath, relPath, filepath.Dir(rootPath), lineNumber, relativePaths, noCodeblock, &mu, &files)
}()
}
treeString = fmt.Sprintf("File: %s", rootPath)
} else {
// Generate the tree representation for directory
treeString, err = generateTreeString(rootPath, allExcludePatterns)
if err != nil {
return "", nil, fmt.Errorf("failed to generate directory tree: %w", err)
}

return nil
})
// Process files in directory
err = filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(rootPath, path)
if err != nil {
return err
}

if shouldIncludeFile(relPath, includePatterns, allExcludePatterns, gitignore, includePriority) && !info.IsDir() {
wg.Add(1)
go func(path, relPath string, info os.FileInfo) {
defer wg.Done()
processFile(path, relPath, rootPath, lineNumber, relativePaths, noCodeblock, &mu, &files)
}(path, relPath, info)
}

return nil
})
}

wg.Wait()

return treeString, files, err
if err != nil {
return "", nil, err
}

return treeString, files, nil
}

func shouldIncludeFile(path string, includePatterns, excludePatterns []string, gitignore *ignore.GitIgnore, includePriority bool) bool {
Expand Down
Loading

0 comments on commit 52e4676

Please sign in to comment.