caffeine-mc
Version:
Select, configure and extend your to-JavaScript compiler, with arbitrary code, on a per file bases from within the file.
224 lines (180 loc) • 7.93 kB
text/coffeescript
{
defineModule, peek, Promise, dashCase, upperCamelCase,
ErrorWithInfo, log, merge, present, find, each, w
mergeInto
currentSecond
snakeCase,
isString
} = require 'art-standard-lib'
Path = require 'path'
dirReader = require './DirReader'
{cacheable} = require './WorkingCache'
normalizeName = cacheable "normalizeName", upperCamelCase
realRequire = eval 'require'
{findSourceRootSync} = require './SourceRoots'
###
2018-07-21 Optimization TODO:
I think if we simplify the semantics such that matches are defined as:
normalizeName(dirName) == normalizeName(moduleName)
AND
# this is the change:
normalizeName(fileName.split(".")[0]) == normalizeName(moduleName)
Then we can probably make much better use of caching:
Read the dir in and create a map:
normalizedName: name
(where normalizedName here means for files, we strip the extensions)
Then, we don't have to scan the dir every time!
NOTE: I think if we have >= 2 files which map to the same noramlized name
we encode that in the map somehow and can therefore raise the same
exception we already do.
###
defineModule module, class ModuleResolver
###
IN:
moduleBaseName: the string before the first '/'
modulePathArray: every other sub-string, split by '/'
This is only used to determine if there is addutional pathing
that must be resolved. It makes a difference what the
require path looks like.
###
: (moduleBaseName, modulePathArray) ->
if /^@/.test moduleBaseName
[a, b] = moduleBaseName.split /\//
try absolutePath = Path.dirname realRequire.resolve name = "@#{dashCase a}/#{dashCase b}"
try absolutePath ?= Path.dirname realRequire.resolve name = "@#{snakeCase a}/#{snakeCase b}"
else
try absolutePath = Path.dirname realRequire.resolve name = dashCase moduleBaseName
try absolutePath ?= Path.dirname realRequire.resolve name = snakeCase moduleBaseName
try absolutePath ?= Path.dirname realRequire.resolve name = moduleBaseName
catch
throw new ErrorWithInfo "ModuleResolver: Could not find requested npm package: #{moduleBaseName}",
npmPackageNamesAttempted: [moduleBaseName, dashCase moduleBaseName]
if modulePathArray?.length > 0
[requireString] = name.split '/'
absolutePath = findSourceRootSync absolutePath
else
requireString = name
{requireString, absolutePath}
: (moduleName, options) =>
if /\//.test moduleName
[base, modulePathArray...] = for mod in [denormalizedBase, denormalizedBase2] = moduleName.split "/"
normalizeName mod
if /^@/.test moduleName
base = "@#{base}/#{modulePathArray[0]}"
denormalizedBase = "#{denormalizedBase}/#{denormalizedBase2}"
[_, modulePathArray...] = modulePathArray
else denormalizedBase = moduleName
{requireString, absolutePath} = denormalizedBase, modulePathArray, options
if modulePathArray
for sub in modulePathArray
if matchingName = sub, absolutePath, options
absolutePath = Path.join absolutePath, matchingName
requireString = "#{requireString}/#{matchingName}"
else
throw new ErrorWithInfo "Could not find pathed submodule inside npm package: #{requireString}",
npmPackage: requireString
localNpmPackageLocation: absolutePath
submodulePath: sub
normalized: normalizeName sub
dirItems: dirReader.read absolutePath
{requireString, absolutePath}
: (moduleName, options) =>
Promise.then => moduleName, options
maybeCouldHaveCached = {}
: (moduleBaseName, modulePathArray, options) =>
normalizedModuleName = upperCamelCase moduleBaseName
{sourceFile, sourceDir, sourceFiles, sourceRoot} = options if options
sourceFile ||= sourceFiles?[0]
if sourceFile || sourceDir
directory = sourceDir = dirReader.resolve sourceDir || Path.dirname sourceFile
sourceRoot ||= findSourceRootSync sourceDir
sourceRoot = sourceRoot && dirReader.resolve sourceRoot
absoluteSourceFilePath = sourceFile && Path.join sourceDir, Path.parse(sourceFile).name
absolutePath = null
shouldContinue = present sourceRoot
while shouldContinue
if (matchingName = normalizedModuleName, directory, options) &&
absoluteSourceFilePath != absolutePath = Path.join directory, matchingName
shouldContinue = false
else
absolutePath = null
if directory == sourceRoot
if normalizedModuleName == normalizeName peek sourceRoot.split "/"
absolutePath = sourceRoot
shouldContinue = false
else
directory = Path.dirname directory
if absolutePath
requireString = Path.relative sourceDir, absolutePath
switch requireString
when "..", "." then requireString = "#{requireString}/"
requireString = "./#{requireString}" unless requireString.match /^\./
{requireString, absolutePath}
else
try
moduleBaseName, modulePathArray
catch e
if e.info
mergeInto e.info, {sourceDir, sourceRoot}
throw e
###
Notes about "." names-with-dots.
Essentially, dots are treated as word-boundaries.
Files:
We need to manage extensions. Current rule:
Full match example: FooCaf matches foo.caf
PartialMatch must fully match on dot-boundaries:
Foo.BarFood.caf does NOT match FooBar, but does match FooBarFood
PartialMatch must match starting at the first character:
Foo.BarFood.caf does NOT match BarFood but does match Foo
Dirs:
Dirs must fully match:
Art.Foo.Bar matches ArtFooBar BUT NOT ArtFoo
Future:
I'd like to be able to treat "."s in dir-names as-if they were '/' (slashes)
Basically, this parallels how NeptuneNamespaces interprets them.
It should work identically to as-if there were nested dirs.
Given these files:
MyFile1.caf
Foo/Bar/MyFile2.caf
OR these files:
MyFile1.caf
Foo.Bar/MyFile2.caf
Then:
# inside MyFile1.caf
# this works:
&Foo/Bar/MyFile2
returns false or name, if it matches
###
: getMatchingName = (normalizedModuleName, name, isDir) ->
if normalizedModuleName == normalizedTestName = normalizeName name
name
else unless isDir
if 0 == normalizedTestName.indexOf normalizedModuleName
foundLegalStop = false
offset = 0
for stop, i in stops = name.split '.'
stop = normalizeName stop
offset += stop.length
if normalizedModuleName.length == offset
return stops.slice(0, i + 1).join '.'
false
# PRIVATE
: (normalizedModuleName, directory, options) ->
matchingName = null
unless isString directory
throw new ErrorWithInfo "Directory must be a string"
for name in dirReader.read directory
if newMatchingName = getMatchingName normalizedModuleName, name, dirReader.isDir Path.join directory, name
if matchingName && matchingName != newMatchingName
throw new ErrorWithInfo """
More than one matching module name with
a) different actual base-names (#{matchingName} != #{newMatchingName}) and
b) for the same normalized name (#{normalizedModuleName})
""",
directory: directory
firstMatch: matchingName
secondMatch: newMatchingName
normalizedModuleName: normalizedModuleName
matchingName = newMatchingName
matchingName