diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..767bd5c --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,65 @@ +name: Go +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.17 + id: go + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Go modules cache + id: cache-go-module + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Setup Go build cache + id: cache-go-build + uses: actions/cache@v3 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-gobuild-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-gobuild- + + - name: Download dependencies + if: steps.cache-go-module.outputs.cache-hit != 'true' + run: go mod download + + - name: Verify go modules up to date + run: | + set -ex + go mod tidy + [ -z "$(git diff -- go.{mod,sum})" ] # Check there are no changes! + + - name: Test + run: go test -race ./... + + # Run golangci-lint in same job as tests as the golangci-lint task can not + # resolve private dependencies. + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.50 + github-token: ${{ secrets.GITHUB_TOKEN }} + only-new-issues: false + skip-cache: false + skip-pkg-cache: true + skip-build-cache: true + args: --timeout=10m diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c1b180d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: go - -go: -- 1.7 -- tip diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 48a1e82..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# Release 0.0.1 (2016-06-13) - -This release marks the initial API layout, as it was when it was released to GitHub back in 2012. diff --git a/README.md b/README.md index 3f19c54..81708d3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/smyrman/units.svg?branch=master)](https://travis-ci.org/smyrman/units) +[![GitHub Actions](https://github.com/smyrman/units/workflows/Go/badge.svg?branch=master)](https://github.com/smyrman/units/actions?query=workflow%3AGo+branch%master) [![Go Reference](https://pkg.go.dev/badge/github.com/smyrman/units.svg)](https://pkg.go.dev/github.com/smyrman/units) # Units @@ -8,16 +8,16 @@ Units is a small Go package that utilizes Go's strict type system to achieve bet It currently has the following sub-packages: - [angular](http://godoc.org/github.com/smyrman/units/angular) defines the types: - - Angle - - Velocity - - Acceleration + - Angle + - Velocity + - Acceleration - [linear](https://godoc.org/github.com/smyrman/units/linear) define the types: - - Distance - - Velocity - - Acceleration + - Distance + - Velocity + - Acceleration - [planar](https://godoc.org/github.com/smyrman/units/planar) defines the types: - - Point - - Area + - Point + - Area Each type has defined constants for relevant units. In general, you don't need to worry about which unit the underlying values is stored in, as long as you **always multiply with a relevant uint** when you first set your value. E.g.: @@ -25,10 +25,9 @@ Each type has defined constants for relevant units. In general, you don't need t var a angular.Angle = 90 * angular.Degrees ``` - ## Use at your own risk ⚠️ -This libary has seen low usage and has little testing. If you still prefer to use this package over other packages, contributions are still welcome. +This library has seen low usage and has little testing. If you still prefer to use this package over other packages, contributions are still welcome. ## Alternatives @@ -50,18 +49,17 @@ Alternative units pacakges: - [x] Modern Imperial units - [ ] Appropriate test coverage per package. - ## Why would I want to use a units package? The [Mars Climate Orbiter](https://en.wikipedia.org/wiki/Mars_Climate_Orbiter) crashed into Mars when software producing the non-SI units of pound-seconds was used to generate input to a program expecting newton-seconds (N s). While the units package would not have prevented that particular issue of communication between programs, the goal is that it will help you to avoid unit related mistakes within your own program, and to make working with various units as straight-forward and painless as possible. -Considerting the low amount of usage of _this_ units package in particular, you may want to consider one that's more used though! Or make sure to add the number of tests that you need to be confident in your solution. +Considering the low amount of usage of _this_ units package in particular, you may want to consider one that's more used though! Or make sure to add the number of tests that you need to be confident in your solution. ## Our experience We found an earlier version of this code surprisingly useful back in 2012, when we where developing an autonomous robot using Go for the high-level programming. -While we still used plain float64 values in the matrices that calculated the next system states, we found using the units package for the input and output values of various blocks extremely useful. This package helped us verify that we never did something stupid like passing a distance to a function that expected a velocity. The simplicity of using the return value from `.Normalize()`on an angle, also made it very easy for us to ensure normalized angles for our navigation system. Forgetting to normilize an angle could lead to spinning robots... +While we still used plain float64 values in the matrices that calculated the next system states, we found using the units package for the input and output values of various blocks extremely useful. This package helped us verify that we never did something stupid like passing a distance to a function that expected a velocity. The simplicity of using the return value from `.Normalize()`on an angle, also made it very easy for us to ensure normalized angles for our navigation system. Forgetting to normalize an angle could lead to spinning robots... We used metric values for both internal and displaying purposes, but the aim is also that this package will help those who wish to provide non-metric units as input or display values for their programs. @@ -87,23 +85,3 @@ func main() { fmt.Printf("v = %.2f mph\n", v.MilesPerHour()) } ``` - -See the [godoc reference docs](http://godoc.org/github.com/smyrman/units) for a full list of methods per type. - -## Migrating from v0.0.1 - -Since v0.0.1, the package layout has been changed. Type and constant definitions have been moved into sub-packages to allow for shorter names, and a more restricted scope from which package symmetry can arise. - -To migrate, search and replace the following: - -- `units.Distance` -> `linear.Distance` -- `units.Velocity` -> `linear.Velocity` -- `units.Angle` -> `angular.Angle` -- `units.AngularVelocity` -> `angular.Velocity` -- `units.Coordinate` -> `planar.Point` - -Then, assure the proper imports, most conveniently through the use of [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports), or manually by replacing the "github.com/smyrman/units" import with the appropriate import(s): - -- "github.com/smyrman/units/linear" -- "github.com/smyrman/units/angular" -- "github.com/smyrman/units/planar" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..364cd4a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/smyrman/units + +go 1.17 diff --git a/internal/cmd/units-codegen/codegen.go b/internal/cmd/units-codegen/codegen.go index 3ed82a7..0f3e4e5 100644 --- a/internal/cmd/units-codegen/codegen.go +++ b/internal/cmd/units-codegen/codegen.go @@ -13,6 +13,7 @@ import ( const helpText = `Usage: units-codegen[ -o FILE] -t TYPE PKG_CONFIG_FILE + ` type outputVal struct { @@ -26,7 +27,7 @@ func (o outputVal) String() string { func (o *outputVal) Write(p []byte) (n int, err error) { if o.w == nil { - return 0, errors.New("No writer configured") + return 0, errors.New("no writer configured") } return o.w.Write(p) } @@ -40,7 +41,7 @@ func (o *outputVal) Set(s string) error { o.name = s fd, err := os.Create(s) if err != nil { - return fmt.Errorf("Could not create output file: %s", err.Error()) + return fmt.Errorf("could not create output file: %s", err.Error()) } o.w = fd return nil @@ -54,10 +55,12 @@ var options struct { func init() { flag.Usage = func() { - fmt.Fprintln(os.Stderr, helpText) + fmt.Fprint(os.Stderr, helpText) flag.PrintDefaults() } - options.output.Set("") + if err := options.output.Set(""); err != nil { + panic(err) + } flag.Var(&options.output, "o", "Write to file instead of stdout") flag.StringVar(&options.typeName, "t", "", "Type to generate code for (required)") log.SetPrefix("units-codegen:") diff --git a/internal/cmd/units-codegen/template.go b/internal/cmd/units-codegen/template.go index 2fc2fbd..e990477 100644 --- a/internal/cmd/units-codegen/template.go +++ b/internal/cmd/units-codegen/template.go @@ -1,6 +1,7 @@ package main import ( + _ "embed" "fmt" "regexp" "strings" @@ -156,60 +157,5 @@ var tmplFunctions = template.FuncMap{ "lower": strings.ToLower, } -const tmpl = `// This code is generated by units-codegen; do not manualy edit this file. -{{$self := .Self}}{{$name := .Name}} -package {{.PkgName}} - -{{if .Import}}import ({{range .Import}} - "{{.}}"{{end}} -){{end}} - -// Units for {{.Name}} values. Always multiply with a unit when setting the initial value like you would for -// time.Time. This prevents you from having to worry about the internal storage format. -const ({{range .Units}} - {{.Name}} {{$name}} = {{.Value}}{{end}} -) -{{range .Units}} -// {{.NamePlural}} returns {{$self}} as a floating point number of {{.NamePlural|lower}}. -func ({{$self}} {{$name}}) {{.NamePlural}}() float64 { - return float64({{$self}} / {{.Name}}) -} -{{end}} -// Abs returns the absolute value of {{.Self}} as a copy. -func ({{.Self}} {{.Name}}) Abs() {{.Name}} { - if {{.Self}} < 0 { - return -{{.Self}} - } - return {{.Self}} -} - -// Mul returns the product of {{.Self}} * x as a new {{.Name}}. -func ({{.Self}} {{.Name}}) Mul(x float64) {{.Name}} { - return {{.Self}} * {{.Name}}(x) -} - -// Div returns the quotient of {{.Self}} / x as a new {{.Name}}. -func ({{.Self}} {{.Name}}) Div(x float64) {{.Name}} { - return {{.Self}} / {{.Name}}(x) -} - -// Div{{.Name}} returns the quotient of {{.Self}} / x as a floating point number. -func ({{.Self}} {{.Name}}) Div{{.Name}}(x {{.Name}}) float64 { - return float64({{.Self}} / x) -} -{{if .DivDurationType}} -// DivDuration returns the quotient of {{.Self}} / t as a {{.DivDurationType}}. -func ({{.Self}} {{.Name}}) DivDuration(t time.Duration) {{.DivDurationType}} { - return {{.DivDurationType}}(float64({{.Self}}) / float64(t)) -} - -// Div{{.DivDurationType}} returns the quotient of {{.Self}} / x as a time.Duration. -func ({{.Self}} {{.Name}}) Div{{.DivDurationType}}(x {{.DivDurationType}}) time.Duration { - return time.Duration(float64({{.Self}}) / float64(x)) -}{{end}} -{{if .MulDurationType}} -// MulDuration returns the product of {{.Self}} * t as a {{.MulDurationType}}. -func ({{.Self}} {{.Name}}) MulDuration(t time.Duration) {{.MulDurationType}} { - return {{.MulDurationType}}(float64({{.Self}}) * float64(t)) -}{{end}} -` +//go:embed template.go.tmpl +var tmpl string diff --git a/internal/cmd/units-codegen/template.go.tmpl b/internal/cmd/units-codegen/template.go.tmpl new file mode 100644 index 0000000..4ee993a --- /dev/null +++ b/internal/cmd/units-codegen/template.go.tmpl @@ -0,0 +1,56 @@ +// This code is generated by units-codegen; do not manualy edit this file. +{{$self := .Self}}{{$name := .Name}} +package {{.PkgName}} + +{{if .Import}}import ({{range .Import}} + "{{.}}"{{end}} +){{end}} + +// Units for {{.Name}} values. Always multiply with a unit when setting the initial value like you would for +// time.Time. This prevents you from having to worry about the internal storage format. +const ({{range .Units}} + {{.Name}} {{$name}} = {{.Value}}{{end}} +) +{{range .Units}} +// {{.NamePlural}} returns {{$self}} as a floating point number of {{.NamePlural|lower}}. +func ({{$self}} {{$name}}) {{.NamePlural}}() float64 { + return float64({{$self}} / {{.Name}}) +} +{{end}} +// Abs returns the absolute value of {{.Self}} as a copy. +func ({{.Self}} {{.Name}}) Abs() {{.Name}} { + if {{.Self}} < 0 { + return -{{.Self}} + } + return {{.Self}} +} + +// Mul returns the product of {{.Self}} * x as a new {{.Name}}. +func ({{.Self}} {{.Name}}) Mul(x float64) {{.Name}} { + return {{.Self}} * {{.Name}}(x) +} + +// Div returns the quotient of {{.Self}} / x as a new {{.Name}}. +func ({{.Self}} {{.Name}}) Div(x float64) {{.Name}} { + return {{.Self}} / {{.Name}}(x) +} + +// Div{{.Name}} returns the quotient of {{.Self}} / x as a floating point number. +func ({{.Self}} {{.Name}}) Div{{.Name}}(x {{.Name}}) float64 { + return float64({{.Self}} / x) +} +{{if .DivDurationType}} +// DivDuration returns the quotient of {{.Self}} / t as a {{.DivDurationType}}. +func ({{.Self}} {{.Name}}) DivDuration(t time.Duration) {{.DivDurationType}} { + return {{.DivDurationType}}(float64({{.Self}}) / float64(t)) +} + +// Div{{.DivDurationType}} returns the quotient of {{.Self}} / x as a time.Duration. +func ({{.Self}} {{.Name}}) Div{{.DivDurationType}}(x {{.DivDurationType}}) time.Duration { + return time.Duration(float64({{.Self}}) / float64(x)) +}{{end}} +{{if .MulDurationType}} +// MulDuration returns the product of {{.Self}} * t as a {{.MulDurationType}}. +func ({{.Self}} {{.Name}}) MulDuration(t time.Duration) {{.MulDurationType}} { + return {{.MulDurationType}}(float64({{.Self}}) * float64(t)) +}{{end}} diff --git a/internal/floattest/floattest.go b/internal/floattest/floattest.go index 2f0483c..6763e46 100644 --- a/internal/floattest/floattest.go +++ b/internal/floattest/floattest.go @@ -9,10 +9,7 @@ import ( func AlmostEqual(x, y float64) bool { const tolerance = 1e-11 - if math.Abs(x-y) < tolerance { - return true - } - return false + return math.Abs(x-y) < tolerance } // CompareCase should be used for Go >= 1.7 sub-tests that compares a returned float64 value from a function (Func) diff --git a/linear/distance_test.go b/linear/distance_test.go index 8e863de..fc1959b 100644 --- a/linear/distance_test.go +++ b/linear/distance_test.go @@ -68,7 +68,7 @@ func TestImperialDistanceUnits(t *testing.T) { Name: "Thou", Unit: linear.Thou, Expect: 25.4 * linear.Micrometer, - Feet: 1 / 12000, + Feet: 1.0 / 12000, Millimeters: 0.0254, Meters: 0.0000254, }, @@ -76,7 +76,7 @@ func TestImperialDistanceUnits(t *testing.T) { Name: "Inch", Unit: linear.Inch, Expect: 1000 * linear.Thou, - Feet: 1 / 12, + Feet: 1.0 / 12, Millimeters: 25.4, Meters: 0.0254, },