Skip to content

Commit 2c71b10

Browse files
committed
feat: initial commit
0 parents  commit 2c71b10

File tree

12 files changed

+15469
-0
lines changed

12 files changed

+15469
-0
lines changed

.gitignore

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
18+
# Directory for instrumented libs generated by jscoverage/JSCover
19+
lib-cov
20+
21+
# Coverage directory used by tools like istanbul
22+
coverage
23+
*.lcov
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (https://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# TypeScript v1 declaration files
45+
typings/
46+
47+
# TypeScript cache
48+
*.tsbuildinfo
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Microbundle cache
57+
.rpt2_cache/
58+
.rts2_cache_cjs/
59+
.rts2_cache_es/
60+
.rts2_cache_umd/
61+
62+
# Optional REPL history
63+
.node_repl_history
64+
65+
# Output of 'npm pack'
66+
*.tgz
67+
68+
# Yarn Integrity file
69+
.yarn-integrity
70+
71+
# dotenv environment variables file
72+
.env
73+
.env.test
74+
75+
# parcel-bundler cache (https://parceljs.org/)
76+
.cache
77+
78+
# Next.js build output
79+
.next
80+
81+
# Nuxt.js build / generate output
82+
.nuxt
83+
dist
84+
85+
# Gatsby files
86+
.cache/
87+
# Comment in the public line in if your project uses Gatsby and *not* Next.js
88+
# https://nextjs.org/blog/next-9-1#public-directory-support
89+
# public
90+
91+
# vuepress build output
92+
.vuepress/dist
93+
94+
# Serverless directories
95+
.serverless/
96+
97+
# FuseBox cache
98+
.fusebox/
99+
100+
# DynamoDB Local files
101+
.dynamodb/
102+
103+
# TernJS port file
104+
.tern-port

.travis.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
language: node_js
2+
3+
node_js:
4+
- node
5+
- lts/*
6+
7+
jobs:
8+
include:
9+
# Define the release stage that runs semantic-release
10+
- stage: release
11+
node_js: lts/*
12+
# Advanced: optionally overwrite your default `script` step to skip the tests
13+
# script: skip
14+
deploy:
15+
provider: script
16+
skip_cleanup: true
17+
script:
18+
- npx semantic-release

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# mx2json
2+
3+
Create a JSON representation of a Machinations Google Doc
4+
5+
> ⚠️ This is WIP. You’ll need quite a few things for this:
6+
> 1. A Google Docs API Key with `spreadsheets.readonly` permissions
7+
> 2. The ID of your Google Sheet
8+
> 3. use 1 & 2 to create the API URL to your Google Doc, something like `https://sheets.googleapis.com/v4/spreadsheets/18J84rwqwC9AuyabZAK9d4JM6cUZn8jBnhZoT0NHMTp8/values/A%3AZ?key=…`
9+
4. `curl` this, optionally store the result in a file
10+
11+
## CLI
12+
13+
`mx2json` always reads from `stdin`. Use the pipe (`|`) or redirect (`<`) operators.
14+
15+
##### Read from a file
16+
`$ mx2json < curl https://....`<br/>
17+
18+
##### Read from a URL
19+
`$ curl https://.... | mx2json`
20+
21+
## API
22+
23+
`mx2json(doc, toJson)`
24+
25+
**`doc`** is the Machinations Google Sheet, either in JSON or already parsed Object.
26+
27+
**`toJson`** `boolean` or `function`. Set to `true` to get a JSON. Pass a `function`, to get the return value of that function.
28+
29+
```js
30+
const mx2object = require('mx2json')
31+
const model = mx2object(myDoc)
32+
const json = mx2object(myDoc, true)
33+
const custom = mx2object(mDoc, doc => /* whatever */)
34+
```
35+
36+
# Example Result
37+
38+
```json
39+
{
40+
"diagramProperties": {
41+
"name": "Untitled",
42+
"url": "https://my.machinations.io/diagram/22179",
43+
"owner": "Andreas Pizsa",
44+
"creationDate": "10/22/2019",
45+
"lastChange": "10/31/2019",
46+
"timeInterval": 1,
47+
"timeStepsLimit": 100,
48+
"numberOfRunsBatch": 10,
49+
"numberOfRunsTotal": 20
50+
},
51+
"globalVariables": [
52+
{
53+
"id": 1,
54+
"name": "Dice",
55+
"value": "D6"
56+
},
57+
{
58+
"id": 2,
59+
"name": "Skill"
60+
},
61+
{
62+
"id": 3,
63+
"name": "Multiplayer"
64+
},
65+
{
66+
"id": 4,
67+
"name": "Strategy"
68+
}
69+
],
70+
"pools": [
71+
{
72+
"id": 11,
73+
"label": "",
74+
"geometry": "{\"_attributes\":{\"x\":\"360\",\"y\":\"110\",\"width\":\"60\",\"height\":\"60\",\"TRANSLATE_CONTROL_POINTS\":\"1\",\"relative\":\"0\",\"as\":\"geometry\"},\"mxPoint\":{\"_attributes\":{\"x\":\"0\",\"y\":\"40\",\"as\":\"offset\"}}}",
75+
"style": "shape=pool-shape;whiteSpace=wrap;html=1;strokeWidth=2;aspect=fixed;resizable=0;fontSize=16;fontColor=#000000;strokeColor=#000000;",
76+
"activation": "passive",
77+
"activationMode": "pull-any",
78+
"resources": 10,
79+
"resourcesColor": "Black",
80+
"capacityLimit": -1,
81+
"capacityDisplay": 25,
82+
"overflow": "block",
83+
"showInChart": 0
84+
},
85+
{
86+
"id": 15,
87+
"label": "",
88+
"geometry": "{\"_attributes\":{\"x\":\"150\",\"y\":\"110\",\"width\":\"60\",\"height\":\"60\",\"TRANSLATE_CONTROL_POINTS\":\"1\",\"relative\":\"0\",\"as\":\"geometry\"},\"mxPoint\":{\"_attributes\":{\"x\":\"0\",\"y\":\"40\",\"as\":\"offset\"}}}",
89+
"style": "shape=pool-shape;whiteSpace=wrap;html=1;strokeWidth=2;aspect=fixed;resizable=0;fontSize=16;fontColor=#000000;strokeColor=#000000;",
90+
"activation": "interactive",
91+
"activationMode": "push-any",
92+
"resources": 5,
93+
"resourcesColor": "Black",
94+
"capacityLimit": -1,
95+
"capacityDisplay": 25,
96+
"overflow": "block",
97+
"showInChart": 0
98+
}
99+
],
100+
"resourceConnections": [
101+
{
102+
"id": 14,
103+
"label": 1,
104+
"geometry": "{\"_attributes\":{\"x\":\"0\",\"y\":\"0\",\"width\":\"60\",\"height\":\"60\",\"relative\":\"1\",\"TRANSLATE_CONTROL_POINTS\":\"1\",\"as\":\"geometry\"},\"mxPoint\":[{\"_attributes\":{\"x\":\"240\",\"y\":\"148.33333333333337\",\"as\":\"sourcePoint\"}},{\"_attributes\":{\"x\":\"324.8528137423857\",\"y\":\"150\",\"as\":\"targetPoint\"}},{\"_attributes\":{\"x\":\"20\",\"y\":\"10\",\"as\":\"offset\"}}]}",
105+
"style": "shape=resource-connection;endArrow=classic;html=1;strokeWidth=2;fontSize=16;fontColor=#000000;strokeColor=#000000;",
106+
"source": 15,
107+
"target": 11,
108+
"transfer": "interval-based",
109+
"colorCoding": 0,
110+
"colorCodingColor": "Orange",
111+
"shuffleSource": 0,
112+
"limitsMinimum": -9999,
113+
"limitsMaximum": 9999
114+
}
115+
],
116+
"stateConnections": [
117+
{
118+
"id": 16,
119+
"label": ">0",
120+
"geometry": "{\"_attributes\":{\"x\":\"0\",\"y\":\"0\",\"width\":\"60\",\"height\":\"60\",\"relative\":\"1\",\"TRANSLATE_CONTROL_POINTS\":\"1\",\"as\":\"geometry\"},\"mxPoint\":[{\"_attributes\":{\"x\":\"420\",\"y\":\"200\",\"as\":\"sourcePoint\"}},{\"_attributes\":{\"x\":\"504.8528137423857\",\"y\":\"140\",\"as\":\"targetPoint\"}},{\"_attributes\":{\"x\":\"20\",\"y\":\"10\",\"as\":\"offset\"}}],\"Array\":{\"_attributes\":{\"as\":\"points\"},\"mxPoint\":{\"_attributes\":{\"x\":\"350\",\"y\":\"290\"}}}}",
121+
"style": "shape=state-connection;endArrow=classic;dashed=1;dashPattern=4 3;html=1;strokeWidth=2;fontSize=16;fontColor=#000000;strokeColor=#000000;",
122+
"source": 11,
123+
"target": 14,
124+
"colorCoding": 0,
125+
"colorCodingColor": "Black"
126+
}
127+
]
128+
}
129+
```

api.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// GET https://sheets.googleapis.com/v4/spreadsheets/18J84rwqwC9AuyabZAK9d4JM6cUZn8jBnhZoT0NHMTp8/values/Sheet1!A1:D5
2+
3+
const {camelCase} = require('change-case')
4+
const flatMap = require('lodash.flatmap')
5+
6+
/*------------------------------------------------------------------------------
7+
Utility functions
8+
------------------------------------------------------------------------------*/
9+
const {toPairs, fromPairs, pipe} = require('./utils')
10+
11+
/*------------------------------------------------------------------------------
12+
objectFromMxGoogleDoc
13+
------------------------------------------------------------------------------*/
14+
function objectFromMxGoogleDoc({values}) {
15+
let section
16+
let expectColumnNames = false
17+
let columnNames
18+
return values.reduce((result, row) => {
19+
if (row.length === 0) {
20+
return result
21+
}
22+
23+
if (row.length === 1) {
24+
section = camelCase(row[0])
25+
expectColumnNames = true
26+
return {
27+
...result,
28+
[section]: []
29+
}
30+
}
31+
32+
if (section === 'diagramProperties') {
33+
result[section].push({
34+
[camelCase(row[1])]: row[2]
35+
})
36+
return result
37+
}
38+
39+
if (expectColumnNames) {
40+
expectColumnNames = false
41+
columnNames = row.filter(col => col.length)
42+
return result
43+
}
44+
45+
result[section].push(
46+
keysAndValuesToObject(columnNames, row)
47+
)
48+
49+
return result
50+
}, {})
51+
}
52+
53+
const cleanupDiagramProperties = input => ({
54+
...input,
55+
diagramProperties: fromPairs(flatMap(input.diagramProperties, toPairs))
56+
})
57+
58+
function keysAndValuesToObject(keys, values) {
59+
return keys.reduce((result, key, index) => ({
60+
...result,
61+
[camelCase(key)]: values[index]
62+
}), {})
63+
}
64+
65+
function deepNumericValuesToIntegers(arg) {
66+
if (Array.isArray(arg)) {
67+
return arg.map(deepNumericValuesToIntegers)
68+
}
69+
70+
if (typeof arg !== 'object') {
71+
return /^-?\d+$/.test(arg) ? parseInt(arg, 10) : arg
72+
}
73+
74+
return Object
75+
.entries(arg)
76+
.reduce((result, [key, value]) => ({
77+
...result,
78+
[key]: deepNumericValuesToIntegers(value)
79+
}), {})
80+
}
81+
82+
const mxToObject = (doc, toJson = false) => {
83+
const fromJson = typeof doc === 'string'
84+
? JSON.parse
85+
: x => x
86+
87+
toJson = (typeof toJson === 'function' && toJson)
88+
|| toJson
89+
? arg => JSON.stringify(arg, null, 2)
90+
: x => x
91+
92+
return pipe(
93+
fromJson,
94+
objectFromMxGoogleDoc,
95+
cleanupDiagramProperties,
96+
deepNumericValuesToIntegers,
97+
toJson
98+
)(doc)
99+
}
100+
101+
module.exports = mxToObject

cli.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env node
2+
3+
const mxToObject = require('./api')
4+
const {pipe} = require('./utils')
5+
6+
// eslint-disable-next-line no-unused-expressions
7+
require('yargs')
8+
.scriptName('mx2json')
9+
.command(
10+
'$0',
11+
'Convert Machinations Google Doc file to JSON',
12+
{},
13+
async argv => {
14+
if (/^.*?:.*/.test(argv.file)) {
15+
console.error(`use\n curl "${argv.file}" | ${argv.$0}\nto use URLs`)
16+
return
17+
}
18+
19+
const input = argv.file
20+
? await require('fs').promises.readFile(argv.file, 'utf8')
21+
: await require('get-stdin')()
22+
23+
pipe(
24+
mxToObject,
25+
doc => JSON.stringify(doc, null, 2),
26+
console.log.bind(console)
27+
)(input)
28+
})
29+
.argv

client_id.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"installed":{"client_id":"738175170949-mkv82e49f610n0tt7psc33557lqghk1j.apps.googleusercontent.com","project_id":"machinatics","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"zuOH3f393pol78zggDsVI-Mp","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}

0 commit comments

Comments
 (0)