all repos

clerk @ 1f6bd77

missing tooling for ledger/hledger
10 files changed, 175 insertions(+), 1 deletions(-)
lsp: write logs to file
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-08-03 13:20:24 +0300
Authored at: 2026-08-03 12:59:18 +0300
Change ID: wvlomxxpyznwuyqtrxskvqmzulnmvzms
Parent: 1c9c428
M internal/lsp/lsp.go
···
        5
        5
         	"io"

      
        6
        6
         	"log/slog"

      
        7
        7
         	"os"

      
        
        8
        +	"path/filepath"

      
        8
        9
         

      
        9
        10
         	"go.lsp.dev/jsonrpc2"

      
        10
        11
         	"go.lsp.dev/protocol"

      
        11
        12
         	"go.lsp.dev/uri"

      
        12
        13
         

      
        13
        14
         	"olexsmir.xyz/clerk/internal/linter"

      
        
        15
        +	"olexsmir.xyz/clerk/internal/xdg"

      
        14
        16
         	"olexsmir.xyz/clerk/journal"

      
        15
        17
         	"olexsmir.xyz/clerk/journal/printer"

      
        16
        18
         )

      ···
        18
        20
         type Server struct{ server *server }

      
        19
        21
         

      
        20
        22
         func NewServer(version string) Server {

      
        
        23
        +	var logger *slog.Logger

      
        
        24
        +	logFile, err := openLogFile()

      
        
        25
        +	if err == nil {

      
        
        26
        +		logger = slog.New(slog.NewTextHandler(logFile, nil))

      
        
        27
        +	} else {

      
        
        28
        +		logger = slog.New(slog.NewTextHandler(os.Stderr, nil))

      
        
        29
        +	}

      
        
        30
        +

      
        21
        31
         	return Server{

      
        22
        32
         		&server{

      
        23
        33
         			name:    "clerk",

      ···
        30
        40
         			loader:  journal.NewLoader(),

      
        31
        41
         			printer: printer.DefaultConfig,

      
        32
        42
         

      
        33
        
        -			log: slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{})),

      
        
        43
        +			log: logger,

      
        34
        44
         		},

      
        35
        45
         	}

      
        
        46
        +}

      
        
        47
        +

      
        
        48
        +func openLogFile() (*os.File, error) {

      
        
        49
        +	dir, err := xdg.StateDir()

      
        
        50
        +	if err != nil {

      
        
        51
        +		return nil, err

      
        
        52
        +	}

      
        
        53
        +	dir = filepath.Join(dir, "clerk")

      
        
        54
        +	if err := os.MkdirAll(dir, 0o700); err != nil {

      
        
        55
        +		return nil, err

      
        
        56
        +	}

      
        
        57
        +	return os.OpenFile(filepath.Join(dir, "lsp.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)

      
        36
        58
         }

      
        37
        59
         

      
        38
        60
         func (s *Server) Run(ctx context.Context, stdin io.ReadCloser, stdout io.WriteCloser) error {

      
A internal/xdg/state.go
···
        
        1
        +package xdg

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"os"

      
        
        5
        +	"path/filepath"

      
        
        6
        +)

      
        
        7
        +

      
        
        8
        +// StateDir returns the base directory for user-specific state data,

      
        
        9
        +// per the XDG Base Directory Specification: $XDG_STATE_HOME if it is set to

      
        
        10
        +// an absolute path, otherwise the platform-specific default.

      
        
        11
        +// Vendored from github.com/adrg/xdg (MIT), StateHome.

      
        
        12
        +func StateDir() (string, error) {

      
        
        13
        +	if dir := os.Getenv("XDG_STATE_HOME"); dir != "" && filepath.IsAbs(dir) {

      
        
        14
        +		return dir, nil

      
        
        15
        +	}

      
        
        16
        +	home, err := os.UserHomeDir()

      
        
        17
        +	if err != nil {

      
        
        18
        +		return "", err

      
        
        19
        +	}

      
        
        20
        +	return defaultStateDir(home), nil

      
        
        21
        +}

      
A internal/xdg/state_darwin.go
···
        
        1
        +//go:build darwin

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import "path/filepath"

      
        
        6
        +

      
        
        7
        +func defaultStateDir(home string) string {

      
        
        8
        +	return filepath.Join(home, "Library", "Application Support")

      
        
        9
        +}

      
A internal/xdg/state_darwin_test.go
···
        
        1
        +//go:build darwin

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import (

      
        
        6
        +	"path/filepath"

      
        
        7
        +	"testing"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +func TestStateDirDefault(t *testing.T) {

      
        
        11
        +	t.Setenv("XDG_STATE_HOME", "")

      
        
        12
        +	t.Setenv("HOME", "/Users/test")

      
        
        13
        +

      
        
        14
        +	want := filepath.Join("/Users/test", "Library", "Application Support")

      
        
        15
        +	got, err := StateDir()

      
        
        16
        +	if err != nil {

      
        
        17
        +		t.Fatal(err)

      
        
        18
        +	}

      
        
        19
        +	if got != want {

      
        
        20
        +		t.Fatalf("StateDir() = %q, want %q", got, want)

      
        
        21
        +	}

      
        
        22
        +}

      
A internal/xdg/state_plan9.go
···
        
        1
        +//go:build plan9

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import "path/filepath"

      
        
        6
        +

      
        
        7
        +func defaultStateDir(home string) string {

      
        
        8
        +	return filepath.Join(home, "lib", "state")

      
        
        9
        +}

      
A internal/xdg/state_plan9_test.go
···
        
        1
        +//go:build plan9

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import (

      
        
        6
        +	"path/filepath"

      
        
        7
        +	"testing"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +func TestStateDirDefault(t *testing.T) {

      
        
        11
        +	t.Setenv("XDG_STATE_HOME", "")

      
        
        12
        +	t.Setenv("home", "/home/test")

      
        
        13
        +

      
        
        14
        +	want := filepath.Join("/home/test", "lib", "state")

      
        
        15
        +	got, err := StateDir()

      
        
        16
        +	if err != nil {

      
        
        17
        +		t.Fatal(err)

      
        
        18
        +	}

      
        
        19
        +	if got != want {

      
        
        20
        +		t.Fatalf("StateDir() = %q, want %q", got, want)

      
        
        21
        +	}

      
        
        22
        +}

      
A internal/xdg/state_unix.go
···
        
        1
        +//go:build aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import "path/filepath"

      
        
        6
        +

      
        
        7
        +func defaultStateDir(home string) string {

      
        
        8
        +	return filepath.Join(home, ".local", "state")

      
        
        9
        +}

      
A internal/xdg/state_unix_test.go
···
        
        1
        +//go:build aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import (

      
        
        6
        +	"path/filepath"

      
        
        7
        +	"testing"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +func TestStateDirDefault(t *testing.T) {

      
        
        11
        +	t.Setenv("XDG_STATE_HOME", "")

      
        
        12
        +	t.Setenv("HOME", "/home/test")

      
        
        13
        +

      
        
        14
        +	want := filepath.Join("/home/test", ".local", "state")

      
        
        15
        +	got, err := StateDir()

      
        
        16
        +	if err != nil {

      
        
        17
        +		t.Fatal(err)

      
        
        18
        +	}

      
        
        19
        +	if got != want {

      
        
        20
        +		t.Fatalf("StateDir() = %q, want %q", got, want)

      
        
        21
        +	}

      
        
        22
        +}

      
A internal/xdg/state_windows.go
···
        
        1
        +//go:build windows

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import (

      
        
        6
        +	"os"

      
        
        7
        +	"path/filepath"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +func defaultStateDir(home string) string {

      
        
        11
        +	if dir := os.Getenv("LOCALAPPDATA"); dir != "" {

      
        
        12
        +		return dir

      
        
        13
        +	}

      
        
        14
        +	return filepath.Join(home, "AppData", "Local")

      
        
        15
        +}

      
A internal/xdg/state_windows_test.go
···
        
        1
        +//go:build windows

      
        
        2
        +

      
        
        3
        +package xdg

      
        
        4
        +

      
        
        5
        +import (

      
        
        6
        +	"path/filepath"

      
        
        7
        +	"testing"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +func TestStateDirDefault(t *testing.T) {

      
        
        11
        +	t.Setenv("XDG_STATE_HOME", "")

      
        
        12
        +	t.Setenv("LOCALAPPDATA", "")

      
        
        13
        +	t.Setenv("USERPROFILE", `C:\Users\test`)

      
        
        14
        +

      
        
        15
        +	want := filepath.Join(`C:\Users\test`, "AppData", "Local")

      
        
        16
        +	got, err := StateDir()

      
        
        17
        +	if err != nil {

      
        
        18
        +		t.Fatal(err)

      
        
        19
        +	}

      
        
        20
        +	if got != want {

      
        
        21
        +		t.Fatalf("StateDir() = %q, want %q", got, want)

      
        
        22
        +	}

      
        
        23
        +}