mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-19 10:23:36 -06:00
Add ability to register HRSCommenters on Structs. (#3609)
Clients can register HRSCommenters to cause additional info to be included as comments when generating the human readable encoding for Noms Structs.
This commit is contained in:
@@ -168,6 +168,7 @@ See Spelling Values at https://github.com/attic-labs/noms/blob/master/doc/spelli
|
||||
log.Flag("oneline", "show a summary of each commit on a single line").Bool()
|
||||
log.Flag("graph", "show ascii-based commit hierarchy on left side of output").Bool()
|
||||
log.Flag("show-value", "show commit value rather than diff information").Bool()
|
||||
log.Flag("tz", "display formatted date comments in specified timezone, must be: local or utc").Enum("local", "utc")
|
||||
log.Arg("path-spec", "").Required().String()
|
||||
|
||||
// merge
|
||||
@@ -201,6 +202,7 @@ See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spell
|
||||
`)
|
||||
show.Flag("raw", "If true, dumps the raw binary version of the data").Bool()
|
||||
show.Flag("stats", "If true, reports statistics related to the value").Bool()
|
||||
show.Flag("tz", "display formatted date comments in specified timezone, must be: local or utc").Enum("local", "utc")
|
||||
show.Arg("object", "a noms object").Required().String()
|
||||
|
||||
// sync
|
||||
|
||||
@@ -164,7 +164,7 @@ func (s *nomsCommitTestSuite) TestNomsCommitMetadata() {
|
||||
|
||||
metaOld = metaNew
|
||||
|
||||
stdoutString, stderrString = s.MustRun(main, []string{"commit", "--allow-dupe=1", "--meta=message=bar", "--date=" + spec.CommitMetaDateFormat, dsName + ".value", sp.String()})
|
||||
stdoutString, stderrString = s.MustRun(main, []string{"commit", "--allow-dupe=1", "--meta=message=bar", "--date=" + spec.CommitMetaDateFormat[:20], dsName + ".value", sp.String()})
|
||||
s.Empty(stderrString)
|
||||
s.Contains(stdoutString, "New head #")
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/attic-labs/noms/cmd/util"
|
||||
"github.com/attic-labs/noms/go/config"
|
||||
@@ -57,6 +59,7 @@ func setupLogFlags() *flag.FlagSet {
|
||||
logFlagSet.BoolVar(&oneline, "oneline", false, "show a summary of each commit on a single line")
|
||||
logFlagSet.BoolVar(&showGraph, "graph", false, "show ascii-based commit hierarchy on left side of output")
|
||||
logFlagSet.BoolVar(&showValue, "show-value", false, "show commit value rather than diff information")
|
||||
logFlagSet.StringVar(&tzName, "tz", "local", "display formatted date comments in specified timezone, must be: local or utc")
|
||||
outputpager.RegisterOutputpagerFlags(logFlagSet)
|
||||
verbose.RegisterVerboseFlags(logFlagSet)
|
||||
return logFlagSet
|
||||
@@ -66,6 +69,9 @@ func runLog(args []string) int {
|
||||
useColor = shouldUseColor()
|
||||
cfg := config.NewResolver()
|
||||
|
||||
tz, _ := locationFromTimezoneArg(tzName, nil)
|
||||
datetime.RegisterHRSCommenter(tz)
|
||||
|
||||
resolved := cfg.ResolvePathSpec(args[0])
|
||||
sp, err := spec.ForPath(resolved)
|
||||
d.CheckErrorNoUsage(err)
|
||||
@@ -107,7 +113,7 @@ func runLog(args []string) int {
|
||||
|
||||
go func(ch chan []byte, node LogNode) {
|
||||
buff := &bytes.Buffer{}
|
||||
printCommit(node, path, buff, database)
|
||||
printCommit(node, path, buff, database, tz)
|
||||
ch <- buff.Bytes()
|
||||
}(ch, ln)
|
||||
|
||||
@@ -135,7 +141,7 @@ func runLog(args []string) int {
|
||||
|
||||
// Prints the information for one commit in the log, including ascii graph on left side of commits if
|
||||
// -graph arg is true.
|
||||
func printCommit(node LogNode, path types.Path, w io.Writer, db datas.Database) (err error) {
|
||||
func printCommit(node LogNode, path types.Path, w io.Writer, db datas.Database, tz *time.Location) (err error) {
|
||||
maxMetaFieldNameLength := func(commit types.Struct) int {
|
||||
maxLen := 0
|
||||
if m, ok := commit.MaybeGet(datas.MetaField); ok {
|
||||
@@ -181,7 +187,7 @@ func printCommit(node LogNode, path types.Path, w io.Writer, db datas.Database)
|
||||
lineno := 1
|
||||
|
||||
if maxLines != 0 {
|
||||
lineno, err = writeMetaLines(node, maxLines, lineno, maxFieldNameLen, w)
|
||||
lineno, err = writeMetaLines(node, maxLines, lineno, maxFieldNameLen, w, tz)
|
||||
if err != nil && err != writers.MaxLinesErr {
|
||||
fmt.Fprintf(w, "error: %s\n", err)
|
||||
return
|
||||
@@ -249,7 +255,7 @@ func genGraph(node LogNode, lineno int) string {
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func writeMetaLines(node LogNode, maxLines, lineno, maxLabelLen int, w io.Writer) (int, error) {
|
||||
func writeMetaLines(node LogNode, maxLines, lineno, maxLabelLen int, w io.Writer, tz *time.Location) (int, error) {
|
||||
if m, ok := node.commit.MaybeGet(datas.MetaField); ok {
|
||||
genPrefix := func(w *writers.PrefixWriter) []byte {
|
||||
return []byte(genGraph(node, int(w.NumLines)))
|
||||
@@ -261,14 +267,16 @@ func writeMetaLines(node LogNode, maxLines, lineno, maxLabelLen int, w io.Writer
|
||||
types.TypeOf(meta).Desc.(types.StructDesc).IterFields(func(fieldName string, t *types.Type, optional bool) {
|
||||
v := meta.Get(fieldName)
|
||||
fmt.Fprintf(pw, "%-*s", maxLabelLen+2, strings.Title(fieldName)+":")
|
||||
// Encode dates as formatted string if this is a top-level meta
|
||||
// field of type datetime.DateTimeType
|
||||
if types.TypeOf(v).Equals(datetime.DateTimeType) {
|
||||
var dt datetime.DateTime
|
||||
dt.UnmarshalNoms(v)
|
||||
fmt.Fprintf(pw, dt.Format(spec.CommitMetaDateFormat))
|
||||
fmt.Fprintln(pw, dt.In(tz).Format(time.RFC3339))
|
||||
} else {
|
||||
types.WriteEncodedValue(pw, v)
|
||||
}
|
||||
fmt.Fprintf(pw, "\n")
|
||||
fmt.Fprintln(pw)
|
||||
})
|
||||
})
|
||||
return int(pw.NumLines), err
|
||||
@@ -374,3 +382,16 @@ func min(i, j int) int {
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func locationFromTimezoneArg(tz string, defaultTZ *time.Location) (*time.Location, error) {
|
||||
switch tz {
|
||||
case "local":
|
||||
return time.Local, nil
|
||||
case "utc":
|
||||
return time.UTC, nil
|
||||
case "":
|
||||
return defaultTZ, nil
|
||||
default:
|
||||
return nil, errors.New("value must be: local or utc")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/attic-labs/noms/go/config"
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
"github.com/attic-labs/noms/go/types"
|
||||
"github.com/attic-labs/noms/go/util/datetime"
|
||||
"github.com/attic-labs/noms/go/util/outputpager"
|
||||
"github.com/attic-labs/noms/go/util/verbose"
|
||||
flag "github.com/juju/gnuflag"
|
||||
@@ -28,8 +29,11 @@ var nomsShow = &util.Command{
|
||||
Nargs: 1,
|
||||
}
|
||||
|
||||
var showRaw = false
|
||||
var showStats = false
|
||||
var (
|
||||
showRaw = false
|
||||
showStats = false
|
||||
tzName string
|
||||
)
|
||||
|
||||
func setupShowFlags() *flag.FlagSet {
|
||||
showFlagSet := flag.NewFlagSet("show", flag.ExitOnError)
|
||||
@@ -37,6 +41,7 @@ func setupShowFlags() *flag.FlagSet {
|
||||
verbose.RegisterVerboseFlags(showFlagSet)
|
||||
showFlagSet.BoolVar(&showRaw, "raw", false, "If true, dumps the raw binary version of the data")
|
||||
showFlagSet.BoolVar(&showStats, "stats", false, "If true, reports statistics related to the value")
|
||||
showFlagSet.StringVar(&tzName, "tz", "local", "display formatted date comments in specified timezone, must be: local or utc")
|
||||
return showFlagSet
|
||||
}
|
||||
|
||||
@@ -69,6 +74,9 @@ func runShow(args []string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
tz, _ := locationFromTimezoneArg(tzName, nil)
|
||||
datetime.RegisterHRSCommenter(tz)
|
||||
|
||||
pgr := outputpager.Start()
|
||||
defer pgr.Stop()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
flag "github.com/juju/gnuflag"
|
||||
)
|
||||
|
||||
const CommitMetaDateFormat = "2006-01-02T15:04:05-0700"
|
||||
const CommitMetaDateFormat = time.RFC3339
|
||||
|
||||
var (
|
||||
commitMetaDate string
|
||||
@@ -101,7 +101,7 @@ func CreateCommitMetaStruct(db datas.Database, date, message string, keyValueStr
|
||||
} else {
|
||||
_, err := time.Parse(CommitMetaDateFormat, date)
|
||||
if err != nil {
|
||||
return types.EmptyStruct, errors.New(fmt.Sprintf("Unable to parse date: %s", date))
|
||||
return types.EmptyStruct, errors.New(fmt.Sprintf("Unable to parse date: %s, error: %s", date, err))
|
||||
}
|
||||
}
|
||||
metaValues["date"] = types.String(date)
|
||||
|
||||
@@ -9,12 +9,80 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
"github.com/attic-labs/noms/go/util/writers"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
// Clients can register a 'commenter' to return a comment that will get appended
|
||||
// to the first line of encoded values. For example, the noms DateTime struct
|
||||
// normally gets encoded as follows:
|
||||
// lastRefresh: DateTime {
|
||||
// secSinceEpoch: 1.501801626877e+09,
|
||||
// }
|
||||
//
|
||||
// By registering a commenter that returns a nicely formatted date,
|
||||
// the struct will be coded with a comment:
|
||||
// lastRefresh: DateTime { // 2017-08-03T16:07:06-07:00
|
||||
// secSinceEpoch: 1.501801626877e+09,
|
||||
// }
|
||||
|
||||
// Function type for commenter functions
|
||||
type HRSCommenter interface {
|
||||
Comment(Value) string
|
||||
}
|
||||
|
||||
var (
|
||||
commenterRegistry = map[string]map[string]HRSCommenter{}
|
||||
registryLock sync.RWMutex
|
||||
)
|
||||
|
||||
// RegisterHRSCommenter is called to with three arguments:
|
||||
// typename: the name of the struct this function will be applied to
|
||||
// unique: an arbitrary string to differentiate functions that should be applied
|
||||
// to different structs that have the same name (e.g. two implementations of
|
||||
// the "Employee" type.
|
||||
// commenter: an interface with a 'Comment()' function that gets called for all
|
||||
// Values with this name. The function should verify the type of the Value
|
||||
// and, if appropriate, return a non-empty string to be appended as the comment
|
||||
func RegisterHRSCommenter(typename, unique string, commenter HRSCommenter) {
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
commenters := commenterRegistry[typename]
|
||||
if commenters == nil {
|
||||
commenters = map[string]HRSCommenter{}
|
||||
commenterRegistry[typename] = commenters
|
||||
}
|
||||
commenters[unique] = commenter
|
||||
}
|
||||
|
||||
// UnregisterHRSCommenter will remove a commenter function for a specified
|
||||
// typename/unique string combination.
|
||||
func UnregisterHRSCommenter(typename, unique string) {
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
r := commenterRegistry[typename]
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
delete(r, unique)
|
||||
}
|
||||
|
||||
// GetHRSCommenters the map of 'unique' strings to HRSCommentFunc for
|
||||
// a specified typename.
|
||||
func GetHRSCommenters(typename string) []HRSCommenter {
|
||||
registryLock.RLock()
|
||||
defer registryLock.RUnlock()
|
||||
// need to copy this value so we can release the lock
|
||||
commenters := []HRSCommenter{}
|
||||
for _, f := range commenterRegistry[typename] {
|
||||
commenters = append(commenters, f)
|
||||
}
|
||||
return commenters
|
||||
}
|
||||
|
||||
// Human Readable Serialization
|
||||
type hrsWriter struct {
|
||||
ind int
|
||||
@@ -190,6 +258,14 @@ func (w *hrsWriter) writeStruct(v Struct) {
|
||||
w.write(" ")
|
||||
}
|
||||
w.write("{")
|
||||
commenters := GetHRSCommenters(v.name)
|
||||
for _, commenter := range commenters {
|
||||
if comment := commenter.Comment(v); comment != "" {
|
||||
w.write(" // " + comment)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
w.indent()
|
||||
|
||||
if len(v.fieldNames) > 0 {
|
||||
|
||||
@@ -322,3 +322,37 @@ func TestWriteHumanReadableStructOptionalFields(t *testing.T) {
|
||||
StructField{"b", BoolType, true})
|
||||
assertWriteHRSEqual(t, "Struct S1 {\n a: Bool,\n b?: Bool,\n}", typ)
|
||||
}
|
||||
|
||||
type TestCommenter struct {
|
||||
prefix string
|
||||
testType *Type
|
||||
}
|
||||
|
||||
func (c TestCommenter) Comment(v Value) string {
|
||||
if !(v.typeOf().Equals(c.testType)) {
|
||||
return ""
|
||||
}
|
||||
return c.prefix + string(v.(Struct).Get("Name").(String))
|
||||
}
|
||||
|
||||
func TestRegisterCommenter(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
|
||||
tt := NewStruct("TestType1", StructData{"Name": String("abc-123")})
|
||||
nt := NewStruct("TestType2", StructData{"Name": String("abc-123")})
|
||||
|
||||
RegisterHRSCommenter("TestType1", "mylib1", TestCommenter{prefix: "MyTest: ", testType: tt.typeOf()})
|
||||
|
||||
s1 := EncodedValue(tt)
|
||||
a.True(strings.Contains(s1, "// MyTest: abc-123"))
|
||||
s1 = EncodedValue(nt)
|
||||
a.False(strings.Contains(s1, "// MyTest: abc-123"))
|
||||
|
||||
RegisterHRSCommenter("TestType1", "mylib1", TestCommenter{prefix: "MyTest2: ", testType: tt.typeOf()})
|
||||
s1 = EncodedValue(tt)
|
||||
a.True(strings.Contains(s1, "// MyTest2: abc-123"))
|
||||
|
||||
UnregisterHRSCommenter("TestType1", "mylib1")
|
||||
s1 = EncodedValue(tt)
|
||||
a.False(strings.Contains(s1, "// MyTest2: abc-123"))
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ import (
|
||||
"github.com/attic-labs/noms/go/types"
|
||||
)
|
||||
|
||||
const (
|
||||
datetypename = "DateTime"
|
||||
hrsEncodingName = "noms-datetime"
|
||||
)
|
||||
|
||||
// DateTime implements marshaling of time.Time to and from Noms.
|
||||
type DateTime struct {
|
||||
time.Time
|
||||
@@ -22,16 +27,20 @@ type DateTime struct {
|
||||
// DateTimeType is the Noms type used to represent date time objects in Noms.
|
||||
// The field secSinceEpoch may contain fractions in cases where seconds are
|
||||
// not sufficient.
|
||||
var DateTimeType = types.MakeStructTypeFromFields("DateTime", types.FieldMap{
|
||||
var DateTimeType = types.MakeStructTypeFromFields(datetypename, types.FieldMap{
|
||||
"secSinceEpoch": types.NumberType,
|
||||
})
|
||||
|
||||
var dateTimeTemplate = types.MakeStructTemplate("DateTime", []string{"secSinceEpoch"})
|
||||
var dateTimeTemplate = types.MakeStructTemplate(datetypename, []string{"secSinceEpoch"})
|
||||
|
||||
// Epoch is the unix Epoch. This time is very consistent,
|
||||
// which makes it useful for testing or checking for uninitialized values
|
||||
var Epoch = DateTime{time.Unix(0, 0)}
|
||||
|
||||
func init() {
|
||||
RegisterHRSCommenter(time.Local)
|
||||
}
|
||||
|
||||
// Now is an alias for a DateTime initialized with time.Now()
|
||||
func Now() DateTime {
|
||||
return DateTime{time.Now()}
|
||||
@@ -65,3 +74,21 @@ func (dt *DateTime) UnmarshalNoms(v types.Value) error {
|
||||
*dt = DateTime{time.Unix(int64(s), int64(frac*1e9))}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DateTimeCommenter struct {
|
||||
tz *time.Location
|
||||
}
|
||||
|
||||
func (c DateTimeCommenter) Comment(v types.Value) string {
|
||||
if !types.IsValueSubtypeOf(v, DateTimeType) {
|
||||
return ""
|
||||
}
|
||||
var dt DateTime
|
||||
marshal.MustUnmarshal(v, &dt)
|
||||
return dt.In(c.tz).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func RegisterHRSCommenter(tz *time.Location) {
|
||||
hrsCommenter := DateTimeCommenter{tz: tz}
|
||||
types.RegisterHRSCommenter(datetypename, hrsEncodingName, hrsCommenter)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -151,3 +152,24 @@ func TestEpoch(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(Epoch, DateTime{time.Unix(0, 0)})
|
||||
}
|
||||
|
||||
func TestHRSComment(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
vs := newTestValueStore()
|
||||
|
||||
dt := Now()
|
||||
mdt := marshal.MustMarshal(vs, dt)
|
||||
|
||||
exp := dt.Format(time.RFC3339)
|
||||
s1 := types.EncodedValue(mdt)
|
||||
a.True(strings.Contains(s1, "{ // "+exp))
|
||||
|
||||
RegisterHRSCommenter(time.UTC)
|
||||
exp = dt.In(time.UTC).Format((time.RFC3339))
|
||||
s1 = types.EncodedValue(mdt)
|
||||
a.True(strings.Contains(s1, "{ // "+exp))
|
||||
|
||||
types.UnregisterHRSCommenter(datetypename, hrsEncodingName)
|
||||
s1 = types.EncodedValue(mdt)
|
||||
a.False(strings.Contains(s1, "{ // 20"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user