# Contributing to PTerm

> This document explains how to participate in the development of PTerm.\
If your goal is to report a bug instead of programming PTerm, you can do so [here](https://github.com/pterm/pterm/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).

## Best practise

We enforce some best practises, especially made for PTerm, to provide a clean and consistent user experience.

### Styles

Styles should always be consumed as pointers. That way, the user can change the style of printers globally.

## Creating a new printer

> In this chapter we will show you how to create a new printer.

### `TextPrinter` Template
```go
package pterm

type TemplatePrinter struct{
	// TODO: Add printer settings here
}

// Sprint formats using the default formats for its operands and returns the resulting string.
// Spaces are added between operands when neither is a string.
func (p TemplatePrinter) Sprint(a ...any) string {
	panic("write printer code here")
}

// Sprintln formats using the default formats for its operands and returns the resulting string.
// Spaces are always added between operands and a newline is appended.
func (p TemplatePrinter) Sprintln(a ...any) string {
	return Sprintln(p.Sprint(a...))
}

// Sprintf formats according to a format specifier and returns the resulting string.
func (p TemplatePrinter) Sprintf(format string, a ...any) string {
	return p.Sprint(Sprintf(format, a...))
}

// Print formats using the default formats for its operands and writes to standard output.
// Spaces are added between operands when neither is a string.
// It returns the number of bytes written and any write error encountered.
func (p TemplatePrinter) Print(a ...any) *TextPrinter {
	Print(p.Sprint(a...))
	tp := TextPrinter(p)
	return &tp
}

// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func (p TemplatePrinter) Println(a ...any) *TextPrinter {
	Println(p.Sprint(a...))
    tp := TextPrinter(p)
    return &tp
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func (p TemplatePrinter) Printf(format string, a ...any) *TextPrinter {
	Print(p.Sprintf(format, a...))
	tp := TextPrinter(p)
	return &tp
}
```

### `RenderablePrinter` Template

```go
package pterm

type TemplatePrinter struct{
	// TODO: Add printer settings here
}

// Srender renders the Template as a string.
func (p TemplatePrinter) Srender() (string, error) {
	var ret strings.Builder

    return ret.String(), nil
}

// Render prints the Template to the terminal.
func (p TemplatePrinter) Render() error {
	s, err := p.Srender()
    if err != nil {
        return err
    }
    Println(s)

    return nil
}
```

### `LivePrinter` Template

```go
// Start the TemplatePrinter.
package pterm
import "github.com/pterm/pterm"

type TemplatePrinter struct{

}


func (s TemplatePrinter) Start(text...any) (*TemplatePrinter, error) { // TODO: Replace Template with actual printer.
	// TODO: start logic
	return &s, nil
}

// Stop terminates the TemplatePrinter immediately.
// The TemplatePrinter will not resolve into anything.
func (s *TemplatePrinter) Stop() error {
	// TODO: stop logic
    return nil
}

// GenericStart runs Start, but returns a LivePrinter.
// This is used for the interface LivePrinter.
// You most likely want to use Start instead of this in your program.
func (s *TemplatePrinter) GenericStart() (*LivePrinter, error) {
	_, err := s.Start()
	lp := LivePrinter(s)
	return &lp, err
}

// GenericStop runs Stop, but returns a LivePrinter.
// This is used for the interface LivePrinter.
// You most likely want to use Stop instead of this in your program.
func (s *TemplatePrinter) GenericStop() (*LivePrinter, error) {
	err := s.Stop()
	lp := LivePrinter(s)
	return &lp, err
}
```

## Writing Tests

> Each method of PTerm must be tested.

### Required tests for every printer

#### Nil Check

> This ensures that a printer without set values will not produce errors.

```go
func TestTemplatePrinterNilPrint(t *testing.T) { // TODO: Replace "Template" with actual printer name.
	p := TemplatePrinter{} // TODO: Replace "Template" with actual printer name.
	p.Println("Hello, World!")
}
```

#### `WithXxx()` Methods

> Each method, which starts with `With` can be tested by checking if it actually creates a new printer and sets the value.

Example from `SectionPrinter`:

```go
func TestSectionPrinter_WithStyle(t *testing.T) {
	p := SectionPrinter{}
	s := NewStyle(FgRed, BgRed, Bold)
	p2 := p.WithStyle(s)

	assert.Equal(t, s, p2.Style)
	assert.Empty(t, p.Style)
}

func TestSectionPrinter_WithTopPadding(t *testing.T) {
	p := SectionPrinter{}
	p2 := p.WithTopPadding(1337)

	assert.Equal(t, 1337, p2.TopPadding)
	assert.Empty(t, p.TopPadding)
}
```

### `TextPrinter` Tests Template

```go
func TestTemplatePrinterPrintMethods(t *testing.T) { // TODO: Replace "Template" with actual printer name.
	p := DefaultTemplate // TODO: Replace "Template" with actual printer name.

	t.Run("Print", func(t *testing.T) {
		testPrintContains(t, func(w io.Writer, a any) {
			p.Print(a)
		})
	})

	t.Run("Printf", func(t *testing.T) {
		testPrintfContains(t, func(w io.Writer, format string, a any) {
			p.Printf(format, a)
		})
	})

	t.Run("Println", func(t *testing.T) {
		testPrintlnContains(t, func(w io.Writer, a any) {
			p.Println(a)
		})
	})

	t.Run("Sprint", func(t *testing.T) {
		testSprintContains(t, func(a any) string {
			return p.Sprint(a)
		})
	})

	t.Run("Sprintf", func(t *testing.T) {
		testSprintfContains(t, func(format string, a any) string {
			return p.Sprintf(format, a)
		})
	})

	t.Run("Sprintln", func(t *testing.T) {
		testSprintlnContains(t, func(a any) string {
			return p.Sprintln(a)
		})
	})
}
```
