1
0
mirror of https://github.com/schollz/cowyo.git synced 2023-08-10 21:13:00 +03:00
cowyo/vendor/github.com/schollz/versionedtext/versionedtext.go
2017-10-03 14:43:55 -04:00

263 lines
6.1 KiB
Go

package versionedtext
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/sergi/go-diff/diffmatchpatch"
)
// VersionedText is the main container for the diff functions
type VersionedText struct {
CurrentText string
Diffs map[int64]string
}
// NewVersionedText returns a new VersionedText object
func NewVersionedText(text string) VersionedText {
data := VersionedText{CurrentText: "", Diffs: make(map[int64]string)}
data.Update(text)
return data
}
// diffRebuildtexts will use the patches to rebuild the text
func (vt *VersionedText) diffRebuildtexts(Diffs []diffmatchpatch.Diff) []string {
text := []string{"", ""}
for _, diff := range Diffs {
if diff.Type != diffmatchpatch.DiffInsert {
text[0] += diff.Text
}
if diff.Type != diffmatchpatch.DiffDelete {
text[1] += diff.Text
}
}
return text
}
// rebuildTextsToDiffN will use patches to build the text to a certain point
func (vt *VersionedText) rebuildTextsToDiffN(timestamp int64, snapshots []int64) (string, error) {
dmp := diffmatchpatch.New()
lastText := ""
for _, snapshot := range snapshots {
diff := vt.Diffs[snapshot]
seq1, _ := dmp.DiffFromDelta(lastText, diff)
textsLinemode := vt.diffRebuildtexts(seq1)
rebuilt := textsLinemode[len(textsLinemode)-1]
if snapshot == timestamp {
return rebuilt, nil
}
lastText = rebuilt
}
return "", fmt.Errorf("Could not rebuild from Diffs")
}
// GetCurrent returns the latest version
func (vt *VersionedText) GetCurrent() string {
return vt.CurrentText
}
// NumEdits returns the number of edits
func (vt *VersionedText) NumEdits() int {
return len(vt.Diffs)
}
// LastEditTime returns the last time it was edited
func (vt *VersionedText) LastEditTime() int64 {
max := int64(0)
for key := range vt.Diffs {
if key > max {
max = key
}
}
return max
}
// Update adds a new version to the current versions
func (vt *VersionedText) Update(newText string) {
// check for changes
if vt.CurrentText == newText {
return
}
dmp := diffmatchpatch.New()
delta := dmp.DiffToDelta(dmp.DiffMain(vt.CurrentText, newText, true))
vt.CurrentText = newText
now := time.Now().UnixNano()
vt.Diffs[now] = delta
}
// GetSnapshots returns a sorted list of integers which
// represent timestamps for each snapshot
func (vt *VersionedText) GetSnapshots() []int64 {
keys := make([]int64, 0, len(vt.Diffs))
for k := range vt.Diffs {
keys = append(keys, k)
}
// SORT KEYS
keys = mergeSortInt64(keys)
return keys
}
// GetMajorSnapshots returns a sorted list of integers which
// represent timestamps for each snapshot, where snapshots
// must be at least the number of specified seconds apart.
func (vt *VersionedText) GetMajorSnapshots(seconds int64) []int64 {
keys := vt.GetSnapshots()
newKeys := make([]int64, len(keys))
newKeysI := 0
for i, key := range keys {
if i == 0 && len(keys) > 1 {
continue
}
if i == len(keys)-1 {
newKeys[newKeysI] = key
newKeysI++
continue
}
if key-keys[i-1] > seconds*1000000000 {
newKeys[newKeysI] = key
newKeysI++
}
}
return newKeys[0:newKeysI]
}
// GetMajorSnapshotsAndChangeSums returns the major snapshots timestamps and the changesums
// as two arrays
func (vt *VersionedText) GetMajorSnapshotsAndChangeSums(seconds int64) ([]int64, []int) {
keys := vt.GetSnapshots()
changeSums := vt.GetChangeSums()
majorChangeSums := make([]int, len(keys))
newKeys := make([]int64, len(keys))
newKeysI := 0
cumulativeSum := 0
for i, key := range keys {
cumulativeSum = cumulativeSum + changeSums[i]
if i == 0 && len(keys) > 1 {
continue
}
if i == len(keys)-1 {
newKeys[newKeysI] = key
majorChangeSums[newKeysI] = cumulativeSum
cumulativeSum = 0
newKeysI++
continue
}
if key-keys[i-1] > seconds*1000000000 {
majorChangeSums[newKeysI] = cumulativeSum
newKeys[newKeysI] = key
cumulativeSum = 0
newKeysI++
}
}
return newKeys[0:newKeysI], majorChangeSums[0:newKeysI]
}
// GetChangeSums returns the length of each change
func (vt *VersionedText) GetChangeSums() []int {
keys := vt.GetSnapshots()
changeSums := make([]int, len(keys))
for i, key := range keys {
changeSums[i] = 0
for _, val := range strings.Split(vt.Diffs[key], "\t") {
if val[0:1] == "-" {
a, _ := strconv.Atoi(val[1:])
changeSums[i] = changeSums[i] - a
} else if val[0:1] == "+" {
changeSums[i] = changeSums[i] + len(val[1:])
}
}
}
return changeSums
}
// GetPreviousByTimestamp uses the diffs to rebuild to the specified snapshot
func (vt *VersionedText) GetPreviousByTimestamp(timestamp int64) (string, error) {
// check inputs
if 0 > timestamp {
return "", fmt.Errorf("Timestamps most be positive integer")
}
// get change snapshot
snapshots := vt.GetSnapshots()
// default to first value
ts := snapshots[0]
// find timestamp
for _, snapshot := range snapshots {
if timestamp >= snapshot && ts < snapshot {
ts = snapshot
}
}
// use timestamp to find value
oldValue, err := vt.rebuildTextsToDiffN(ts, snapshots)
return oldValue, err
}
// GetPreviousByIndex returns a snapshot based on the index
func (vt *VersionedText) GetPreviousByIndex(idx int) (string, error) {
if vt.NumEdits() == 0 {
return vt.GetCurrent(), nil
}
// check inputs
if 0 > idx {
return "", fmt.Errorf("Index most be positive integer")
}
// get change snapshots
snapshots := vt.GetSnapshots()
// if index greater than length of snapshot
// default to last snapshot
if idx > len(snapshots)-1 {
idx = len(snapshots) - 1
}
// use index to find timestamp
ts := snapshots[idx]
// use timestamp to find value
oldValue, err := vt.rebuildTextsToDiffN(ts, snapshots)
return oldValue, err
}
func mergeInt64(l, r []int64) []int64 {
ret := make([]int64, 0, len(l)+len(r))
for len(l) > 0 || len(r) > 0 {
if len(l) == 0 {
return append(ret, r...)
}
if len(r) == 0 {
return append(ret, l...)
}
if l[0] <= r[0] {
ret = append(ret, l[0])
l = l[1:]
} else {
ret = append(ret, r[0])
r = r[1:]
}
}
return ret
}
func mergeSortInt64(s []int64) []int64 {
if len(s) <= 1 {
return s
}
n := len(s) / 2
l := mergeSortInt64(s[:n])
r := mergeSortInt64(s[n:])
return mergeInt64(l, r)
}