File renovate-pretty-log-0.1.2.obscpio of Package renovate-pretty-log

07070100000000000081A400000000000000000000000168309D8F00000225000000000000000000000000000000000000002200000000renovate-pretty-log-0.1.2/LICENSECopyright 2025 Jamie Tanna

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
07070100000001000081A400000000000000000000000168309D8F0000082A000000000000000000000000000000000000002400000000renovate-pretty-log-0.1.2/README.md# `renovate-pretty-log`

Two utilities for exploring [Renovate](https://docs.renovatebot.com/) debug log files.

## `cmd/renovate-pretty-log-tui`

The `renovate-pretty-log-tui` command provides a Terminal User Interface (TUI) for interacting with a Renovate debug log export.

This provides a more rich means to explore the log lines, allowing  scrolling down the list of log lines, and can expand a given log line, if there are any extra pieces of context (denoted by a `+`):

[![asciicast](https://asciinema.org/a/Taz0vUWhmszaDtbWEuiJgICRi.svg)](https://asciinema.org/a/Taz0vUWhmszaDtbWEuiJgICRi)

Usage:

```sh
# to read the log file, and by default only see INFO level logs (and above)
renovate-pretty-log-tui -path /path/to/debug.log

# to read the log file, and see INFO level logs (and above)
renovate-pretty-log-tui -path /path/to/debug.log -level info

# to read the log file, and see DEBUG level logs (and above)
renovate-pretty-log-tui -path /path/to/debug.log -level debug
```

## `cmd/renovate-pretty-log`

The `renovate-pretty-log` provides a Terminal User Interface (TUI) for interacting with a Renovate debug log export.

This provides a more rich means to explore the log lines, allowing  scrolling down the list of log lines, and can expand a given log line, if there are any extra pieces of context (denoted by a `+`):

[![asciicast demo](https://asciinema.org/a/lc0dXTIyk7g8PKPrMRRAkX0ZD.svg)](https://asciinema.org/a/lc0dXTIyk7g8PKPrMRRAkX0ZD)

Usage:

```sh
# to read the log file, and by default only see INFO level logs (and above)
renovate-pretty-log -path /path/to/debug.log

# to read the log file, and see INFO level logs (and above)
renovate-pretty-log -path /path/to/debug.log -level info

# to read the log file, and see DEBUG level logs (and above)
renovate-pretty-log -path /path/to/debug.log -level debug
```

## License

This project is licensed under the Apache-2.0 license.

However, note that this includes output from the following Large Language Models (LLMs), via GitHub Copilot:

- claude:3.7-sonnet
- claude:3.7-sonnet-thinking
- gpt:4o
- gpt:4.1
07070100000002000041ED00000000000000000000000268309D8F00000000000000000000000000000000000000000000001E00000000renovate-pretty-log-0.1.2/cmd07070100000003000041ED00000000000000000000000268309D8F00000000000000000000000000000000000000000000003200000000renovate-pretty-log-0.1.2/cmd/renovate-pretty-log07070100000004000041ED00000000000000000000000268309D8F00000000000000000000000000000000000000000000003600000000renovate-pretty-log-0.1.2/cmd/renovate-pretty-log-tui07070100000005000081A400000000000000000000000168309D8F0000295F000000000000000000000000000000000000003E00000000renovate-pretty-log-0.1.2/cmd/renovate-pretty-log-tui/main.gopackage main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"strings"

	tea "github.com/charmbracelet/bubbletea"
	charmlog "github.com/charmbracelet/log"
	"gitlab.com/tanna.dev/renovate-pretty-log/internal/log"
	"gitlab.com/tanna.dev/renovate-pretty-log/internal/style"
)

// parentViewportHeight indicates how many lines of logs will be shown on a given "page" of logs
const parentViewportHeight = 30

// extrasViewportHeight indicates how many lines of "extra" metadata will be shown under a given log, if expanded
const extrasViewportHeight = 20

type model struct {
	// logs contains the log lines themselves. They must be pre-filtered
	logs   []log.Line
	cursor int
	// parentViewportStart is the index of the first visible log
	parentViewportStart int
	// expanded tracks which indexes are currently expanded
	expanded map[int]bool
	// extrasExpandedViewport allows expanding a given log.Line's Extras, to show any additional context the log line has
	extrasExpandedViewport extrasExpandedViewport
}

func newModel(lines []log.Line) model {
	return model{
		logs:                lines,
		cursor:              0,
		parentViewportStart: 0,
		expanded:            make(map[int]bool),
	}
}

func (m model) Init() tea.Cmd {
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case
			"up",
			"k":
			m.MoveUp()
		case
			"down",
			"j":
			m.MoveDown()
		case
			"ctrl+u",
			"pgup":
			m.cursor -= parentViewportHeight
			if m.cursor < 0 {
				m.cursor = 0
			}
			m.parentViewportStart -= parentViewportHeight
			if m.parentViewportStart < 0 {
				m.parentViewportStart = 0
			}

		case
			"ctrl+d",
			"pgdown":
			m.cursor += parentViewportHeight

			if m.cursor >= len(m.logs)-1 {
				m.cursor = len(m.logs) - 1
			}

			m.parentViewportStart += parentViewportHeight
			if m.parentViewportStart >= len(m.logs)-1 {
				m.parentViewportStart = len(m.logs) - 1
			}
		case "enter":
			m.expanded[m.cursor] = !m.expanded[m.cursor]

			log := m.logs[m.cursor]
			extras := strings.Split(style.RenderLineExtras(log), "\n")
			m.extrasExpandedViewport.cursor = 0
			m.extrasExpandedViewport.length = len(extras)
		case "q", "esc", "ctrl+c":
			return m, tea.Quit
		}
	}

	return m, nil
}

func (m model) View() string {
	var b strings.Builder

	end := min(m.parentViewportStart+parentViewportHeight, len(m.logs))

	for i := m.parentViewportStart; i < end; i++ {
		line := m.logs[i]

		cursor := " "
		if i == m.cursor {
			cursor = ">"
		}

		hasExtras := len(line.Extras) > 0
		hasExtrasIndicator := " "
		if hasExtras {
			hasExtrasIndicator = style.LogLineHasExtrasStyle.Render("+")
			if m.expanded[i] {
				hasExtrasIndicator = style.LogLineHasExtrasStyle.Render("-")
			}
		}

		renderedLine := style.RenderLine(line, false)

		fmt.Fprintf(&b, "%s %s %s", cursor, hasExtrasIndicator, renderedLine)
		if m.expanded[i] && hasExtras {
			extras := strings.Split(style.RenderLineExtras(line), "\n")

			tooLongPrefix := ""
			tooLongSuffix := ""

			start := 0
			end := extrasViewportHeight
			if end > len(extras) {
				end = len(extras)
			}

			if len(extras) > extrasViewportHeight {
				tooLongSuffix = "\n" + style.LogLineExtraIndent + "..."
			}

			if i == m.cursor {
				if m.extrasExpandedViewport.AtTop() {
					tooLongPrefix = ""
				} else {
					tooLongPrefix = fmt.Sprintf("%s... %d/%d\n", style.LogLineExtraIndent, m.extrasExpandedViewport.cursor, m.extrasExpandedViewport.length)
				}

				if m.extrasExpandedViewport.AtBottom() {
					tooLongSuffix = ""
				} else {
					tooLongSuffix = fmt.Sprintf("\n%s... %d/%d", style.LogLineExtraIndent, m.extrasExpandedViewport.cursor+extrasViewportHeight, m.extrasExpandedViewport.length)
				}

				start = m.extrasExpandedViewport.cursor
				end = m.extrasExpandedViewport.cursor + extrasViewportHeight
				if end > len(extras) {
					end = len(extras)
				}
			}

			fmt.Fprintf(&b, "\n%s%s%s",
				tooLongPrefix,
				style.LogLineExtraValueStyle.Render(strings.Join(extras[start:end], "\n")),
				tooLongSuffix)
		}
		fmt.Fprintln(&b)
	}

	if len(m.logs) > end {
		fmt.Fprintf(&b, "\nViewing %d/%d logs\n", m.cursor, len(m.logs)-end)
	}

	// TODO: migrate this to a help widget
	currentLog := m.logs[m.cursor]
	if len(currentLog.Extras) > 0 {
		if m.expanded[m.cursor] {
			fmt.Fprintf(&b, "\n[Use arrow keys to navigate, %s, q to quit]", style.LogLineHasExtrasStyle.Render("Enter to collapse"))
		} else {
			fmt.Fprintf(&b, "\n[Use arrow keys to navigate, %s, q to quit]", style.LogLineHasExtrasStyle.Render("Enter to expand"))
		}
	} else {
		fmt.Fprintf(&b, "\n[Use arrow keys to navigate, %s, q to quit]", style.LogLineTimestampStyle.Render("Enter to expand"))
	}

	return b.String()
}

func (m *model) MoveUp() {
	// if we're currently at an expanded log line ...
	if m.expanded[m.cursor] {
		// and we're already at the top
		if m.extrasExpandedViewport.AtTop() {
			// then scroll up as normal, out of the expanded log line
			m.cursor--
			if m.cursor < 0 {
				m.cursor = 0
			}

			// if we've gone out of view (as we're now before the previous start point) scroll us up
			if m.cursor < m.parentViewportStart {
				m.parentViewportStart--
			}
			if m.parentViewportStart < 0 {
				m.parentViewportStart = 0
			}

			// we may now be on a log line that is expanded, so we need to makes sure our internal state is reset
			m.updateExtrasExpandedViewportStateAfterMoving()
		} else {
			// otherwise, we want to scroll our expanded log line up a line
			m.extrasExpandedViewport.MoveUp()
		}
	} else if m.cursor > 0 {
		// if we're on a regular line, then move us up
		m.cursor--
		if m.cursor < 0 {
			m.cursor = 0
		}

		// if we've gone out of view (as we're now before the previous start point) scroll us up
		if m.cursor < m.parentViewportStart {
			m.parentViewportStart--
		}
		if m.parentViewportStart < 0 {
			m.parentViewportStart = 0
		}

		previous := m.cursor - 1
		if previous < 0 {
			previous = 0
		}

		// we may now be on a log line that is expanded, so we need to makes sure our internal state is reset
		m.updateExtrasExpandedViewportStateAfterMoving()
	}
}

func (m *model) MoveDown() {
	// if we're currently at an expanded log line ...
	if m.expanded[m.cursor] {
		// and we're already at the bottom
		if m.extrasExpandedViewport.AtBottom() {
			// then scroll down as normal, out of the expanded log line
			m.cursor++
			if m.cursor > len(m.logs)-1 {
				m.cursor = len(m.logs) - 1
			}

			// if we've gone out of view (as we're now before the previous start point) scroll us down
			if m.cursor > m.parentViewportStart {
				m.parentViewportStart++
			}
			if m.parentViewportStart > len(m.logs)-1 {
				m.parentViewportStart = len(m.logs) - 1
			}

			// we may now be on a log line that is expanded, so we need to makes sure our internal state is reset
			m.updateExtrasExpandedViewportStateAfterMoving()
		} else {
			// otherwise, we want to scroll our expanded log line down a line
			m.extrasExpandedViewport.MoveDown()
		}
	} else if m.cursor < len(m.logs)-1 {
		// if we're on a regular line, then move us down
		m.cursor++
		if m.cursor > len(m.logs)-1 {
			m.cursor = len(m.logs) - 1
		}

		// if we've gone out of view (as we're now before the previous start point) scroll us down
		if m.cursor > m.parentViewportStart {
			m.parentViewportStart++
		}
		if m.parentViewportStart > len(m.logs)-1 {
			m.parentViewportStart = len(m.logs) - 1
		}

		// we may now be on a log line that is expanded, so we need to makes sure our internal state is reset
		m.updateExtrasExpandedViewportStateAfterMoving()
	}
}

func (m *model) updateExtrasExpandedViewportStateAfterMoving() {
	log := m.logs[m.cursor]
	extras := strings.Split(style.RenderLineExtras(log), "\n")
	m.extrasExpandedViewport.cursor = 0
	m.extrasExpandedViewport.length = len(extras)
}

// extrasExpandedViewport allows expanding a given log line's "extra" log metadata, in a scrollable fashion.
type extrasExpandedViewport struct {
	cursor int
	length int
}

func (e extrasExpandedViewport) AtTop() bool {
	return e.cursor == 0
}

func (e extrasExpandedViewport) AtBottom() bool {
	if e.cursor == (e.length - 1) {
		return true
	}

	isShowingLastPage := (e.length)-extrasViewportHeight <= e.cursor

	return isShowingLastPage
}

func (e *extrasExpandedViewport) MoveUp() {
	e.cursor--
	if e.cursor <= 0 {
		e.cursor = 0
	}
}

func (e *extrasExpandedViewport) MoveDown() {
	e.cursor++
	if e.cursor > e.length {
		e.cursor = e.length
	}
}

func parseLevelFlag(level *string) (log.Level, error) {
	if level == nil {
		return log.LevelInfo, nil
	}

	val := strings.ToLower(*level)

	switch val {
	case "trace":
		return log.LevelTrace, nil
	case "debug":
		return log.LevelDebug, nil
	case "info":
		return log.LevelInfo, nil
	case "warn":
		return log.LevelWarn, nil
	case "error":
		return log.LevelError, nil
	case "fatal":
		return log.LevelFatal, nil
	}

	return -1, fmt.Errorf("unknown log level: %v", *level)
}

func main() {
	cliLogger := charmlog.New(os.Stderr)

	levelFlag := flag.String("level", "info", "Set the log level (trace, debug, info, warn, error, fatal)")
	pathFlag := flag.String("path", "", "The path to the debug log file (`RENOVATE_DEBUG_LOG_FILE`)")
	flag.Parse()

	if pathFlag == nil {
		cliLogger.Fatal("No `-path` flag was provided")
		os.Exit(1)
	}

	if *pathFlag == "" {
		cliLogger.Fatal("No `-path` flag was provided")
		os.Exit(1)
	}

	f, err := os.Open(*pathFlag)
	if err != nil {
		cliLogger.Fatal(fmt.Sprintf("Error opening file: %v", err), "err", err)
		os.Exit(1)
	}

	defer f.Close()

	allowedLogLevel, err := parseLevelFlag(levelFlag)
	if err != nil {
		cliLogger.Fatal(fmt.Sprintf("The `-level` flag was invalid: %v", err), "err", err)
		os.Exit(1)
	}

	var lines []log.Line
	// TODO via https://gitlab.com/jamietanna/dotfiles-arch/-/blob/67d79966/go/home/go/src/jvt.me/dotfiles/jjj/main.go#L24-26 we may want to allow handling /very/ long log lines
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		var line log.Line
		data := scanner.Bytes()
		err := json.Unmarshal(data, &line)
		if err != nil {
			cliLogger.Fatal(fmt.Sprintf("Failed to parse a log line as JSON: %v", err), "err", err)
			continue
		}

		if line.Level >= allowedLogLevel {
			lines = append(lines, line)
		}
	}

	if len(lines) == 0 {
		fmt.Printf("The file you provided did not contain any %s level logs\n", allowedLogLevel.String())
		os.Exit(0)
	}

	p := tea.NewProgram(newModel(lines))
	if _, err := p.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "Error starting app: %v\n", err)
		os.Exit(1)
	}
}
07070100000006000081A400000000000000000000000168309D8F000007A2000000000000000000000000000000000000003A00000000renovate-pretty-log-0.1.2/cmd/renovate-pretty-log/main.gopackage main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"strings"

	charmlog "github.com/charmbracelet/log"

	log "gitlab.com/tanna.dev/renovate-pretty-log/internal/log"
	"gitlab.com/tanna.dev/renovate-pretty-log/internal/style"
)

func parseLevelFlag(level *string) (log.Level, error) {
	if level == nil {
		return log.LevelInfo, nil
	}

	val := strings.ToLower(*level)

	switch val {
	case "trace":
		return log.LevelTrace, nil
	case "debug":
		return log.LevelDebug, nil
	case "info":
		return log.LevelInfo, nil
	case "warn":
		return log.LevelWarn, nil
	case "error":
		return log.LevelError, nil
	case "fatal":
		return log.LevelFatal, nil
	}

	return -1, fmt.Errorf("unknown log level: %v", *level)
}

func main() {
	cliLogger := charmlog.New(os.Stderr)

	levelFlag := flag.String("level", "info", "Set the log level (trace, debug, info, warn, error, fatal)")
	pathFlag := flag.String("path", "", "The path to the debug log file (`RENOVATE_DEBUG_LOG_FILE`)")
	flag.Parse()

	if pathFlag == nil {
		cliLogger.Fatal("No `-path` flag was provided")
		os.Exit(1)
	}

	f, err := os.Open(*pathFlag)
	if err != nil {
		cliLogger.Fatal(fmt.Sprintf("Error opening file: %v", err), "err", err)
		os.Exit(1)
	}

	allowedLogLevel, err := parseLevelFlag(levelFlag)
	if err != nil {
		cliLogger.Fatal(fmt.Sprintf("The `-level` flag was invalid: %v", err), "err", err)
		os.Exit(1)
	}

	defer f.Close()
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		var line log.Line
		data := scanner.Bytes()
		err := json.Unmarshal(data, &line)
		if err != nil {
			cliLogger.Fatal(fmt.Sprintf("Failed to parse a log line as JSON: %v", err), "err", err)
			continue
		}

		if line.Level < allowedLogLevel {
			cliLogger.Debug("Skipping log line from a log level we've not been requested to show", "level", line.Level.String(), "msg", line.Message)
			continue
		}

		// TODO check level is enabled flag
		fmt.Println(style.RenderLine(line, true))
	}
}
07070100000007000081A400000000000000000000000168309D8F00000538000000000000000000000000000000000000002100000000renovate-pretty-log-0.1.2/go.modmodule gitlab.com/tanna.dev/renovate-pretty-log

go 1.24

require (
	github.com/charmbracelet/bubbletea v1.3.5
	github.com/charmbracelet/lipgloss v1.1.0
	github.com/charmbracelet/log v0.4.2
)

require (
	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
	github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
	github.com/charmbracelet/x/ansi v0.8.0 // indirect
	github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
	github.com/charmbracelet/x/term v0.2.1 // indirect
	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
	github.com/go-logfmt/logfmt v0.6.0 // indirect
	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mattn/go-localereader v0.0.1 // indirect
	github.com/mattn/go-runewidth v0.0.16 // indirect
	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
	github.com/muesli/cancelreader v0.2.2 // indirect
	github.com/muesli/termenv v0.16.0 // indirect
	github.com/rivo/uniseg v0.4.7 // indirect
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
	golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
	golang.org/x/sync v0.13.0 // indirect
	golang.org/x/sys v0.32.0 // indirect
	golang.org/x/text v0.3.8 // indirect
)
07070100000008000081A400000000000000000000000168309D8F000014AE000000000000000000000000000000000000002100000000renovate-pretty-log-0.1.2/go.sumgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
07070100000009000041ED00000000000000000000000268309D8F00000000000000000000000000000000000000000000002300000000renovate-pretty-log-0.1.2/internal0707010000000A000041ED00000000000000000000000268309D8F00000000000000000000000000000000000000000000002700000000renovate-pretty-log-0.1.2/internal/log0707010000000B000081A400000000000000000000000168309D8F00000AA9000000000000000000000000000000000000002F00000000renovate-pretty-log-0.1.2/internal/log/main.gopackage log

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/charmbracelet/log"
)

type Level int

const (
	LevelTrace Level = 10
	LevelDebug Level = 20
	LevelInfo  Level = 30
	LevelWarn  Level = 40
	LevelError Level = 50
	LevelFatal Level = 60
)

func (l Level) AsLevel() log.Level {
	// TODO remove

	switch l {
	case LevelTrace:
		return log.DebugLevel // there's no TRACE
	case LevelDebug:
		return log.DebugLevel
	case LevelInfo:
		return log.InfoLevel
	case LevelWarn:
		return log.WarnLevel
	case LevelError:
		return log.ErrorLevel
	case LevelFatal:
		return log.FatalLevel
	default:
		return log.DebugLevel // default to DEBUG
	}
}

func (l Level) String() string {
	switch l {
	case LevelTrace:
		return "TRACE"
	case LevelDebug:
		return "DEBUG"
	case LevelInfo:
		return "INFO"
	case LevelWarn:
		return "WARN"
	case LevelError:
		return "ERROR"
	case LevelFatal:
		return "FATAL"
	default:
		return fmt.Sprintf("Unknown log Level (%d)", l)
	}
}

type Line struct {
	Name       string    `json:"name"`
	Hostname   string    `json:"hostname"`
	Pid        int       `json:"pid"`
	Level      Level     `json:"level"`
	LogContext string    `json:"logContext"`
	Repository string    `json:"repository"`
	Message    string    `json:"msg"`
	Time       time.Time `json:"time"`
	Version    int       `json:"v"`

	Extras map[string]any `json:"-"`
}

func (ll Line) Render() string {
	return ""
}

func (ll *Line) UnmarshalJSON(data []byte) error {
	var tempMap map[string]interface{}
	if err := json.Unmarshal(data, &tempMap); err != nil {
		return err
	}

	// Extract known fields and delete them from the map
	if name, ok := tempMap["name"].(string); ok {
		ll.Name = name
		delete(tempMap, "name")
	}
	if hostname, ok := tempMap["hostname"].(string); ok {
		ll.Hostname = hostname
		delete(tempMap, "hostname")
	}
	if pid, ok := tempMap["pid"].(float64); ok { // JSON numbers are float64
		ll.Pid = int(pid)
		delete(tempMap, "pid")
	}
	if level, ok := tempMap["level"].(float64); ok {
		ll.Level = Level(int(level))
		delete(tempMap, "level")
	}
	if logContext, ok := tempMap["logContext"].(string); ok {
		ll.LogContext = logContext
		delete(tempMap, "logContext")
	}
	if repository, ok := tempMap["repository"].(string); ok {
		ll.Repository = repository
		delete(tempMap, "repository")
	}
	if msg, ok := tempMap["msg"].(string); ok {
		ll.Message = msg
		delete(tempMap, "msg")
	}
	if timeStr, ok := tempMap["time"].(string); ok {
		parsedTime, err := time.Parse(time.RFC3339, timeStr)
		if err != nil {
			return err
		}
		ll.Time = parsedTime
		delete(tempMap, "time")
	}
	if version, ok := tempMap["v"].(float64); ok {
		ll.Version = int(version)
		delete(tempMap, "v")
	}

	ll.Extras = tempMap

	return nil
}
0707010000000C000041ED00000000000000000000000268309D8F00000000000000000000000000000000000000000000002900000000renovate-pretty-log-0.1.2/internal/style0707010000000D000081A400000000000000000000000168309D8F0000102A000000000000000000000000000000000000003100000000renovate-pretty-log-0.1.2/internal/style/main.gopackage style

import (
	"bytes"
	"encoding/json"
	"fmt"
	"regexp"
	"slices"
	"strings"
	"time"

	"github.com/charmbracelet/lipgloss"
	"gitlab.com/tanna.dev/renovate-pretty-log/internal/log"
)

// HACK for https://github.com/charmbracelet/lipgloss/issues/199
var reHackToRemoveInadvertentWhitespaceIntroducedByLipGloss = regexp.MustCompile(`([ \t]*[\n\r]|[ \t]*$|[\n\r]$)`)

// HACK for https://github.com/charmbracelet/lipgloss/issues/199
var hackInadvertentTrailerBytes = []byte{
	27, 91, 48, 109, 10, 27, 91, 51, 56, 59, 50, 59, 49, 56, 54, 59, 49, 54, 54, 59, 49, 50, 55, 109, 27, 91, 48, 109, 10,
}

var LogLineTraceLevelStyle = lipgloss.NewStyle().
	SetString("TRAC").
	Bold(true).
	MaxWidth(4).
	Foreground(lipgloss.Color("#2C78BF"))

var LogLineDebugLevelStyle = lipgloss.NewStyle().
	SetString("DEBU").
	Bold(true).
	MaxWidth(4).
	Foreground(lipgloss.Color("#2C78BF"))

var LogLineInfoLevelStyle = lipgloss.NewStyle().
	SetString("INFO").
	Bold(true).
	MaxWidth(4).
	Foreground(lipgloss.Color("#519F50"))

var LogLineWarnLevelStyle = lipgloss.NewStyle().
	SetString("WARN").
	Bold(true).
	MaxWidth(4).
	Foreground(lipgloss.Color("#FBB829"))

var LogLineErrorLevelStyle = lipgloss.NewStyle().
	SetString("ERRO").
	Bold(true).
	MaxWidth(4).
	Foreground(lipgloss.Color("#EF2F27"))

var LogLineFatalLevelStyle = lipgloss.NewStyle().
	SetString("FATA").
	Bold(true).
	MaxWidth(4).
	Foreground(lipgloss.Color("#F75341"))

var LogLineHasExtrasStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color("#ff5c8f"))

var LogLineExtraKeyStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color("#FCE8C3"))

var LogLineExtraValueStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color("#BAA67F"))

var LogLineTimestampStyle = lipgloss.NewStyle().
	Bold(true).
	MaxWidth(19).
	Foreground(lipgloss.Color("240"))

var LogLineMessageStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color("#FFFFFF"))

var LogLineExtraIndent = "     "

func LevelStyle(level log.Level) lipgloss.Style {
	switch level {
	case log.LevelTrace:
		return LogLineTraceLevelStyle
	case log.LevelDebug:
		return LogLineDebugLevelStyle
	case log.LevelInfo:
		return LogLineInfoLevelStyle
	case log.LevelWarn:
		return LogLineWarnLevelStyle
	case log.LevelError:
		return LogLineErrorLevelStyle
	case log.LevelFatal:
		return LogLineFatalLevelStyle
	default:
		panic("TODO")
	}
}

func TimestampStyle(timestamp *time.Time) lipgloss.Style {
	return LogLineTimestampStyle.
		SetString(timestamp.Format(time.RFC3339))
}

func MessageStyle(message string) lipgloss.Style {
	return LogLineMessageStyle.
		SetString(message)
}

func RenderLine(line log.Line, withExtras bool) string {
	var parts []string

	out := LevelStyle(line.Level).Render()
	parts = append(parts, out)

	out = TimestampStyle(&line.Time).Render()
	parts = append(parts, out)

	out = MessageStyle(line.Message).Render()
	parts = append(parts, out)

	if withExtras {
		extras := RenderLineExtras(line)
		if extras != "" {
			parts = append(parts, "\n"+extras)
		}
	}

	return strings.Join(parts, " ")
}

func RenderLineExtras(line log.Line) string {
	keys := make([]string, 0, len(line.Extras))
	for k := range line.Extras {
		keys = append(keys, k)
	}
	slices.Sort(keys)

	var buf bytes.Buffer
	jsonEncoder := json.NewEncoder(&buf)
	jsonEncoder.SetIndent(LogLineExtraIndent, "  ")
	/*
	   "go/google.golang.org/protobuf": {
	     "\u003c 1.33.0": "1.33.0"
	   }
	*/
	jsonEncoder.SetEscapeHTML(false)

	var extras []string
	for _, k := range keys {
		v := line.Extras[k]
		out := ""

		switch v := v.(type) {
		case bool,
			float32,
			float64,
			string:
			out = fmt.Sprintf("%#v", v)
		default:
			buf.Reset()
			err := jsonEncoder.Encode(v)
			if err == nil {
				out = buf.String()
			} else {
				out = fmt.Sprintf("%#v", v)
			}
		}

		out = LogLineExtraValueStyle.Render(out)
		out = reHackToRemoveInadvertentWhitespaceIntroducedByLipGloss.ReplaceAllString(out, "\n")
		out = strings.TrimSuffix(out, string(hackInadvertentTrailerBytes))

		extras = append(extras, LogLineExtraIndent+LogLineExtraKeyStyle.Render(k)+": "+out)
	}

	if len(extras) > 0 {
		return strings.Join(extras, "\n")
	}

	return ""
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!61 blocks
openSUSE Build Service is sponsored by