WIP: Use rake to start compiling resources (like require.coffee)

This commit is contained in:
Nathan Sobo
2012-08-26 16:29:46 -05:00
parent 72468b7028
commit c3e748a17c
128 changed files with 35213 additions and 7 deletions

View File

@@ -0,0 +1,35 @@
# node.js child-process
# http://nodejs.org/docs/v0.6.3/api/child_processes.html
$ = require 'jquery'
_ = require 'underscore'
module.exports =
class ChildProccess
@exec: (command, options={}) ->
deferred = $.Deferred()
if options.bufferLines
options.stdout = @bufferLines(options.stdout) if options.stdout
options.stderr = @bufferLines(options.stderr) if options.stderr
$native.exec command, options, (exitStatus, stdout, stderr) ->
try
if exitStatus != 0
deferred.reject({command, exitStatus, stderr})
else
deferred.resolve(stdout, stderr)
catch e
console.error "In ChildProccess termination callback: ", e.message
console.error e.stack
deferred
@bufferLines: (callback) ->
buffered = ""
(data) ->
buffered += data
lastNewlineIndex = buffered.lastIndexOf('\n')
if lastNewlineIndex >= 0
callback(buffered.substring(0, lastNewlineIndex + 1))
buffered = buffered.substring(lastNewlineIndex + 1)

20
src/stdlib/event.coffee Normal file
View File

@@ -0,0 +1,20 @@
_ = require 'underscore'
module.exports =
class Event
events: {}
on: (name, callback) ->
@events[name] ?= []
@events[name].push callback
off: (name, callback) ->
delete @events[name][_.indexOf callback] if @events[name]
trigger: (name, data...) ->
if name.match /^app:/
OSX.NSApp.triggerGlobalAtomEvent_data name, data
return
_.each @events[name], (callback) => callback data...
null

131
src/stdlib/fs.coffee Normal file
View File

@@ -0,0 +1,131 @@
# commonjs fs module
# http://ringojs.org/api/v0.8/fs/
_ = require 'underscore'
$ = require 'jquery'
module.exports =
# Make the given path absolute by resolving it against the
# current working directory.
absolute: (path) ->
$native.absolute(path)
# Return the basename of the given path. That is the path with
# any leading directory components removed. If specified, also
# remove a trailing extension.
base: (path, ext) ->
base = path.replace(/\/$/, '').split("/").pop()
if ext then base.replace(RegExp(ext + "$"), "") else base
# Returns the path of a file's containing directory, albeit the
# parent directory if the file is a directory. A terminal directory
# separator is ignored.
directory: (path) ->
parentPath = path.replace(new RegExp("/#{@base(path)}\/?$"), '')
return "" if path == parentPath
parentPath
# Returns true if the file specified by path exists
exists: (path) ->
return false unless path?
$native.exists(path)
# Returns the extension of a file. The extension of a file is the
# last dot (excluding any number of initial dots) followed by one or
# more non-dot characters. Returns an empty string if no valid
# extension exists.
extension: (path) ->
return '' unless typeof path is 'string'
match = @base(path).match(/\.[^\.]+$/)
if match
match[0]
else
""
join: (paths...) ->
return paths[0] if paths.length == 1
[first, rest...] = paths
first.replace(/\/?$/, "/") + @join(rest...)
# Returns true if the file specified by path exists and is a
# directory.
isDirectory: (path) ->
$native.isDirectory path
# Returns true if the file specified by path exists and is a
# regular file.
isFile: (path) ->
return false unless path?
$native.isFile(path)
# Returns an array with all the names of files contained
# in the directory path.
list: (path) ->
$native.list(path, false)
listTree: (path) ->
$native.list(path, true)
move: (source, target) ->
$native.move(source, target)
# Remove a file at the given path. Throws an error if path is not a
# file or a symbolic link to a file.
remove: (path) ->
$native.remove path
# Open, read, and close a file, returning the file's contents.
read: (path) ->
$native.read(path)
# Returns an array of path components. If the path is absolute, the first
# component will be an indicator of the root of the file system; for file
# systems with drives (such as Windows), this is the drive identifier with a
# colon, like "c:"; on Unix, this is an empty string "". The intent is that
# calling "join.apply" with the result of "split" as arguments will
# reconstruct the path.
split: (path) ->
path.split("/")
# Open, write, flush, and close a file, writing the given content.
write: (path, content) ->
$native.write(path, content)
makeDirectory: (path) ->
$native.makeDirectory(path)
# Creates the directory specified by "path" including any missing parent
# directories.
makeTree: (path) ->
return unless path
if not @exists(path)
@makeTree(@directory(path))
@makeDirectory(path)
traverseTree: (rootPath, fn) ->
recurse = null
prune = -> recurse = false
for path in @list(rootPath)
recurse = true
fn(path, prune)
@traverseTree(path, fn) if @isDirectory(path) and recurse
lastModified: (path) ->
$native.lastModified(path)
md5ForPath: (path) ->
$native.md5ForPath(path)
async:
list: (path) ->
deferred = $.Deferred()
$native.asyncList path, false, (subpaths) ->
deferred.resolve subpaths
deferred
listTree: (path) ->
deferred = $.Deferred()
$native.asyncList path, true, (subpaths) ->
deferred.resolve subpaths
deferred

View File

@@ -0,0 +1,16 @@
stringScore = require 'stringscore'
module.exports = (candidates, query, options) ->
if query
scoredCandidates = candidates.map (candidate) ->
string = if options.key? then candidate[options.key] else candidate
{ candidate, score: stringScore(string, query) }
scoredCandidates.sort (a, b) ->
if a.score > b.score then -1
else if a.score < b.score then 1
else 0
candidates = (scoredCandidate.candidate for scoredCandidate in scoredCandidates when scoredCandidate.score > 0)
candidates = candidates[0...options.maxResults] if options.maxResults?
candidates

View File

@@ -0,0 +1,32 @@
$ = require 'jquery'
$.fn.scrollBottom = (newValue) ->
if newValue?
@scrollTop(newValue - @height())
else
@scrollTop() + @height()
$.fn.scrollRight = (newValue) ->
if newValue?
@scrollLeft(newValue - @width())
else
@scrollLeft() + @width()
$.fn.containsElement = (element) ->
(element[0].compareDocumentPosition(this[0]) & 8) == 8
$.fn.preempt = (eventName, handler) ->
@on eventName, (e, args...) ->
if handler(e, args...) == false then e.stopImmediatePropagation()
eventNameWithoutNamespace = eventName.split('.')[0]
handlers = @data('events')[eventNameWithoutNamespace]
handlers.unshift(handlers.pop())
$.fn.hasParent = ->
@parent()[0]?
$.fn.flashError = ->
@addClass 'error'
removeErrorClass = => @removeClass 'error'
window.setTimeout(removeErrorClass, 200)

9
src/stdlib/native.coffee Normal file
View File

@@ -0,0 +1,9 @@
module.exports =
class Native
@alert: (args...) -> $native.alert(args...)
@saveDialog: (args...) -> $native.saveDialog(args...)
@reload: -> $native.reload()
@moveToTrash: (args...) -> $native.moveToTrash(args...)

View File

@@ -0,0 +1,23 @@
(function() {
native function buildOnigRegExp(source);
native function search(string, index);
native function getCaptureIndices(string, index);
native function getCaptureCount();
native function test(string);
function OnigRegExp(source) {
var regexp = buildOnigRegExp(source);
regexp.constructor = OnigRegExp;
regexp.__proto__ = OnigRegExp.prototype;
regexp.source = source;
return regexp;
}
OnigRegExp.prototype.search = search;
OnigRegExp.prototype.test = test;
OnigRegExp.prototype.getCaptureIndices = getCaptureIndices;
OnigRegExp.prototype.getCaptureCount = getCaptureCount;
this.OnigRegExp = OnigRegExp;
})();

15
src/stdlib/path.coffee Normal file
View File

@@ -0,0 +1,15 @@
# node.js path module
# http://nodejs.org/docs/v0.6.0/api/path.html
_ = require 'underscore'
module.exports =
# Return the last portion of a path. Similar to the Unix basename command.
basename: (filepath) ->
_.last filepath.split '/'
# Return the extension of the path, from the last '.' to end of string in
# the last portion of the path. If there is no '.' in the last portion of
# the path or the first character of it is '.', then it returns an empty string.
extname: (filepath) ->
_.last filepath.split '.'

147
src/stdlib/require.coffee Normal file
View File

@@ -0,0 +1,147 @@
paths = [
"#{atom.loadPath}/spec"
"#{atom.loadPath}/benchmark"
"#{atom.loadPath}/src/stdlib"
"#{atom.loadPath}/src/app"
"#{atom.loadPath}/src/extensions"
"#{atom.loadPath}/src"
"#{atom.loadPath}/vendor"
"#{atom.loadPath}/static"
"#{atom.loadPath}/bundles"
"#{atom.loadPath}/themes"
"#{atom.loadPath}"
]
window.__filename = null
nakedLoad = (file) ->
file = resolve file
code = __read file
window.eval(code + "\n//@ sourceURL=" + file)
require = (file, cb) ->
return cb require file if cb?
file = resolve file
parts = file.split '.'
ext = parts[parts.length-1]
if __modules[file]?
if not __modules.loaded[file.toLowerCase()]?
console.warn "Circular require: #{__filename} required #{file}"
return __modules[file]
else if __modules.loaded[file.toLowerCase()]
console.warn "Multiple requires (different cases) for #{file}"
[ previousFilename, window.__filename ] = [ __filename, file ]
__modules[file] = {} # Fix for circular references
__modules[file] = (exts[ext] or (file) -> __read file) file
window.__filename = previousFilename
__modules[file]
define = (cb) ->
__defines.push ->
exports = __modules[__filename] or {}
module = exports: exports
cb.call exports, require, exports, module
__modules.loaded[__filename.toLowerCase()] = true
module.exports or exports
exts =
js: (file, code) ->
code or= __read file
if not /define\(/.test code
code = """
define(function(require, exports, module) { 'use strict'; #{code};
});
"""
eval(code + "\n//@ sourceURL=" + file)
__defines.pop()?.call()
coffee: (file) ->
exts.js(file, __coffeeCache(file))
resolve = (file) ->
if /!/.test file
parts = file.split '!'
file = parts[parts.length-1]
if file[0..1] is './'
prefix = __filename.split('/')[0..-2].join '/'
file = file.replace './', "#{prefix}/"
if file[0..2] is '../'
prefix = __filename.split('/')[0..-3].join '/'
file = file.replace '../', "#{prefix}/"
if file[0] isnt '/'
paths.some (path) ->
fileExists = /\.(.+)$/.test(file) and __exists "#{path}/#{file}"
jsFileExists = not /\.(.+)$/.test(file) and __exists "#{path}/#{file}.js"
if fileExists
file = "#{path}/#{file}"
if jsFileExists
file = "#{path}/#{file}.js"
else if expanded = __expand "#{path}/#{file}"
file = expanded
else
file = __expand(file) or file
if file[0] isnt '/'
throw "require: Can't find '#{file}'"
return file
__expand = (path) ->
return path if __isFile path
for ext, handler of exts
if __exists "#{path}.#{ext}"
return "#{path}.#{ext}"
else if __exists "#{path}/index.#{ext}"
return "#{path}/index.#{ext}"
return path if __exists path
return null
__exists = (path) ->
$native.exists path
__isFile = (path) ->
$native.isFile path
__coffeeCache = (filePath) ->
{CoffeeScript} = require 'coffee-script'
tmpPath = "/tmp/atom-compiled-scripts"
cacheFilePath = [tmpPath, $native.md5ForPath(filePath)].join("/")
if __exists(cacheFilePath)
__read(cacheFilePath)
else
compiled = CoffeeScript.compile(__read(filePath), filename: filePath)
$native.write(cacheFilePath, compiled)
compiled
__read = (path) ->
try
$native.read(path)
catch e
console.error "Failed to read `#{path}`"
throw e
__modules = { loaded : {} }
__defines = []
this.require = require
this.nakedLoad = nakedLoad
this.define = define
this.require.paths = paths
this.require.exts = exts
this.require.resolve = resolve
this.require.nameToUrl = (path) -> "#{path}.js"
this.require.__modules = __modules
# issue #17
this.require.noWorker = true

View File

@@ -0,0 +1,55 @@
fs = require 'fs'
{CoffeeScript} = require 'coffee-script'
# Settings file looks like:
# editor: # name of class
# theme: "twilight"
# tabSize: 2
# softTabs: true
# showInvisibles: false
#
# project:
# ignorePattern: /x|y|z/
#
# Settings are applied to object x's settings variable by calling applyTo(x) on
# instance of Settings.
module.exports =
class Settings
settings: {}
load: (path) ->
path = require.resolve path
if not fs.isFile path
console.warn "Could not find settings file '#{path}'"
return
try
json = CoffeeScript.eval "return " + (fs.read path)
for className, value of json
@settings[@simplifyClassName className] = value
catch error
console.error "Can't evaluate settings at `#{path}`."
console.error error
applyTo: (object) ->
if not object.settings
console.warning "#{object.constructor.name}: Does not have `settings` varible"
return
classHierarchy = []
# ICK: Using internal var __super to get the build heirarchy
walker = object
while walker
classHierarchy.unshift @simplifyClassName walker.constructor.name
walker = walker.constructor.__super__
for className in classHierarchy
for setting, value of @settings[className] ? {}
object.settings[setting] = value
# I don't care if you use camelCase, underscores or dashes. It should all
# point to the same place
simplifyClassName: (className) ->
className.toLowerCase().replace /\W/, ''

39
src/stdlib/storage.coffee Normal file
View File

@@ -0,0 +1,39 @@
module.exports =
class Storage
storagePath: (require.resolve '~/.atom/.storage')
get: (key, defaultValue=null) ->
try
value = @storage().valueForKeyPath key
@toJS value or defaultValue
catch error
error.message += "\nGetting #{key}"
console.error(error)
set: (key, value) ->
keys = key.split '.'
parent = storage = @storage()
for key in keys.slice 0, -1
parent[key] = {} unless parent[key]
parent = parent[key]
parent[keys.slice -1] = value
storage.writeToFile_atomically @storagePath, true
storage: ->
storage = OSX.NSMutableDictionary.dictionaryWithContentsOfFile @storagePath
storage ?= OSX.NSMutableDictionary.dictionary
toJS: (value) ->
if not value or not value.isKindOfClass
value
else if value.isKindOfClass OSX.NSDictionary.class
dict = {}
dict[k.valueOf()] = @toJS v for k, v of value
dict
else if value.isKindOfClass OSX.NSArray.class
array = []
array.push @toJS v for v in value
array
else
value.valueOf()

View File

@@ -0,0 +1,53 @@
_ = require 'underscore'
_.mixin
remove: (array, element) ->
index = array.indexOf(element)
array.splice(index, 1) if index >= 0
sum: (array) ->
sum = 0
sum += elt for elt in array
sum
adviseBefore: (object, methodName, advice) ->
original = object[methodName]
object[methodName] = (args...) ->
unless advice.apply(this, args) == false
original.apply(this, args)
escapeRegExp: (string) ->
# Referring to the table here:
# https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp
# these characters should be escaped
# \ ^ $ * + ? . ( ) | { } [ ]
# These characters only have special meaning inside of brackets
# they do not need to be escaped, but they MAY be escaped
# without any adverse effects (to the best of my knowledge and casual testing)
# : ! , =
# my test "~!@#$%^&*(){}[]`/=?+\|-_;:'\",<.>".match(/[\#]/g)
specials = [
# order matters for these
"-"
"["
"]"
# order doesn't matter for any of these
"/"
"{"
"}"
"("
")"
"*"
"+"
"?"
"."
"\\"
"^"
"$"
"|"]
# I choose to escape every character with '\'
# even though only some strictly require it when inside of []
regex = RegExp('[' + specials.join('\\') + ']', 'g')
string.replace(regex, "\\$&");

50
src/stdlib/watcher.coffee Normal file
View File

@@ -0,0 +1,50 @@
module.exports =
class Watcher
@watchedPaths: {}
@setup: ->
if not OSX.__AAWatcher__
OSX.__AAWatcher__ = OSX.JSCocoa.createClass_parentClass "__AAWatcher__", "NSObject"
OSX.JSCocoa.addInstanceMethod_class_jsFunction_encoding "watcher:receivedNotification:forPath:", OSX.__AAWatcher__, @watcher_receivedNotification_forPath, "v:@@@@"
@delegate = OSX.__AAWatcher__.alloc.init
@queue = OSX.UKKQueue.alloc.init
@queue.setDelegate @delegate
@watch: (path, callback) ->
@setup() unless @queue?
path = OSX.NSString.stringWithString(path).stringByStandardizingPath
@queue.addPath path if not @watchedPaths[path]
(@watchedPaths[path] ?= []).push callback
callback # Handy for anonymous functions.
@unwatch: (path, callback=null) ->
return unless @watchedPaths[path]
@watchedPaths[path] = (item for item in @watchedPaths[path] when item != callback)
if not callback? or @watchedPaths[path].length == 0
@watchedPaths[path] = null
console.log "Unwatch #{path}"
@queue.removePathFromQueue path
# Delegate method for __AAWatcher__
@watcher_receivedNotification_forPath = (queue, notification, path) =>
callbacks = @watchedPaths[path] ? []
switch notification.toString()
when "UKKQueueFileRenamedNotification"
throw "Doesn't handle this yet"
when "UKKQueueFileDeletedNotification"
@watchedPaths[path] = null
@queue.removePathFromQueue path
callback notification, path, callback for callback in callbacks
when "UKKQueueFileWrittenToNotification"
callback notification, path, callback for callback in callbacks
when "UKKQueueFileAttributesChangedNotification"
# Just ignore this
console.log "Attribute Changed on #{path}"
else
console.error "I HAVE NO IDEA WHY #{notification} WAS TRIGGERED ON #{path}"