-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathutil.coffee
269 lines (210 loc) · 7.03 KB
/
util.coffee
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
fileSeparator = "/"
normalizePath = (path) ->
path.replace(/\/+/g, fileSeparator) # /// -> /
.replace(/\/[^/]*\/\.\./g, "") # /base/something/.. -> /base
.replace(/\/\.\//g, fileSeparator) # /base/. -> /base
# NOTE: Allows paths like '../..' to go above the base path
absolutizePath = (base, relativePath) ->
normalizePath "/#{base}/#{relativePath}"
makeScript = (src) ->
"<script src=#{JSON.stringify(src)}><\/script>"
dependencyScripts = (remoteDependencies=[]) ->
remoteDependencies.map(makeScript).join("\n")
metaTag = (name, content) ->
"<meta name=#{JSON.stringify(name)} content=#{JSON.stringify(content)}>"
htmlForPackage = (pkg, opts={}) ->
metas = [
'<meta charset="utf-8">'
]
{config, progenitor} = pkg
config ?= {}
{title, description} = config
if title
metas.push "<title>#{title}</title>"
if description
metas.push metaTag "description", description.replace("\n", " ")
url = pkg.progenitor?.url
if url
metas.push "<link rel=\"Progenitor\" href=#{JSON.stringify(url)}>"
code = """
require('./#{pkg.entryPoint}');
"""
# Wrap code with !system integration if it exists
if pkg.dependencies["!system"]
depVersion = pkg.dependencies["!system"].config?.version or ""
ourVersion = PACKAGE.dependencies["!system"].config.version
if versionCompare(ourVersion, depVersion) is 1
console.info "Force updating `!system` dependency (#{depVersion} -> #{ourVersion})", pkg
pkg.dependencies["!system"] = PACKAGE.dependencies["!system"]
code = """
window.Observable = system.client.Observable;
#{code}
"""
code = """
require("!system").launch(function() {
#{code}
});
"""
"""
<!DOCTYPE html>
<html>
<head>
#{metas.join("\n ")}
#{dependencyScripts(pkg.remoteDependencies)}
</head>
<body>
<script>
#{require.packageWrapper(pkg, code)}
<\/script>
</body>
</html>
"""
startsWith = (str, prefix) ->
str.lastIndexOf(prefix, 0) is 0
endsWith = (str, suffix) ->
str.indexOf(suffix, str.length - suffix.length) != -1
extensionFor = (path) ->
result = path.match /\.([^.]+)$/
if result
result[1]
readTree = (fs, directoryPath, recursive=true) ->
fs.list(directoryPath)
.then (files) ->
Promise.all files.map (file) ->
if file.folder
if recursive
readTree(fs, file.path)
else
folder: true
path: path
else
file
.then (filesAndFolderFiles) ->
filesAndFolderFiles.reduce (a, b) ->
a.concat(b)
, []
ajax = require('ajax')()
MemoizedPromise = require "./lib/memoized-promise"
###
If our string is an absolute URL then we assume that the server is CORS enabled
and we can make a cross origin request to collect the JSON data.
We also handle a Github repo dependency such as `STRd6/issues:master`.
This loads the package from the published gh-pages branch of the given repo.
`STRd6/issues:master` will be accessible at `http://strd6.github.io/issues/master.json`.
###
fetchDependency = MemoizedPromise (path) ->
if typeof path is "string"
if startsWith(path, "http")
ajax.getJSON(path)
.catch ({status, response}) ->
switch status
when 0
message = "Aborted"
when 404
message = "Not Found"
else
throw new Error response
throw new Error "#{status} #{message}: #{path}"
else
if (match = path.match(/([^\/]*)\/([^\:]*)\:(.*)/))
[callback, user, repo, branch] = match
url = "https://#{user}.github.io/#{repo}/#{branch}.json"
ajax.getJSON(url)
.catch ->
throw new Error "Failed to load package '#{path}' from #{url}"
else
Promise.reject new Error """
Failed to parse repository info string #{path}, be sure it's in the
form `<user>/<repo>:<ref>` for example: `STRd6/issues:master`
or `STRd6/editor:v0.9.1`
"""
else
Promise.reject new Error "Can only handle url string dependencies right now, received: #{path}"
# modified from https://github.com/substack/semver-compare/blob/master/index.js
versionCompare = (a, b) ->
pa = a.split('.')
pb = b.split('.')
i = 0
while i < 4
na = parseInt pa[i], 10
nb = parseInt pb[i], 10
return 1 if (na > nb)
return -1 if (nb > na)
return 1 if !isNaN(na) and isNaN(nb)
return -1 if isNaN(na) and !isNaN(nb)
i++
return 0
module.exports = Util =
emptyElement: (element) ->
while element.lastChild
element.removeChild element.lastChild
extensionFor: extensionFor
fileSeparator: fileSeparator
htmlForPackage: htmlForPackage
normalizePath: normalizePath
absolutizePath: absolutizePath
versionCompare: versionCompare
fetchDependency: fetchDependency
# Execute a program with the given environment and context
#
# `program` is a string containing JavaScript code.
# `context` is what is bound to `this` when executing the program.
# `environment` is an object that binds its values to variables named by its
# keys.
execute: (program, context, environment) ->
args = Object.keys(environment)
values = args.map (name) -> environment[name]
Function(args..., program).apply(context, values)
isAbsolutePath: (path) ->
path.match /^\//
isRelativePath: (path) ->
path.match /^.?.\//
baseDirectory: (absolutePath="/") ->
absolutePath.match(/^.*\//)?[0] or "/"
parentElementOfType: (tagname, element) ->
tagname = tagname.toLowerCase()
if element.nodeName.toLowerCase() is tagname
return element
while element = element.parentNode
if element.nodeName.toLowerCase() is tagname
return element
# Convert node style errbacks to promise style
pinvoke: (object, method, params...) ->
new Promise (resolve, reject) ->
object[method] params..., (err, result) ->
if err
reject err
return
resolve result
startsWith: startsWith
endsWith: endsWith
evalCSON: (coffeeSource) ->
Promise.resolve()
.then ->
CoffeeScript.compile(coffeeSource, bare: true)
.then (jsCode) ->
# TODO: Security, lol
Function("return " + jsCode)()
generalType: (type="") ->
type.replace(/^application\/|\/.*$/, "").replace(/;.*$/, "")
readTree: readTree
###
Get the first file from a drop event. Does nothing if the event has had
defaultPrevented. Calls preventDefault if we handle the drop.
We also handle the special case of dragging and dropping files from the system
explorer.
Returns a promise that is fulfilled with the file.
###
fileFromDropEvent: (e) ->
return if e.defaultPrevented
fileSelectionData = e.dataTransfer.getData("zineos/file-selection")
if fileSelectionData
e.preventDefault()
data = JSON.parse fileSelectionData
selectedFile = data.files[0]
return system.readFile(selectedFile.path)
files = e.dataTransfer.files
if files.length
e.preventDefault()
return Promise.resolve(files[0])
return Promise.resolve()