diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..206efb5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,31 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: "[bug-report]"
+labels: bug
+assignees: TLINDEN
+
+---
+
+**Describtion**
+
+
+
+**Steps To Reproduce**
+
+
+
+**Expected behavior**
+
+
+
+**Version information**
+
+
+
+**Additional informations**
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..ed09312
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Suggest a feature
+title: "[feature-request]"
+labels: feature-request
+assignees: TLINDEN
+
+---
+
+**Describtion**
+
+
+
+
+**Version information**
+
+
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..d9ceac6
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,36 @@
+name: build-and-test-rpn
+on: [push, pull_request]
+jobs:
+ build:
+ strategy:
+ matrix:
+ version: [1.21]
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ name: Build
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set up Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: ${{ matrix.version }}
+ id: go
+
+ - name: checkout
+ uses: actions/checkout@v3
+
+ - name: build
+ run: go build
+
+ - name: test
+ run: make test
+
+ golangci:
+ name: lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/setup-go@v3
+ with:
+ go-version: 1.21
+ - uses: actions/checkout@v3
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v3
diff --git a/.gitignore b/.gitignore
index ed13d1c..7f33a39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
releases
rpn
rpn.1
+coverage.out
diff --git a/Makefile b/Makefile
index 24131eb..ac90935 100644
--- a/Makefile
+++ b/Makefile
@@ -39,10 +39,6 @@ ifdef HAVE_POD
echo "var manpage = \`" >> $*.go
pod2text $*.pod >> $*.go
echo "\`" >> $*.go
-
- echo "var usage = \`" >> $*.go
- awk '/SYNOPS/{f=1;next} /DESCR/{f=0} f' $*.pod | sed 's/^ //' >> $*.go
- echo "\`" >> $*.go
endif
buildlocal:
@@ -59,7 +55,6 @@ clean:
test:
go test -v ./...
- bash t/test.sh
singletest:
@echo "Call like this: ''make singletest TEST=TestPrepareColumns MOD=lib"
@@ -77,3 +72,15 @@ buildall:
release: buildall
gh release create v$(VERSION) --generate-notes releases/*
+
+show-versions: buildlocal
+ @echo "### rpn version:"
+ @./rpn -v
+
+ @echo
+ @echo "### go module versions:"
+ @go list -m all
+
+ @echo
+ @echo "### go version used for building:"
+ @grep -m 1 go go.mod
diff --git a/README.md b/README.md
index eafd073..72149e1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+[![Actions](https://github.com/tlinden/rpnc/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/rpnc/actions)
+[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/rpnc/blob/master/LICENSE)
+[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/rpnc)](https://goreportcard.com/report/github.com/tlinden/rpnc)
+
## Reverse Polish Notation Calculator for the commandline
This is a small commandline calculator which takes its input in
diff --git a/calc.go b/calc.go
index be15743..c131a7d 100644
--- a/calc.go
+++ b/calc.go
@@ -20,7 +20,6 @@ package main
import (
"errors"
"fmt"
- "math"
"os"
"regexp"
"strconv"
@@ -376,50 +375,6 @@ func (c *Calc) Debug(msg string) {
}
}
-// do simple calculations
-func (c *Calc) simple(op byte) {
- c.stack.Backup()
-
- for c.stack.Len() > 1 {
- b := c.stack.Pop()
- a := c.stack.Pop()
- var x float64
-
- c.Debug(fmt.Sprintf("evaluating: %.2f %c %.2f", a, op, b))
-
- switch op {
- case '+':
- x = a + b
- case '-':
- x = a - b
- case 'x':
- fallthrough // alias for *
- case '*':
- x = a * b
- case '/':
- if b == 0 {
- fmt.Println("error: division by null!")
- return
- }
- x = a / b
- case '^':
- x = math.Pow(a, b)
- default:
- panic("invalid operator!")
- }
-
- c.stack.Push(x)
-
- c.History("%f %c %f = %f", a, op, b, x)
-
- if !c.batch {
- break
- }
- }
-
- c.Result()
-}
-
func (c *Calc) luafunc(funcname string) {
// called from calc loop
var x float64
diff --git a/calc_test.go b/calc_test.go
new file mode 100644
index 0000000..3ad864a
--- /dev/null
+++ b/calc_test.go
@@ -0,0 +1,141 @@
+/*
+Copyright © 2023 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package main
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestCalc(t *testing.T) {
+ calc := NewCalc()
+
+ var tests = []struct {
+ name string
+ cmd string
+ exp float64
+ batch bool
+ }{
+ {
+ name: "plus",
+ cmd: `15 15 +`,
+ exp: 30,
+ },
+ {
+ name: "power",
+ cmd: `4 2 ^`,
+ exp: 16,
+ },
+ {
+ name: "minus",
+ cmd: `100 50 -`,
+ exp: 50,
+ },
+ {
+ name: "multi",
+ cmd: `4 4 x`,
+ exp: 16,
+ },
+ {
+ name: "divide",
+ cmd: `10 2 /`,
+ exp: 5,
+ },
+ {
+ name: "percent",
+ cmd: `400 20 %`,
+ exp: 80,
+ },
+ {
+ name: "percent-minus",
+ cmd: `400 20 %-`,
+ exp: 320,
+ },
+ {
+ name: "percent-plus",
+ cmd: `400 20 %+`,
+ exp: 480,
+ },
+ {
+ name: "mod",
+ cmd: `9 2 mod`,
+ exp: 1,
+ },
+ {
+ name: "sqrt",
+ cmd: `16 sqrt`,
+ exp: 4,
+ },
+ {
+ name: "ceil",
+ cmd: `15.5 ceil`,
+ exp: 16,
+ },
+ {
+ name: "dim",
+ cmd: `6 4 dim`,
+ exp: 2,
+ },
+ {
+ name: "batch-sum",
+ cmd: `2 2 2 2 sum`,
+ exp: 8,
+ batch: true,
+ },
+ {
+ name: "batch-median",
+ cmd: `1 2 3 4 5 median`,
+ exp: 3,
+ batch: true,
+ },
+ {
+ name: "batch-mean",
+ cmd: `2 2 8 2 2 mean`,
+ exp: 3.2,
+ batch: true,
+ },
+ {
+ name: "batch-min",
+ cmd: `1 2 3 4 5 min`,
+ exp: 1,
+ batch: true,
+ },
+ {
+ name: "batch-max",
+ cmd: `1 2 3 4 5 max`,
+ exp: 5,
+ batch: true,
+ },
+ }
+
+ for _, tt := range tests {
+ testname := fmt.Sprintf("cmd-%s-expect-%.2f",
+ tt.name, tt.exp)
+
+ t.Run(testname, func(t *testing.T) {
+ calc.batch = tt.batch
+ calc.Eval(tt.cmd)
+ got := calc.Result()
+ calc.stack.Clear()
+ if got != tt.exp {
+ t.Errorf("calc failed:\n+++ got: %f\n--- want: %f",
+ got, tt.exp)
+ }
+ })
+ }
+}
diff --git a/rpn.go b/rpn.go
index 1441cdb..8890af5 100644
--- a/rpn.go
+++ b/rpn.go
@@ -222,17 +222,3 @@ AUTHORS
Thomas von Dein tom AT vondein DOT org
`
-var usage = `
-
-Usage: rpn [-bdvh] []
-
-Options:
- -b, --batchmode enable batch mode
- -d, --debug enable debug mode
- -v, --version show version
- -h, --help show help
-
-When is given, batch mode ist automatically enabled. Use
-this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
-
-`
diff --git a/stack_test.go b/stack_test.go
new file mode 100644
index 0000000..aa83e96
--- /dev/null
+++ b/stack_test.go
@@ -0,0 +1,190 @@
+/*
+Copyright © 2023 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package main
+
+import (
+ "testing"
+)
+
+func TestPush(t *testing.T) {
+ t.Run("push", func(t *testing.T) {
+ s := NewStack()
+ s.Push(5)
+
+ if s.linklist.Back().Value != 5.0 {
+ t.Errorf("push failed:\n+++ got: %f\n--- want: %f",
+ s.linklist.Back().Value, 5.0)
+ }
+ })
+}
+
+func TestPop(t *testing.T) {
+ t.Run("pop", func(t *testing.T) {
+ s := NewStack()
+ s.Push(5)
+ got := s.Pop()
+
+ if got != 5.0 {
+ t.Errorf("pop failed:\n+++ got: %f\n--- want: %f",
+ got, 5.0)
+ }
+
+ if s.Len() != 0 {
+ t.Errorf("stack not empty after pop()")
+ }
+ })
+}
+
+func TestPops(t *testing.T) {
+ t.Run("pops", func(t *testing.T) {
+ s := NewStack()
+ s.Push(5)
+ s.Push(5)
+ s.Push(5)
+ s.Pop()
+
+ if s.Len() != 2 {
+ t.Errorf("stack len not correct after pop:\n+++ got: %d\n--- want: %d",
+ s.Len(), 2)
+ }
+ })
+}
+
+func TestShift(t *testing.T) {
+ t.Run("shift", func(t *testing.T) {
+ s := NewStack()
+ s.Shift()
+
+ if s.Len() != 0 {
+ t.Errorf("stack not empty after shift()")
+ }
+ })
+}
+
+func TestClear(t *testing.T) {
+ t.Run("clear", func(t *testing.T) {
+ s := NewStack()
+ s.Push(5)
+ s.Push(5)
+ s.Push(5)
+ s.Clear()
+
+ if s.Len() != 0 {
+ t.Errorf("stack not empty after clear()")
+ }
+ })
+}
+
+func TestLast(t *testing.T) {
+ t.Run("last", func(t *testing.T) {
+ s := NewStack()
+ s.Push(5)
+ got := s.Last()
+
+ if len(got) != 1 {
+ t.Errorf("last failed:\n+++ got: %d elements\n--- want: %d elements",
+ len(got), 1)
+ }
+
+ if got[0] != 5.0 {
+ t.Errorf("last failed:\n+++ got: %f\n--- want: %f",
+ got, 5.0)
+ }
+
+ if s.Len() != 1 {
+ t.Errorf("stack modified after last()")
+ }
+ })
+}
+
+func TestAll(t *testing.T) {
+ t.Run("all", func(t *testing.T) {
+ s := NewStack()
+ list := []float64{2, 4, 6, 8}
+
+ for _, item := range list {
+ s.Push(item)
+ }
+
+ got := s.All()
+
+ if len(got) != len(list) {
+ t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",
+ len(got), len(list))
+ }
+
+ for i := 1; i < len(list); i++ {
+ if got[i] != list[i] {
+ t.Errorf("all failed (element %d):\n+++ got: %f\n--- want: %f",
+ i, got[i], list[i])
+ }
+ }
+
+ if s.Len() != len(list) {
+ t.Errorf("stack modified after last()")
+ }
+ })
+}
+
+func TestBackupRestore(t *testing.T) {
+ t.Run("shift", func(t *testing.T) {
+ s := NewStack()
+ s.Push(5)
+ s.Backup()
+ s.Clear()
+ s.Restore()
+
+ if s.Len() != 1 {
+ t.Errorf("stack not correctly restored()")
+ }
+
+ a := s.Pop()
+ if a != 5.0 {
+ t.Errorf("stack not identical to old revision:\n+++ got: %f\n--- want: %f",
+ a, 5.0)
+ }
+ })
+}
+
+func TestReverse(t *testing.T) {
+ t.Run("reverse", func(t *testing.T) {
+ s := NewStack()
+ list := []float64{2, 4, 6}
+ reverse := []float64{6, 4, 2}
+
+ for _, item := range list {
+ s.Push(item)
+ }
+
+ s.Reverse()
+
+ got := s.All()
+
+ if len(got) != len(list) {
+ t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",
+ len(got), len(list))
+ }
+
+ for i := 1; i < len(reverse); i++ {
+ if got[i] != reverse[i] {
+ t.Errorf("reverse failed (element %d):\n+++ got: %f\n--- want: %f",
+ i, got[i], list[i])
+ }
+ }
+ })
+}
diff --git a/util_test.go b/util_test.go
new file mode 100644
index 0000000..7c37735
--- /dev/null
+++ b/util_test.go
@@ -0,0 +1,32 @@
+/*
+Copyright © 2023 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package main
+
+import (
+ "testing"
+)
+
+func TestContains(t *testing.T) {
+ list := []string{"a", "b", "c"}
+
+ t.Run("contains", func(t *testing.T) {
+ if !contains(list, "a") {
+ t.Errorf("a in [a,b,c] not found")
+ }
+ })
+}