-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed tests, added package.json and generally made library work
- Loading branch information
Showing
10 changed files
with
731 additions
and
200 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// These methods let you build a transform function from a transformComponent | ||
// function for OT types like JSON0 in which operations are lists of components | ||
// and transforming them requires N^2 work. I find it kind of nasty that I need | ||
// this, but I'm not really sure what a better solution is. Maybe I should do | ||
// this automatically to types that don't have a compose function defined. | ||
|
||
// Add transform and transformX functions for an OT type which has | ||
// transformComponent defined. transformComponent(destination array, | ||
// component, other component, side) | ||
module.exports = bootstrapTransform | ||
function bootstrapTransform(type, transformComponent, checkValidOp, append) { | ||
var transformComponentX = function(left, right, destLeft, destRight) { | ||
transformComponent(destLeft, left, right, 'left'); | ||
transformComponent(destRight, right, left, 'right'); | ||
}; | ||
|
||
var transformX = type.transformX = function(leftOp, rightOp) { | ||
checkValidOp(leftOp); | ||
checkValidOp(rightOp); | ||
var newRightOp = []; | ||
|
||
for (var i = 0; i < rightOp.length; i++) { | ||
var rightComponent = rightOp[i]; | ||
|
||
// Generate newLeftOp by composing leftOp by rightComponent | ||
var newLeftOp = []; | ||
var k = 0; | ||
while (k < leftOp.length) { | ||
var nextC = []; | ||
transformComponentX(leftOp[k], rightComponent, newLeftOp, nextC); | ||
k++; | ||
|
||
if (nextC.length === 1) { | ||
rightComponent = nextC[0]; | ||
} else if (nextC.length === 0) { | ||
for (var j = k; j < leftOp.length; j++) { | ||
append(newLeftOp, leftOp[j]); | ||
} | ||
rightComponent = null; | ||
break; | ||
} else { | ||
// Recurse. | ||
var pair = transformX(leftOp.slice(k), nextC); | ||
for (var l = 0; l < pair[0].length; l++) { | ||
append(newLeftOp, pair[0][l]); | ||
} | ||
for (var r = 0; r < pair[1].length; r++) { | ||
append(newRightOp, pair[1][r]); | ||
} | ||
rightComponent = null; | ||
break; | ||
} | ||
} | ||
|
||
if (rightComponent != null) { | ||
append(newRightOp, rightComponent); | ||
} | ||
leftOp = newLeftOp; | ||
} | ||
return [leftOp, newRightOp]; | ||
}; | ||
|
||
// Transforms op with specified type ('left' or 'right') by otherOp. | ||
type.transform = function(op, otherOp, type) { | ||
if (!(type === 'left' || type === 'right')) | ||
throw new Error("type must be 'left' or 'right'"); | ||
|
||
if (otherOp.length === 0) return op; | ||
|
||
if (op.length === 1 && otherOp.length === 1) | ||
return transformComponent([], op[0], otherOp[0], type); | ||
|
||
if (type === 'left') | ||
return transformX(op, otherOp)[0]; | ||
else | ||
return transformX(otherOp, op)[1]; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Only the JSON type is exported, because the text type is deprecated | ||
// otherwise. (If you want to use it somewhere, you're welcome to pull it out | ||
// into a separate module that json0 can depend on). | ||
|
||
module.exports = { | ||
type: require('./json0') | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "ot-json0", | ||
"version": "1.0.0", | ||
"description": "JSON OT type", | ||
"main": "lib/index.js", | ||
"directories": { | ||
"test": "test" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"ot-fuzzer": "^1.0.0", | ||
"mocha": "^1.20.1" | ||
}, | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/ottypes/json0" | ||
}, | ||
"keywords": [ | ||
"ot", | ||
"json", | ||
"sharejs", | ||
"operational-transformation" | ||
], | ||
"author": "Joseph Gentle <[email protected]>", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/ottypes/json0/issues" | ||
}, | ||
"homepage": "https://github.com/ottypes/json0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
json0 = require '../lib/json0' | ||
{randomInt, randomReal, randomWord} = require 'ot-fuzzer' | ||
|
||
# This is an awful function to clone a document snapshot for use by the random | ||
# op generator. .. Since we don't want to corrupt the original object with | ||
# the changes the op generator will make. | ||
clone = (o) -> JSON.parse(JSON.stringify(o)) | ||
|
||
randomKey = (obj) -> | ||
if Array.isArray(obj) | ||
if obj.length == 0 | ||
undefined | ||
else | ||
randomInt obj.length | ||
else | ||
count = 0 | ||
|
||
for key of obj | ||
result = key if randomReal() < 1/++count | ||
result | ||
|
||
# Generate a random new key for a value in obj. | ||
# obj must be an Object. | ||
randomNewKey = (obj) -> | ||
# There's no do-while loop in coffeescript. | ||
key = randomWord() | ||
key = randomWord() while obj[key] != undefined | ||
key | ||
|
||
# Generate a random object | ||
randomThing = -> | ||
switch randomInt 6 | ||
when 0 then null | ||
when 1 then '' | ||
when 2 then randomWord() | ||
when 3 | ||
obj = {} | ||
obj[randomNewKey(obj)] = randomThing() for [1..randomInt(5)] | ||
obj | ||
when 4 then (randomThing() for [1..randomInt(5)]) | ||
when 5 then randomInt(50) | ||
|
||
# Pick a random path to something in the object. | ||
randomPath = (data) -> | ||
path = [] | ||
|
||
while randomReal() > 0.85 and typeof data == 'object' | ||
key = randomKey data | ||
break unless key? | ||
|
||
path.push key | ||
data = data[key] | ||
|
||
path | ||
|
||
|
||
module.exports = genRandomOp = (data) -> | ||
pct = 0.95 | ||
|
||
container = data: clone data | ||
|
||
op = while randomReal() < pct | ||
pct *= 0.6 | ||
|
||
# Pick a random object in the document operate on. | ||
path = randomPath(container['data']) | ||
|
||
# parent = the container for the operand. parent[key] contains the operand. | ||
parent = container | ||
key = 'data' | ||
for p in path | ||
parent = parent[key] | ||
key = p | ||
operand = parent[key] | ||
|
||
if randomReal() < 0.4 and parent != container and Array.isArray(parent) | ||
# List move | ||
newIndex = randomInt parent.length | ||
|
||
# Remove the element from its current position in the list | ||
parent.splice key, 1 | ||
# Insert it in the new position. | ||
parent.splice newIndex, 0, operand | ||
|
||
{p:path, lm:newIndex} | ||
|
||
else if randomReal() < 0.3 or operand == null | ||
# Replace | ||
|
||
newValue = randomThing() | ||
parent[key] = newValue | ||
|
||
if Array.isArray(parent) | ||
{p:path, ld:operand, li:clone(newValue)} | ||
else | ||
{p:path, od:operand, oi:clone(newValue)} | ||
|
||
else if typeof operand == 'string' | ||
# String. This code is adapted from the text op generator. | ||
|
||
if randomReal() > 0.5 or operand.length == 0 | ||
# Insert | ||
pos = randomInt(operand.length + 1) | ||
str = randomWord() + ' ' | ||
|
||
path.push pos | ||
parent[key] = operand[...pos] + str + operand[pos..] | ||
c = {p:path, si:str} | ||
else | ||
# Delete | ||
pos = randomInt(operand.length) | ||
length = Math.min(randomInt(4), operand.length - pos) | ||
str = operand[pos...(pos + length)] | ||
|
||
path.push pos | ||
parent[key] = operand[...pos] + operand[pos + length..] | ||
c = {p:path, sd:str} | ||
|
||
if json0._testStringSubtype | ||
# Subtype | ||
subOp = {p:path.pop()} | ||
if c.si? | ||
subOp.i = c.si | ||
else | ||
subOp.d = c.sd | ||
|
||
c = {p:path, t:'text0', o:[subOp]} | ||
|
||
c | ||
|
||
else if typeof operand == 'number' | ||
# Number | ||
inc = randomInt(10) - 3 | ||
parent[key] += inc | ||
{p:path, na:inc} | ||
|
||
else if Array.isArray(operand) | ||
# Array. Replace is covered above, so we'll just randomly insert or delete. | ||
# This code looks remarkably similar to string insert, above. | ||
|
||
if randomReal() > 0.5 or operand.length == 0 | ||
# Insert | ||
pos = randomInt(operand.length + 1) | ||
obj = randomThing() | ||
|
||
path.push pos | ||
operand.splice pos, 0, obj | ||
{p:path, li:clone(obj)} | ||
else | ||
# Delete | ||
pos = randomInt operand.length | ||
obj = operand[pos] | ||
|
||
path.push pos | ||
operand.splice pos, 1 | ||
{p:path, ld:clone(obj)} | ||
else | ||
# Object | ||
k = randomKey(operand) | ||
|
||
if randomReal() > 0.5 or not k? | ||
# Insert | ||
k = randomNewKey(operand) | ||
obj = randomThing() | ||
|
||
path.push k | ||
operand[k] = obj | ||
{p:path, oi:clone(obj)} | ||
else | ||
obj = operand[k] | ||
|
||
path.push k | ||
delete operand[k] | ||
{p:path, od:clone(obj)} | ||
|
||
[op, container.data] |
Oops, something went wrong.