Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

113 lines (98 sloc) 4.235 kB
fs = require 'fs'
colors = require 'colors'
# The Notes class holds all the logic needed for crawling a directory of files,
# searching for a set of patterns to annotate.
# Samples:
# NOTE: This line should get annoated by Notes.
# OPTIMIZE Make things faster!
# TODO: Annotate your tasks.
# FIXME: Keep up with things to fix.
class Notes
# Defines the patterns that will be checked during file annotating.
# If you want to run this on something other than ruby, coffeesciprt, or javascript
# files then you may need to change this. The default pattern is looking for a line
# beginning with a comment and then followed with "TODO", "NOTE", or "OPTIMIZE" keywords.
@patterns =
regexp: /^.*(#|\/\/|\/\*)\s*TODO\W*/
label: "✓ TODO".underline.magenta
regexp: /^.*(#|\/\/|\/\*)\s*NOTE\W*/
label: "✐ NOTE"
regexp: /^.*(#|\/\/|\/\*)\s*OPTIMIZE\W*/
label: "↘ OPTIMIZE".underline.yellow
regexp: /^.*(#|\/\/|\/\*)\s*FIXME\W*/
label: "☂ FIXME"
# You can also customize what types of file extensions will be filtered out of annotation.
@filterExtensions = [
"\\.jpg", "\\.jpeg", "\\.mov", "\\.mp3", "\\.gif", "\\.png",
"\\.log", "\\.bin", "\\.psd", "\\.swf", "\\.fla", "\\.ico"
# You can filter out full directory trees
@filterDirectories = ["node_modules"]
@skipHidden = true
@concurrentFiles = 30
constructor: (@rootDir) ->
# Constructor must take at least a root directory as first argument
throw "Root directory is required." unless @rootDir
annotate: ->
files = []
filesUnderDirectory @rootDir, (file) ->
files.push file
# Simple way to control # of files being opened at a time...
concurrency = 0
output = {}
# TODO: Clean this up some. The implementation got much more complex than I originally planned.
run = ->
while files.length > 0 and concurrency < Notes.concurrentFiles
# For each line in the file, check the patterns and output any matches
onLine = (line, lineNum, filePath) ->
for key, pattern of Notes.patterns
if line.match(pattern.regexp)?
output[filePath] = "* #{filePath.replace('//','/')}\n".green unless output[filePath]?
line = line.replace(pattern.regexp, '')
# Make the output kinda pretty...
spaces = ' '
spaces = spaces.substring(0, spaces.length-1) for n in (lineNum+1).toString()
lineNumStr = "Line #{lineNum}:".grey
output[filePath] += " #{lineNumStr}#{spaces}#{pattern.label} #{line}\n"
onCompletion = (filePath) ->
# Spit out the results for the file
console.log output[filePath] if output[filePath]?
file = files.shift()
# Process the file line-by-line
eachLineIn file, onLine, onCompletion
filesUnderDirectory = (dir, fileCallback) ->
files = fs.readdirSync dir
# If it's another directory, make a recursive call into it
if files?
files = (f for f in files when !f.match(/^\./)) if Notes.skipHidden # Skip hidden files/directories
files = (f for f in files when Notes.filterDirectories.indexOf(f) < 0) # Skip directories that should be filtered
filesUnderDirectory("#{dir}/#{f}", fileCallback) for f in files
catch error
if error.code is "ENOTDIR"
filter = ///(#{Notes.filterExtensions.join('|')})$/// # skip files matching filterExtensions
# It's a file, so pass it to the callback
fileCallback dir unless dir.match(filter)
else if error.code is "ELOOP"
console.log "#{error}... continuing."
throw error
eachLineIn = (filePath, onLine, onCompletion) ->
fs.readFile filePath, (err, data) ->
throw err if err?
# OPTIMIZE: can this handle large files well?
lines = data.toString('utf-8').split("\n")
onLine(line, i+1, filePath) for line, i in lines
module.exports = Notes
Jump to Line
Something went wrong with that request. Please try again.