github.com/gopherd/gonum@v0.0.4/unit/generate_unit.go (about)

     1  // Copyright ©2014 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build ignore
     6  // +build ignore
     7  
     8  package main
     9  
    10  import (
    11  	"bytes"
    12  	"go/format"
    13  	"log"
    14  	"os"
    15  	"strings"
    16  	"text/template"
    17  
    18  	"github.com/gopherd/gonum/unit"
    19  )
    20  
    21  type Unit struct {
    22  	DimensionName string
    23  	Receiver      string
    24  	PowerOffset   int    // from normal (for example, mass base unit is kg, not g)
    25  	PrintString   string // print string for the unit (kg for mass)
    26  	ExtraConstant []Constant
    27  	Name          string
    28  	TypeComment   string // text to comment the type
    29  	Dimensions    []Dimension
    30  	ErForm        string // for Xxxer interface
    31  }
    32  
    33  type Dimension struct {
    34  	Name  string
    35  	Power int
    36  }
    37  
    38  func (u Unit) Units() string {
    39  	dims := make(unit.Dimensions)
    40  	for _, d := range u.Dimensions {
    41  		dims[dimOf[d.Name]] = d.Power
    42  	}
    43  	return dims.String()
    44  }
    45  
    46  const (
    47  	AngleName             string = "AngleDim"
    48  	CurrentName           string = "CurrentDim"
    49  	LengthName            string = "LengthDim"
    50  	LuminousIntensityName string = "LuminousIntensityDim"
    51  	MassName              string = "MassDim"
    52  	MoleName              string = "MoleDim"
    53  	TemperatureName       string = "TemperatureDim"
    54  	TimeName              string = "TimeDim"
    55  )
    56  
    57  var dimOf = map[string]unit.Dimension{
    58  	"AngleDim":             unit.AngleDim,
    59  	"CurrentDim":           unit.CurrentDim,
    60  	"LengthDim":            unit.LengthDim,
    61  	"LuminousIntensityDim": unit.LuminousIntensityDim,
    62  	"MassDim":              unit.MassDim,
    63  	"MoleDim":              unit.MoleDim,
    64  	"TemperatureDim":       unit.TemperatureDim,
    65  	"TimeDim":              unit.TimeDim,
    66  }
    67  
    68  type Constant struct {
    69  	Name  string
    70  	Value string
    71  }
    72  
    73  type Prefix struct {
    74  	Name  string
    75  	Power int
    76  }
    77  
    78  var Units = []Unit{
    79  	// Base units.
    80  	{
    81  		DimensionName: "Dimless",
    82  		Receiver:      "d",
    83  		TypeComment:   "Dimless represents a dimensionless constant",
    84  		Dimensions:    []Dimension{},
    85  	},
    86  	{
    87  		DimensionName: "Angle",
    88  		Receiver:      "a",
    89  		PrintString:   "rad",
    90  		Name:          "Rad",
    91  		TypeComment:   "Angle represents an angle in radians",
    92  		Dimensions: []Dimension{
    93  			{Name: AngleName, Power: 1},
    94  		},
    95  	},
    96  	{
    97  		DimensionName: "Current",
    98  		Receiver:      "i",
    99  		PrintString:   "A",
   100  		Name:          "Ampere",
   101  		TypeComment:   "Current represents a current in Amperes",
   102  		Dimensions: []Dimension{
   103  			{Name: CurrentName, Power: 1},
   104  		},
   105  	},
   106  	{
   107  		DimensionName: "Length",
   108  		Receiver:      "l",
   109  		PrintString:   "m",
   110  		Name:          "Metre",
   111  		TypeComment:   "Length represents a length in metres",
   112  		Dimensions: []Dimension{
   113  			{Name: LengthName, Power: 1},
   114  		},
   115  	},
   116  	{
   117  		DimensionName: "LuminousIntensity",
   118  		Receiver:      "j",
   119  		PrintString:   "cd",
   120  		Name:          "Candela",
   121  		TypeComment:   "Candela represents a luminous intensity in candela",
   122  		Dimensions: []Dimension{
   123  			{Name: LuminousIntensityName, Power: 1},
   124  		},
   125  	},
   126  	{
   127  		DimensionName: "Mass",
   128  		Receiver:      "m",
   129  		PowerOffset:   -3,
   130  		PrintString:   "kg",
   131  		Name:          "Gram",
   132  		TypeComment:   "Mass represents a mass in kilograms",
   133  		ExtraConstant: []Constant{
   134  			{Name: "Kilogram", Value: "Kilo * Gram"},
   135  		},
   136  		Dimensions: []Dimension{
   137  			{Name: MassName, Power: 1},
   138  		},
   139  	},
   140  	{
   141  		DimensionName: "Mole",
   142  		Receiver:      "n",
   143  		PrintString:   "mol",
   144  		Name:          "Mol",
   145  		TypeComment:   "Mole represents an amount in moles",
   146  		Dimensions: []Dimension{
   147  			{Name: MoleName, Power: 1},
   148  		},
   149  	},
   150  	{
   151  		DimensionName: "Temperature",
   152  		Receiver:      "t",
   153  		PrintString:   "K",
   154  		Name:          "Kelvin",
   155  		TypeComment:   "Temperature represents a temperature in Kelvin",
   156  		Dimensions: []Dimension{
   157  			{Name: TemperatureName, Power: 1},
   158  		},
   159  		ErForm: "Temperaturer",
   160  	},
   161  	{
   162  		DimensionName: "Time",
   163  		Receiver:      "t",
   164  		PrintString:   "s",
   165  		Name:          "Second",
   166  		TypeComment:   "Time represents a duration in seconds",
   167  		ExtraConstant: []Constant{
   168  			{Name: "Minute", Value: "60 * Second"},
   169  			{Name: "Hour", Value: "60 * Minute"},
   170  		},
   171  		Dimensions: []Dimension{
   172  			{Name: TimeName, Power: 1},
   173  		},
   174  		ErForm: "Timer",
   175  	},
   176  
   177  	// Derived units.
   178  	{
   179  		DimensionName: "AbsorbedRadioactiveDose",
   180  		Receiver:      "a",
   181  		PrintString:   "Gy",
   182  		Name:          "Gray",
   183  		TypeComment:   "AbsorbedRadioactiveDose is a measure of absorbed dose of ionizing radiation in grays",
   184  		Dimensions: []Dimension{
   185  			{Name: LengthName, Power: 2},
   186  			{Name: TimeName, Power: -2},
   187  		},
   188  	},
   189  	{
   190  		DimensionName: "Acceleration",
   191  		Receiver:      "a",
   192  		PrintString:   "m s^-2",
   193  		TypeComment:   "Acceleration represents an acceleration in metres per second squared",
   194  		Dimensions: []Dimension{
   195  			{Name: LengthName, Power: 1},
   196  			{Name: TimeName, Power: -2},
   197  		},
   198  	},
   199  	{
   200  		DimensionName: "Area",
   201  		Receiver:      "a",
   202  		PrintString:   "m^2",
   203  		TypeComment:   "Area represents an area in square metres",
   204  		Dimensions: []Dimension{
   205  			{Name: LengthName, Power: 2},
   206  		},
   207  	},
   208  	{
   209  		DimensionName: "Radioactivity",
   210  		Receiver:      "r",
   211  		PrintString:   "Bq",
   212  		Name:          "Becquerel",
   213  		TypeComment:   "Radioactivity represents a rate of radioactive decay in becquerels",
   214  		Dimensions: []Dimension{
   215  			{Name: TimeName, Power: -1},
   216  		},
   217  	},
   218  	{
   219  		DimensionName: "Capacitance",
   220  		Receiver:      "cp",
   221  		PrintString:   "F",
   222  		Name:          "Farad",
   223  		TypeComment:   "Capacitance represents an electrical capacitance in Farads",
   224  		Dimensions: []Dimension{
   225  			{Name: CurrentName, Power: 2},
   226  			{Name: LengthName, Power: -2},
   227  			{Name: MassName, Power: -1},
   228  			{Name: TimeName, Power: 4},
   229  		},
   230  		ErForm: "Capacitancer",
   231  	},
   232  	{
   233  		DimensionName: "Charge",
   234  		Receiver:      "ch",
   235  		PrintString:   "C",
   236  		Name:          "Coulomb",
   237  		TypeComment:   "Charge represents an electric charge in Coulombs",
   238  		Dimensions: []Dimension{
   239  			{Name: CurrentName, Power: 1},
   240  			{Name: TimeName, Power: 1},
   241  		},
   242  		ErForm: "Charger",
   243  	},
   244  	{
   245  		DimensionName: "Conductance",
   246  		Receiver:      "co",
   247  		PrintString:   "S",
   248  		Name:          "Siemens",
   249  		TypeComment:   "Conductance represents an electrical conductance in Siemens",
   250  		Dimensions: []Dimension{
   251  			{Name: CurrentName, Power: 2},
   252  			{Name: LengthName, Power: -2},
   253  			{Name: MassName, Power: -1},
   254  			{Name: TimeName, Power: 3},
   255  		},
   256  		ErForm: "Conductancer",
   257  	},
   258  	{
   259  		DimensionName: "EquivalentRadioactiveDose",
   260  		Receiver:      "a",
   261  		PrintString:   "Sy",
   262  		Name:          "Sievert",
   263  		TypeComment:   "EquivalentRadioactiveDose is a measure of equivalent dose of ionizing radiation in sieverts",
   264  		Dimensions: []Dimension{
   265  			{Name: LengthName, Power: 2},
   266  			{Name: TimeName, Power: -2},
   267  		},
   268  	},
   269  	{
   270  		DimensionName: "Energy",
   271  		Receiver:      "e",
   272  		PrintString:   "J",
   273  		Name:          "Joule",
   274  		TypeComment:   "Energy represents a quantity of energy in Joules",
   275  		Dimensions: []Dimension{
   276  			{Name: LengthName, Power: 2},
   277  			{Name: MassName, Power: 1},
   278  			{Name: TimeName, Power: -2},
   279  		},
   280  	},
   281  	{
   282  		DimensionName: "Frequency",
   283  		Receiver:      "f",
   284  		PrintString:   "Hz",
   285  		Name:          "Hertz",
   286  		TypeComment:   "Frequency represents a frequency in Hertz",
   287  		Dimensions: []Dimension{
   288  			{Name: TimeName, Power: -1},
   289  		},
   290  	},
   291  	{
   292  		DimensionName: "Force",
   293  		Receiver:      "f",
   294  		PrintString:   "N",
   295  		Name:          "Newton",
   296  		TypeComment:   "Force represents a force in Newtons",
   297  		Dimensions: []Dimension{
   298  			{Name: LengthName, Power: 1},
   299  			{Name: MassName, Power: 1},
   300  			{Name: TimeName, Power: -2},
   301  		},
   302  		ErForm: "Forcer",
   303  	},
   304  	{
   305  		DimensionName: "Inductance",
   306  		Receiver:      "i",
   307  		PrintString:   "H",
   308  		Name:          "Henry",
   309  		TypeComment:   "Inductance represents an electrical inductance in Henry",
   310  		Dimensions: []Dimension{
   311  			{Name: CurrentName, Power: -2},
   312  			{Name: LengthName, Power: 2},
   313  			{Name: MassName, Power: 1},
   314  			{Name: TimeName, Power: -2},
   315  		},
   316  		ErForm: "Inductancer",
   317  	},
   318  	{
   319  		DimensionName: "Power",
   320  		Receiver:      "pw",
   321  		PrintString:   "W",
   322  		Name:          "Watt",
   323  		TypeComment:   "Power represents a power in Watts",
   324  		Dimensions: []Dimension{
   325  			{Name: LengthName, Power: 2},
   326  			{Name: MassName, Power: 1},
   327  			{Name: TimeName, Power: -3},
   328  		},
   329  	},
   330  	{
   331  		DimensionName: "Resistance",
   332  		Receiver:      "r",
   333  		PrintString:   "Ω",
   334  		Name:          "Ohm",
   335  		TypeComment:   "Resistance represents an electrical resistance, impedance or reactance in Ohms",
   336  		Dimensions: []Dimension{
   337  			{Name: CurrentName, Power: -2},
   338  			{Name: LengthName, Power: 2},
   339  			{Name: MassName, Power: 1},
   340  			{Name: TimeName, Power: -3},
   341  		},
   342  		ErForm: "Resistancer",
   343  	},
   344  	{
   345  		DimensionName: "MagneticFlux",
   346  		Receiver:      "m",
   347  		PrintString:   "Wb",
   348  		Name:          "Weber",
   349  		TypeComment:   "MagneticFlux represents a magnetic flux in Weber",
   350  		Dimensions: []Dimension{
   351  			{Name: CurrentName, Power: -1},
   352  			{Name: LengthName, Power: 2},
   353  			{Name: MassName, Power: 1},
   354  			{Name: TimeName, Power: -2},
   355  		},
   356  	},
   357  	{
   358  		DimensionName: "MagneticFluxDensity",
   359  		Receiver:      "m",
   360  		PrintString:   "T",
   361  		Name:          "Tesla",
   362  		TypeComment:   "MagneticFluxDensity represents a magnetic flux density in Tesla",
   363  		Dimensions: []Dimension{
   364  			{Name: CurrentName, Power: -1},
   365  			{Name: MassName, Power: 1},
   366  			{Name: TimeName, Power: -2},
   367  		},
   368  	},
   369  	{
   370  		DimensionName: "Pressure",
   371  		Receiver:      "pr",
   372  		PrintString:   "Pa",
   373  		Name:          "Pascal",
   374  		TypeComment:   "Pressure represents a pressure in Pascals",
   375  		Dimensions: []Dimension{
   376  			{Name: LengthName, Power: -1},
   377  			{Name: MassName, Power: 1},
   378  			{Name: TimeName, Power: -2},
   379  		},
   380  		ErForm: "Pressurer",
   381  	},
   382  	{
   383  		DimensionName: "Torque",
   384  		Receiver:      "t",
   385  		PrintString:   "N m",
   386  		Name:          "Newtonmetre",
   387  		TypeComment:   "Torque represents a torque in Newton metres",
   388  		Dimensions: []Dimension{
   389  			{Name: LengthName, Power: 2},
   390  			{Name: MassName, Power: 1},
   391  			{Name: TimeName, Power: -2},
   392  		},
   393  		ErForm: "Torquer",
   394  	},
   395  	{
   396  		DimensionName: "Velocity",
   397  		Receiver:      "v",
   398  		PrintString:   "m s^-1",
   399  		TypeComment:   "Velocity represents a velocity in metres per second",
   400  		Dimensions: []Dimension{
   401  			{Name: LengthName, Power: 1},
   402  			{Name: TimeName, Power: -1},
   403  		},
   404  	},
   405  	{
   406  		DimensionName: "Voltage",
   407  		Receiver:      "v",
   408  		PrintString:   "V",
   409  		Name:          "Volt",
   410  		TypeComment:   "Voltage represents a voltage in Volts",
   411  		Dimensions: []Dimension{
   412  			{Name: CurrentName, Power: -1},
   413  			{Name: LengthName, Power: 2},
   414  			{Name: MassName, Power: 1},
   415  			{Name: TimeName, Power: -3},
   416  		},
   417  		ErForm: "Voltager",
   418  	},
   419  	{
   420  		DimensionName: "Volume",
   421  		Receiver:      "v",
   422  		PowerOffset:   -3,
   423  		PrintString:   "m^3",
   424  		Name:          "Litre",
   425  		TypeComment:   "Volume represents a volume in cubic metres",
   426  		Dimensions: []Dimension{
   427  			{Name: LengthName, Power: 3},
   428  		},
   429  	},
   430  }
   431  
   432  // Generate generates a file for each of the units
   433  func main() {
   434  	for _, unit := range Units {
   435  		generate(unit)
   436  		generateTest(unit)
   437  	}
   438  }
   439  
   440  const headerTemplate = `// Code generated by "go generate github.com/gopherd/gonum/unit”; DO NOT EDIT.
   441  
   442  // Copyright ©2014 The Gonum Authors. All rights reserved.
   443  // Use of this source code is governed by a BSD-style
   444  // license that can be found in the LICENSE file.
   445  
   446  package unit
   447  
   448  import (
   449  	"errors"
   450  	"fmt"
   451  	"math"
   452  	{{if .PrintString}}"unicode/utf8"{{end}}
   453  )
   454  
   455  // {{.TypeComment}}.
   456  type {{.DimensionName}} float64
   457  `
   458  
   459  var header = template.Must(template.New("header").Parse(headerTemplate))
   460  
   461  const constTemplate = `
   462  const {{if .ExtraConstant}}({{end}}
   463  	{{.Name}} {{.DimensionName}} = {{if .PowerOffset}} 1e{{.PowerOffset}} {{else}} 1 {{end}}
   464  	{{$name := .Name}}
   465  	{{range .ExtraConstant}} {{.Name}} = {{.Value}}
   466  	{{end}}
   467  {{if .ExtraConstant}}){{end}}
   468  `
   469  
   470  var prefix = template.Must(template.New("prefix").Parse(constTemplate))
   471  
   472  const methodTemplate = `
   473  // Unit converts the {{.DimensionName}} to a *Unit.
   474  func ({{.Receiver}} {{.DimensionName}}) Unit() *Unit {
   475  	return New(float64({{.Receiver}}), Dimensions{
   476  		{{range .Dimensions}} {{.Name}}: {{.Power}},
   477  		{{end}}
   478  		})
   479  }
   480  
   481  // {{.DimensionName}} allows {{.DimensionName}} to implement a {{if .ErForm}}{{.ErForm}}{{else}}{{.DimensionName}}er{{end}} interface.
   482  func ({{.Receiver}} {{.DimensionName}}) {{.DimensionName}}() {{.DimensionName}} {
   483  	return {{.Receiver}}
   484  }
   485  
   486  // From converts the unit into the receiver. From returns an
   487  // error if there is a mismatch in dimension.
   488  func ({{.Receiver}} *{{.DimensionName}}) From(u Uniter) error {
   489  	if !DimensionsMatch(u, {{if .Name}}{{.Name}}{{else}}{{.DimensionName}}(0){{end}}){
   490  		*{{.Receiver}} = {{.DimensionName}}(math.NaN())
   491  		return errors.New("unit: dimension mismatch")
   492  	}
   493  	*{{.Receiver}} = {{.DimensionName}}(u.Unit().Value())
   494  	return nil
   495  }
   496  `
   497  
   498  var methods = template.Must(template.New("methods").Parse(methodTemplate))
   499  
   500  const formatTemplate = `
   501  func ({{.Receiver}} {{.DimensionName}}) Format(fs fmt.State, c rune) {
   502  	switch c {
   503  	case 'v':
   504  		if fs.Flag('#') {
   505  			fmt.Fprintf(fs, "%T(%v)", {{.Receiver}}, float64({{.Receiver}}))
   506  			return
   507  		}
   508  		fallthrough
   509  	case 'e', 'E', 'f', 'F', 'g', 'G':
   510  		p, pOk := fs.Precision()
   511  		w, wOk := fs.Width()
   512  		{{if .PrintString}}const unit = " {{.PrintString}}"
   513  		switch {
   514  		case pOk && wOk:
   515  			fmt.Fprintf(fs, "%*.*"+string(c), pos(w-utf8.RuneCount([]byte(unit))), p, float64({{.Receiver}}))
   516  		case pOk:
   517  			fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}}))
   518  		case wOk:
   519  			fmt.Fprintf(fs, "%*"+string(c), pos(w-utf8.RuneCount([]byte(unit))), float64({{.Receiver}}))
   520  		default:
   521  			fmt.Fprintf(fs, "%"+string(c), float64({{.Receiver}}))
   522  		}
   523  		fmt.Fprint(fs, unit)
   524  	default:
   525  		fmt.Fprintf(fs, "%%!%c(%T=%g {{.PrintString}})", c, {{.Receiver}}, float64({{.Receiver}})) {{else}} switch {
   526  		case pOk && wOk:
   527  			fmt.Fprintf(fs, "%*.*"+string(c), w, p, float64({{.Receiver}}))
   528  		case pOk:
   529  			fmt.Fprintf(fs, "%.*"+string(c), p, float64({{.Receiver}}))
   530  		case wOk:
   531  			fmt.Fprintf(fs, "%*"+string(c), w, float64({{.Receiver}}))
   532  		default:
   533  			fmt.Fprintf(fs, "%"+string(c), float64({{.Receiver}}))
   534  		}
   535  	default:
   536  		fmt.Fprintf(fs, "%%!%c(%T=%g)", c, {{.Receiver}}, float64({{.Receiver}})) {{end}}
   537  	}
   538  }
   539  `
   540  
   541  var form = template.Must(template.New("format").Parse(formatTemplate))
   542  
   543  func generate(unit Unit) {
   544  	lowerName := strings.ToLower(unit.DimensionName)
   545  	filename := lowerName + ".go"
   546  	f, err := os.Create(filename)
   547  	if err != nil {
   548  		log.Fatal(err)
   549  	}
   550  	defer f.Close()
   551  
   552  	var buf bytes.Buffer
   553  
   554  	err = header.Execute(&buf, unit)
   555  	if err != nil {
   556  		log.Fatal(err)
   557  	}
   558  
   559  	if unit.Name != "" {
   560  		err = prefix.Execute(&buf, unit)
   561  		if err != nil {
   562  			log.Fatal(err)
   563  		}
   564  	}
   565  
   566  	err = methods.Execute(&buf, unit)
   567  	if err != nil {
   568  		log.Fatal(err)
   569  	}
   570  
   571  	err = form.Execute(&buf, unit)
   572  	if err != nil {
   573  		log.Fatal(err)
   574  	}
   575  
   576  	b, err := format.Source(buf.Bytes())
   577  	if err != nil {
   578  		f.Write(buf.Bytes()) // This is here to debug bad format
   579  		log.Fatalf("error formatting %q: %s", unit.DimensionName, err)
   580  	}
   581  
   582  	f.Write(b)
   583  }
   584  
   585  const testTemplate = `// Code generated by "go generate github.com/gopherd/gonum/unit; DO NOT EDIT.
   586  
   587  // Copyright ©2019 The Gonum Authors. All rights reserved.
   588  // Use of this source code is governed by a BSD-style
   589  // license that can be found in the LICENSE file.
   590  
   591  package unit
   592  
   593  import (
   594  	"fmt"
   595  	"testing"
   596  )
   597  
   598  func Test{{.DimensionName}}(t *testing.T) {
   599  	t.Parallel()
   600  	for _, value := range []float64{-1, 0, 1} {
   601  		var got {{.DimensionName}}
   602  		err := got.From({{.DimensionName}}(value).Unit())
   603  		if err != nil {
   604  			t.Errorf("unexpected error for %T conversion: %v", got, err)
   605  		}
   606  		if got != {{.DimensionName}}(value) {
   607  			t.Errorf("unexpected result from round trip of %T(%v): got: %v want: %v", got, value, got, value)
   608  		}
   609  		if got != got.{{.DimensionName}}() {
   610  			t.Errorf("unexpected result from self interface method call: got: %#v want: %#v", got, value)
   611  		}
   612  		err = got.From(ether(1))
   613  		if err == nil {
   614  			t.Errorf("expected error for ether to %T conversion", got)
   615  		}
   616  	}
   617  }
   618  
   619  func Test{{.DimensionName}}Format(t *testing.T) {
   620  	t.Parallel()
   621  	for _, test := range []struct{
   622  		value  {{.DimensionName}}
   623  		format string
   624  		want   string
   625  	}{
   626  		{1.23456789, "%v", "1.23456789{{with .PrintString}} {{.}}{{end}}"},
   627  		{1.23456789, "%.1v", "1{{with .PrintString}} {{.}}{{end}}"},
   628  		{1.23456789, "%20.1v", "{{if .PrintString}}{{$s := printf "1 %s" .PrintString}}{{printf "%20s" $s}}{{else}}{{printf "%20s" "1"}}{{end}}"},
   629  		{1.23456789, "%20v", "{{if .PrintString}}{{$s := printf "1.23456789 %s" .PrintString}}{{printf "%20s" $s}}{{else}}{{printf "%20s" "1.23456789"}}{{end}}"},
   630  		{1.23456789, "%1v", "1.23456789{{with .PrintString}} {{.}}{{end}}"},
   631  		{1.23456789, "%#v", "unit.{{.DimensionName}}(1.23456789)"},
   632  		{1.23456789, "%s", "%!s(unit.{{.DimensionName}}=1.23456789{{with .PrintString}} {{.}}{{end}})"},
   633  	} {
   634  		got := fmt.Sprintf(test.format, test.value)
   635  		if got != test.want {
   636  			t.Errorf("Format %q %v: got: %q want: %q", test.format, test.value, got, test.want)
   637  		}
   638  	}
   639  }
   640  `
   641  
   642  var tests = template.Must(template.New("test").Parse(testTemplate))
   643  
   644  func generateTest(unit Unit) {
   645  	lowerName := strings.ToLower(unit.DimensionName)
   646  	filename := lowerName + "_test.go"
   647  	f, err := os.Create(filename)
   648  	if err != nil {
   649  		log.Fatal(err)
   650  	}
   651  	defer f.Close()
   652  
   653  	var buf bytes.Buffer
   654  	err = tests.Execute(&buf, unit)
   655  	if err != nil {
   656  		log.Fatal(err)
   657  	}
   658  
   659  	b, err := format.Source(buf.Bytes())
   660  	if err != nil {
   661  		f.Write(buf.Bytes()) // This is here to debug bad format.
   662  		log.Fatalf("error formatting test for %q: %s", unit.DimensionName, err)
   663  	}
   664  
   665  	f.Write(b)
   666  }