UNPKG

objfile

Version:

Read from + write to + update INI and JSON files via a simple asynchronous API

225 lines (173 loc) 5.95 kB
# node dependency fs = require 'fs' path = require 'path' # npm dependency mkdirp = require 'mkdirp' loaded_files = {} class ObjFile _objFileAllData: null _objFileCue: null _objFileData: null _objFileError: null _objFileOptions: null _objFilePath: null _objFileReading: null _objFileWriting: null constructor: (source_path, opts, cb) -> @_objFileLoad source_path, opts, cb _objFileLoad: (source_path, opts, cb) => if not cb and typeof opts is 'function' [cb, opts] = [opts, {}] @_opts opts ?= {} if typeof opts isnt 'object' throw new Error 'options must be object: ' + 'ObjFile::_objFileLoad(path, options, callback)' if (@_objFilePath ?= source_path) isnt source_path return cb? new Error 'source_path does not match previously set path' @_objFileCue ?= [] @_objFileOptions ?= opts # expose moethods on targets if requested if (target = opts.expose) and typeof opts.expose is 'object' unless Array.isArray target target = [target] for item in target when item and typeof item is 'object' item.get = (args...) => @get args... item.set = (args...) => @set args... item.del = (args...) => @del args... if (target = opts.exposeGet) and typeof opts.exposeGet is 'object' unless Array.isArray target target = [target] for item in target when item and typeof item is 'object' item.get = (args...) => @get args... setTimeout => # ensure async (return variable assignment happens first) @_objFileRead cb _objFileRead: (cb) => process = => @_objFileError = null @_objFileReading = true error = (err) => @_objFileError = if typeof err is 'string' then (new Error err) else err @_objFileReading = false cb? @_objFileError @_objFileCue.shift()?() # flush next in cue ready = => @_objFileReading = false setTimeout => cb?() @_objFileCue.shift()?() # flush next in cue unless @_objFilePath return error 'File source path missing' if @_objFileAllData return ready() @_objFileAllData = {} if root = @_objFileOptions.dataRoot # TODO: does only 1 level @_objFileData = @_objFileAllData[root] = {} else @_objFileData = @_objFileAllData fs.readFile @_objFilePath, encoding: 'utf8', (err, data) => unless err return @constructor.decode data, (err, pkg) => if err return error err try for own key of @_objFileAllData delete @_objFileAllData[key] for key, value of pkg @_objFileAllData[key] = value if root unless @_objFileAllData[root] and typeof @_objFileAllData[root] is 'object' @_objFileData = @_objFileAllData[root] = {} return @_objFileWrite (err) -> if err return error err ready() else @_objFileData = @_objFileAllData[root] else @_objFileData = @_objFileAllData catch _err return ready _err ready() ready() if @_objFileError cb? @_objFileError else if @_objFileReading or @_objFileWriting @_objFileCue.push process else process cb return _objFileWrite: (cb) => error = (err) => @_objFileError = if typeof err is 'string' then (new Error err) else err @_objFileWriting = false cb? @_objFileError @_objFileCue.shift()?() # flush next in cue @_objFileRead (err) => # re-read first if err return error err process = => @_objFileWriting = true @constructor.encode @_objFileAllData, (err, str) => if err return error err mkdirp path.dirname(@_objFilePath), {mode: '0755'}, (err) => if err return error err fs.writeFile @_objFilePath, str, encoding: 'utf8', (err) => if err return error err @_objFileWriting = false cb?() @_objFileCue.shift()?() # flush next in cue if @_objFileError cb? @_objFileError else if @_objFileReading or @_objFileWriting @_objFileCue.push process else process() return del: (level_names..., name, cb) => @_objFileRead (err) => if err return cb? err target = @_objFileData for level_name in level_names unless target[level_name] and typeof target[level_name] is 'object' target[level_name] = {} target = target[level_name] delete target[name] @_objFileWrite cb get: (level_names..., name, cb) => unless typeof cb is 'function' throw new Error 'Callback function is required' @_objFileRead (err) => if err return cb? err undefined_key = -> cb? new Error 'Undefined: ' + level_names.concat([name]).join '.' target = @_objFileData for level_name in level_names unless target[level_name] and typeof target[level_name] is 'object' return undefined_key() target = target[level_name] unless target.hasOwnProperty name return undefined_key() cb null, target[name] set: (level_names..., name, value, cb) => @_objFileRead (err) => if err return cb? err target = @_objFileData for level_name in level_names unless target[level_name] and typeof target[level_name] is 'object' target[level_name] = {} target = target[level_name] target[name] = value @_objFileWrite cb module.exports = ObjFile