neft
Version:
Universal Platform
266 lines (220 loc) • 7.72 kB
text/coffeescript
'use strict'
fs = require 'fs'
pathUtils = require 'path'
yaml = require 'js-yaml'
mkdirp = require 'mkdirp'
Jimp = require 'jimp'
utils = require 'src/utils'
log = require 'src/log'
assert = require 'src/assert'
Resources = require 'src/resources'
log = log.scope 'Resources', 'Parser'
IMAGE_FORMATS =
__proto__: null
png: true
jpg: true
jpeg: true
gif: true
DEFAULT_CONFIG =
resolutions: [1]
stack = null
resizeStack = null
isResourcesPath = (path) ->
/\/resources\.(?:json|yaml)$/.test path
toCamelCase = (str) ->
words = str.split ' '
r = words[0]
for i in [1...words.length] by 1
r += utils.capitalize words[i]
r
resolutionToString = (resolution) ->
if resolution is 1
''
else
'@' + (resolution + '').replace('.', 'p') + 'x'
getImageSize = do ->
cache = Object.create null
(path, mtime, callback) ->
if cache[path] and mtime < Date.now() - 1000
callback null, cache[path]
return
log.show "Get resource image size '#{path}'"
Jimp.read path, (err, image) ->
if err
return callback err
cache[path] =
width: image.bitmap.width
height: image.bitmap.height
callback null, cache[path]
return
return
supportImageResource = (path, rsc) ->
# get size
stats = fs.statSync path
mtime = new Date stats.mtime
resizeImage = (path, width, height, output, callback) ->
log.show "Resize resources image file '#{path}'"
Jimp.read path, (err, image) ->
if err
return callback err
image.resize(width, height).write output, callback
stack.add (callback) ->
getImageSize path, mtime.valueOf(), (err, meta) ->
if err
return callback err
name = pathUtils.basename path
name = Resources.Resource.parseFileName name
nameResolution = name.resolution ? 1
width = Math.round meta.width / nameResolution
height = Math.round meta.height / nameResolution
rsc.width = width
rsc.height = height
for format in rsc.formats
for resolution in rsc.resolutions
resPath = rsc.paths[format][resolution].slice(1)
if nameResolution isnt resolution
resPath = "build/#{resPath}"
shouldResize = not (exists = fs.existsSync(resPath))
shouldResize ||= new Date(fs.statSync(resPath).mtime) < mtime
if shouldResize
unless exists
mkdirp.sync pathUtils.dirname(resPath)
resizeStack.add resizeImage, null, [
path, width * resolution, height * resolution, resPath
]
callback()
return
parseResourcesFolder = (path) ->
throw new Error "Resources folder not implemented"
parseResourcesFile = (path, config) ->
assert.isString path
try
file = require path
catch err
log.error "Error in file '#{path}'"
throw err
getValue file, path, config
parseResourcesObject = (obj, dirPath, config) ->
assert.isPlainObject obj
if obj.resources?
config = utils.clone config
utils.merge config, obj
delete config.resources
else
obj = resources: obj
if dirPath.indexOf('.') isnt -1
dirPath = pathUtils.dirname dirPath
if Array.isArray(obj.resources)
parseResourcesArray obj.resources, dirPath, config
else
rscs = new Resources
for prop, val of obj.resources
rscs[prop] = getValue val, dirPath, config
rscs
parseResourcesArray = (arr, dirPath, config) ->
obj = utils.arrayToObject arr, (_, val) ->
name = val.file or val
if isResourcesPath(name)
pathUtils.dirname(name)
else
Resources.Resource.parseFileName(name).file
parseResourcesObject obj, dirPath, config
parseResourceFile = (path, config) ->
log.show "Parse resources file '#{path}'"
dirPath = pathUtils.dirname path
name = pathUtils.basename path
name = Resources.Resource.parseFileName(name)
name.resolution ?= 1
unless fs.existsSync(path)
msg = "File '#{path}' doesn't exist"
unless name.format
msg += "; format is missed"
log.error msg
return
rsc = new Resources.Resource
newConfig = {}
for key, val of config
newConfig[toCamelCase(key)] = utils.cloneDeep val
utils.merge rsc, newConfig
if name.file
rsc.file = name.file
if rsc.resolutions
unless utils.has(rsc.resolutions, name.resolution)
rsc.resolutions.push name.resolution
else
rsc.resolutions = [name.resolution]
# remove greater resolutions
rsc.resolutions = rsc.resolutions.filter (elem) ->
elem <= name.resolution
if name.format
if rsc.formats
# log.warn "Multiple formats are not currently supported; '#{rsc.formats}' got"
unless utils.has(rsc.formats, name.format)
rsc.formats.push name.format
else
rsc.formats = [name.format]
rsc.formats ?= []
paths = rsc.paths = {}
for format in rsc.formats
formatPaths = paths[format] = {}
for resolution in rsc.resolutions
resPath = "/#{dirPath}/#{name.file}#{resolutionToString(resolution)}.#{format}"
formatPaths[resolution] = resPath
if IMAGE_FORMATS[name.format]
supportImageResource path, rsc
rsc
getValue = (val, dirPath, config) ->
if utils.isObject(val)
config = utils.clone config
utils.merge config, val
delete config.file
delete config.resources
if typeof val is 'string'
path = pathUtils.join dirPath, val
getFile path, config
else if val?.file
path = pathUtils.join dirPath, val.file
getFile path, config
else if Array.isArray(val)
parseResourcesArray val, dirPath, config
else if utils.isObject(val)
if val.resources?
if Array.isArray(val.resources)
parseResourcesArray val.resources, dirPath, config
else
parseResourcesObject val.resources, dirPath, config
else
config = utils.clone config
utils.merge config, val
parseResourceFile dirPath, config
getFile = (path, config) ->
try
stat = fs.statSync path
catch
log.error "File '#{path}' doesn't exist"
return
possiblePaths = [
pathUtils.join(path, './resources.json'),
pathUtils.join(path, './resources.yaml'),
pathUtils.join(path, './resources.yml'),
]
if isResourcesPath(path)
return parseResourcesFile path, config
if stat.isDirectory()
for possiblePath in possiblePaths
if fs.existsSync(possiblePath)
return parseResourcesFile possiblePath, config
return parseResourcesFolder path, config
return parseResourceFile path, config
exports.parse = (path, callback) ->
stack = new utils.async.Stack
resizeStack = new utils.async.Stack
rscs = getFile path, DEFAULT_CONFIG
stack.runAllSimultaneously (err) ->
unless rscs instanceof Resources
rscs = {}
if not err and resizeStack.length
resizeStack.runAllSimultaneously ->
callback err, rscs
else
callback err, rscs