added proton drive integration

This commit is contained in:
Bartal Laearsson
2026-08-05 10:28:43 +01:00
parent 4992bcc7f7
commit 54f1c2e355
3 changed files with 169 additions and 31 deletions
+46 -8
View File
@@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"dirmd/internal/config"
"dirmd/internal/drive"
"dirmd/internal/renderer"
"dirmd/internal/walker"
)
@@ -30,7 +31,13 @@ Examples:
dirmd -a -i README.md -o existing.md
# Append another directory
dirmd -a -o existing.md ~/repos/anotherproject`,
dirmd -a -o existing.md ~/repos/anotherproject
# Generate and upload to Proton Drive (default remote path /my-files/md/<filename>)
dirmd -o output.md ~/repos/myproject --proton-drive
# Generate and upload to a specific remote path
dirmd -o output.md ~/repos/myproject --proton-drive --drive-path /my-files/docs/api.md`,
RunE: run,
}
@@ -41,6 +48,8 @@ Examples:
maxDepth int
ignorePatterns []string
extensions []string
protonDrive bool
drivePath string
)
func Execute() {
@@ -57,18 +66,26 @@ func init() {
rootCmd.Flags().IntVar(&maxDepth, "max-depth", config.DefaultMaxDepth, "max directory recursion depth")
rootCmd.Flags().StringArrayVar(&ignorePatterns, "ignore", nil, "additional ignore patterns (glob)")
rootCmd.Flags().StringArrayVar(&extensions, "extensions", nil, "additional file extensions to skip (repeatable)")
rootCmd.Flags().BoolVar(&protonDrive, "proton-drive", false, "upload output to Proton Drive after writing locally")
rootCmd.Flags().StringVar(&drivePath, "drive-path", "", "full remote path on Proton Drive (e.g. /my-files/md/report.md). Requires --proton-drive")
rootCmd.MarkPersistentFlagRequired("output")
}
func run(cmd *cobra.Command, args []string) error {
if drivePath != "" && !protonDrive {
return fmt.Errorf("--drive-path requires --proton-drive")
}
cfg := &config.Config{
OutputPath: outputPath,
AppendMode: appendMode,
MaxSize: maxSize,
MaxDepth: maxDepth,
Ignores: append(config.DefaultIgnores, ignorePatterns...),
Extensions: append(config.DefaultExts, extensions...),
OutputPath: outputPath,
AppendMode: appendMode,
MaxSize: maxSize,
MaxDepth: maxDepth,
Ignores: append(config.DefaultIgnores, ignorePatterns...),
Extensions: append(config.DefaultExts, extensions...),
ProtonDrive: protonDrive,
DrivePath: drivePath,
}
if singleFile != "" {
@@ -111,6 +128,27 @@ func run(cmd *cobra.Command, args []string) error {
}
fmt.Fprintf(os.Stderr, "wrote %d entries to %s\n", len(result.Entries), outputPath)
if protonDrive {
remotePath := drivePath
if remotePath == "" {
remotePath = "/my-files/md/" + filepath.Base(outputPath)
}
if err := drive.CheckAuth(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
fmt.Fprintln(os.Stderr, "local file was written successfully, but upload was skipped")
return err
}
if err := drive.Upload(outputPath, remotePath); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
fmt.Fprintln(os.Stderr, "local file was written successfully, but upload failed")
return err
}
fmt.Fprintf(os.Stderr, "uploaded %s to %s on Proton Drive\n", outputPath, remotePath)
}
return nil
}
+35 -23
View File
@@ -53,15 +53,17 @@ func (s *stringSlice) Set(v string) error {
}
type Config struct {
InputPath string
OutputPath string
AppendMode bool
SingleFile bool
MaxSize int64
MaxDepth int
Ignores []string
Extensions []string
AbsRoot string
InputPath string
OutputPath string
AppendMode bool
SingleFile bool
MaxSize int64
MaxDepth int
Ignores []string
Extensions []string
AbsRoot string
ProtonDrive bool
DrivePath string
}
func PrintUsage() {
@@ -74,14 +76,16 @@ func PrintUsage() {
func Parse() (*Config, error) {
var (
input string
output string
appendMd bool
maxSize int64
maxDepth int
ignores stringSlice
exts stringSlice
showVer bool
input string
output string
appendMd bool
maxSize int64
maxDepth int
ignores stringSlice
exts stringSlice
showVer bool
protonDrive bool
drivePath string
)
flag.StringVar(&input, "i", "", "single file input (append mode only)")
@@ -92,6 +96,8 @@ func Parse() (*Config, error) {
flag.Var(&ignores, "ignore", "additional ignore patterns (glob, repeatable)")
flag.Var(&exts, "extensions", "additional file extensions to skip (repeatable)")
flag.BoolVar(&showVer, "version", false, "print version and exit")
flag.BoolVar(&protonDrive, "proton-drive", false, "upload output to Proton Drive after writing locally")
flag.StringVar(&drivePath, "drive-path", "", "full remote path on Proton Drive (requires --proton-drive)")
flag.Usage = PrintUsage
flag.Parse()
@@ -105,13 +111,19 @@ func Parse() (*Config, error) {
return nil, fmt.Errorf("-o is required")
}
if drivePath != "" && !protonDrive {
return nil, fmt.Errorf("--drive-path requires --proton-drive")
}
cfg := &Config{
OutputPath: output,
AppendMode: appendMd,
MaxSize: maxSize,
MaxDepth: maxDepth,
Ignores: append(DefaultIgnores, ignores...),
Extensions: append(DefaultExts, exts...),
OutputPath: output,
AppendMode: appendMd,
MaxSize: maxSize,
MaxDepth: maxDepth,
Ignores: append(DefaultIgnores, ignores...),
Extensions: append(DefaultExts, exts...),
ProtonDrive: protonDrive,
DrivePath: drivePath,
}
if input != "" {
+88
View File
@@ -0,0 +1,88 @@
package drive
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
const binaryName = "proton-drive"
// Lookup returns the full path to the proton-drive binary in PATH.
func Lookup() (string, error) {
return exec.LookPath(binaryName)
}
// CheckAuth verifies that the user is authenticated by listing /my-files.
func CheckAuth() error {
binary, err := Lookup()
if err != nil {
return fmt.Errorf("proton-drive not found in PATH: %w", err)
}
cmd := exec.Command(binary, "filesystem", "list", "/my-files")
cmd.Stdout = nil
cmd.Stderr = nil
if err := cmd.Run(); err != nil {
return fmt.Errorf("not authenticated with proton-drive (run 'proton-drive auth login'): %w", err)
}
return nil
}
// Upload uploads localPath to remotePath on Proton Drive.
// remotePath is a full path including filename (e.g. /my-files/md/report.md).
// The CLI uploads to a parent directory and preserves the local filename,
// so if the remote filename differs from the local one, a temp copy is staged.
// Existing remote files are overwritten (--conflict-strategy replace).
func Upload(localPath, remotePath string) error {
binary, err := Lookup()
if err != nil {
return fmt.Errorf("proton-drive not found in PATH: %w", err)
}
remoteDir := filepath.Dir(remotePath)
remoteName := filepath.Base(remotePath)
localName := filepath.Base(localPath)
if remoteName == localName {
return runUpload(binary, localPath, remoteDir)
}
tmpDir, err := os.MkdirTemp("", "dirmd-upload-*")
if err != nil {
return fmt.Errorf("cannot create temp dir: %w", err)
}
defer os.RemoveAll(tmpDir)
tmpFile := filepath.Join(tmpDir, remoteName)
if err := copyFile(localPath, tmpFile); err != nil {
return fmt.Errorf("cannot stage temp file: %w", err)
}
return runUpload(binary, tmpFile, remoteDir)
}
func runUpload(binary, localPath, remoteDir string) error {
cmd := exec.Command(binary, "filesystem", "upload", localPath, remoteDir,
"--conflict-strategy", "replace")
var stderr strings.Builder
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("upload to proton-drive failed: %w: %s",
err, strings.TrimSpace(stderr.String()))
}
return nil
}
func copyFile(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, data, 0644)
}