-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.js
More file actions
231 lines (211 loc) · 9.59 KB
/
utils.js
File metadata and controls
231 lines (211 loc) · 9.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
var spawn = require('child_process').spawn,
path = require('path'),
fs = require('fs'),
q = require('q'),
mustache = require('mustache'),
crypto = require('crypto'),
settings = require('../../settings'),
utils
utils = module.exports = {
/**
* Converts events in dash-delimited format to properties of the form onEventName
* @param {Array} prefix the prefix of the propified events. Default: on
* @param {Array} events the events to propify
* @return {Object} a hash from onEventName to event-name strings
*/
events2Props: function( prefixesArg, eventsArg ) {
var prefixes = eventsArg ? prefixesArg : [ 'on' ],
events = eventsArg ? eventsArg : prefixesArg
return events.reduce( function( propHash, event ) {
var eventPropSuffix = event.split('-').map( function( eventIdentifier ) {
return eventIdentifier.slice( 0, 1 ).toUpperCase() + eventIdentifier.slice( 1 )
}).join('')
prefixes.map( function( prefix ) {
var eventProp = prefix + eventPropSuffix
propHash[ eventProp ] = event
})
return propHash
}, {} )
},
/**
* Executes a git command in a specified repo
* @param {String} repo the path to the repository in which to execute the command
* @param {String} cmd the git command to run
* @param {String|Array} args the arguments to pass to the command
* @return {Promise} a promise on the completion of the command
*/
git: function( repo, cmd, args ) {
return q.nfcall( fs.stat, repo )
.then( function() {
var done = q.defer(),
cmdArgs = ( args instanceof Array ? args : args.trim().split(' ') ),
unused = console.log('running git', cmd, cmdArgs, repo),
git = spawn( 'git', [ cmd ].concat( cmdArgs ), { cwd: repo } ),
output = '',
errOutput = ''
git.stderr.on( 'data', function( data ) { errOutput += data.toString() })
git.stdout.on( 'data', function( data ) { output += data.toString() })
git.on( 'close', function( code ) {
if ( code !== 0 ) {
done.reject( Error( errOutput ) )
} else {
done.resolve( output )
}
})
return done.promise
})
},
/**
* Recursively makes directories, a la `mkdir -p`
* @param {String} dirPath the path to the directory to make
* @param {String} base the directory in which to create the tree. Default: /
* @return {Promise}
*/
mkdirp: function( dirPath, base ) {
var rel = base || '/',
dirs = Array.isArray( dirPath ) ? dirPath : dirPath.split( path.sep ),
theDir = path.join( rel, dirs.shift() || '' ),
mkChildDir = dirs.length === 0 ? function() {} : utils.mkdirp.bind( null, dirs, theDir )
return q.nfcall( fs.stat, theDir )
.then( mkChildDir, function( e ) {
if ( e.code === 'ENOENT' ) {
return q.nfcall( fs.mkdir, theDir ).then( mkChildDir )
.catch( function( e ) { if ( e.code !== 'EEXIST' ) { throw e } } )
}
throw e
})
},
/**
* Templates a file and writes it out
* @param {String} src the path to the source file/dir. Leave blank to create new file.
* @param {String} dest the path to the output file/dir (only file if `src` is unspecified)
* @param {Object} template a template object a la mustache
* @return {Promise}
*/
writeOut: function( src, dest, template ) {
if ( !src ) {
return utils.mkdirp( path.dirname( dest ) )
.then( q.nfcall.bind( null, fs.writeFile, dest, '' ) )
}
return q.nfcall( fs.stat, src )
.then( function( stats ) {
var srcPath = path.join.bind( null, src ),
destPath = path.join.bind( null, dest )
if ( stats.isDirectory() ) { // recursiely template and write out directory
return q.nfcall( fs.mkdir, dest ) // make the dest directory
.then( function() {
return q.nfcall( fs.readdir, src ) // recurse into the src dir
})
.then( function( dirContents ) {
return q.all( dirContents.map( function( file ) {
return utils.writeOut( srcPath( file ), destPath( file ), template )
}) )
})
} else {
return q.nfcall( fs.readFile, src, { encoding: 'utf8' } )
.then( function( file ) {
var fileTemplate = typeof template === 'function' ? template( src ) : template,
templated = mustache.render( file, fileTemplate )
return utils.mkdirp( path.dirname( dest ) )
.then( q.nfcall.bind( null, fs.writeFile, dest, templated ) )
})
}
})
},
/**
* Adds the specified files (possibly templated) to the given repo and commits them
* @param {String} repo the path to the repository. Dest files are relative to the repo root
* @param {String} srcBase path to which src files paths are relative. Default: /
* @param {Object} spec the specification for the commit
* spec: {
* msg: String,
* author: String, // Default: GitStream <gitstream@csail.mit.edu>
* date: Date, // Default: current Date
* files: Array[String|Object]
* // if String, copies from src to dest. Assumes same directory structure
* // if Object, refers to a fileSpec, as described below
* }
*
* fileSpec: {
* src: String, // path to file relative to `srcBase`. Leave blank when creating new files.
* // can be a directory. will be recursively templated/written out
* dest: String, // path to destination relative to `repo`. Will recursively create dirs.
* template: Object|Function, // a Mustache template object or object-generating function
* // the object-generating function receives the source path. noop if src is undefined
* }
* @return {Promise} a promise on the completion of the commands
*/
addCommit: function( repo, srcBase, spec ) {
var srcPath = path.join.bind( null, arguments.length < 3 ? '/' : srcBase ),
destPath = path.join.bind( null, repo ),
commitAuthor = spec.author || 'GitStream <gitstream@csail.mit.edu>',
commitDate = ( spec.date || new Date() ).toISOString(),
commitMsg = spec.msg.replace( /'/g, '\\"' ),
filesToStage = []
return q.all( spec.files.map( function( fileSpec ) {
var srcArg = typeof fileSpec === 'string' ? fileSpec : fileSpec.src,
src = srcArg ? srcPath( srcArg ) : '',
dest = typeof fileSpec === 'string' ? fileSpec : fileSpec.dest || fileSpec.src,
template = typeof fileSpec === 'string' ? {} : fileSpec.template
filesToStage.push( ':' + dest )
return utils.writeOut( src, destPath( dest ), template )
}) )
.then( function() {
if (filesToStage.length == 0) return Promise.resolve();
else return utils.git( repo, 'add', filesToStage.join(' ') )
})
.then( function() {
return utils.git( repo, 'commit', [
'-m', commitMsg,
'--author="' + commitAuthor + '"',
'--date=' + commitDate
])
})
},
// errorCallback: Error -> void, called only when there is an error;
// may be called more than once
exportToOmnivore: function( userId, exerciseName, errorCallback ) {
try {
if (!exerciseName) {
return;
}
// omnivoreEndpoints: Array<{url:"https://omni.mit.edu/<course>/<sem>/api/v2/multiadd",
// key:"/classes/..."}>
var omnivoreEndpoints = settings.omnivoreEndpointsForExercise(exerciseName);
console.log(userId, 'completed', omnivoreEndpoints);
omnivoreEndpoints.forEach(function( omnivoreEndpoint ) {
var record = {
username: userId,
key: omnivoreEndpoint.key,
ts: new Date(),
value: true, // filler
}
var input = [ record ]
var sign = crypto.createSign('RSA-SHA256')
sign.update(JSON.stringify(input))
var privateKey = fs.readFileSync('gitstream.pem');
var signature = sign.sign(privateKey, 'base64')
const url = omnivoreEndpoint.url;
const headers = {
'Content-Type': 'application/json',
'X-Omnivore-Signed': 'gitstream ' + signature
};
const body = JSON.stringify(input);
fetch(url, {
method: 'POST',
headers,
body
}).then(response => {
console.log('Omnivore responded', response.status, response.statusText);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
}).catch(error => {
errorCallback(error);
});
});
} catch (e) {
return errorCallback(e);
}
}
}