Several large refactorings

* Refactor location of all data files to include a subdirectory
  for the mod version
* Update the BaseModel class to allow models to load themselves from
  any text file (whether JSON or CG)
* Refactor ModPack & ModVersion to move the laoding functionality in
  one into the other (and use the new stuff in BaseModel)
* Move the parser versions into a new subdirectory which allows for
  future additional parsers
This commit is contained in:
Andrew Miner
2015-01-16 20:26:54 -08:00
parent c14997ef90
commit 5f57622cf3
954 changed files with 284 additions and 1239 deletions
+49 -2
View File
@@ -5,22 +5,69 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
{Event} = require '../constants'
{ModelState} = require '../constants'
########################################################################################################################
module.exports = class BaseModel extends Backbone.Model
constructor: (attributes={}, options={})->
options.allowedSyncMethods = []
super attributes, options
makeGetter = (name)-> return -> @get name
makeSetter = (name)-> return (value)-> @set name, value
for name in _.keys attributes
for name, value of attributes
continue if name is 'id'
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
@allowedSyncMethods = options.allowedSyncMethods
@state = ModelState.unloaded
@on 'request', => @state = ModelState.loading
@on 'sync', => @state = ModelState.loaded
@on 'error', => @state = ModelState.error
@loading = null
# Public Methods ###############################################################################
# Event Methods ################################################################################
onLoadSucceeded: (text, status, xhr)->
try
@set @parse text
@trigger Event.sync, this, text
catch e
logger.error "A parsing error occured: #{e.stack}"
@onLoadFailed e.message, 'parsing failed', xhr
onLoadFailed: (error, status, xhr)->
logger.error "#{@constructor.name} (#{@cid}) failed to load: status:#{status}, message:#{error}"
@trigger Event.error, this, error
# Backbone.Model Overrides #####################################################################
sync: -> # do nothing
fetch: ->
url = @url()
logger.info "#{@constructor.name} (#{@cid}) reading from url: #{url}"
@trigger Event.request, this
@loading = w.promise (resolve, reject)=>
$.ajax
url: url
dataType: 'text'
success: (text, status, xhr)=> resolve @onLoadSucceeded text, status, xhr
error: (xhr, status, error)=> reject @onLoadFailed error, status, xhr
return @loading
parse: (text)->
return JSON.parse text
sync: (method, model)->
throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}"
trigger: (name)->
logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event"
+28
View File
@@ -0,0 +1,28 @@
###
Crafting Guide - mod.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
########################################################################################################################
module.exports = class Mod extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required'
attributes.id = attributes.slug = _.slugify attributes.name
super attributes, options
# Backbone.View Overrides ######################################################################
parse: (response)->
sync: (method, model, options={})->
if method isnt 'read' then throw new Error "Mod data can only be read, not #{method}d"
super method, model, options
url: ->
return "/data/#{@slug}/mod.cg"
+25 -70
View File
@@ -5,11 +5,11 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
ModVersionParser = require './mod_version_parser'
{RequiredMods} = require '../constants'
{Url} = require '../constants'
BaseModel = require './base_model'
{Event} = require '../constants'
ModVersionParser = require './mod_version_parser'
{DefaultModVersions} = require '../constants'
{Url} = require '../constants'
########################################################################################################################
@@ -19,11 +19,20 @@ module.exports = class ModPack extends BaseModel
attributes.modVersions ?= []
super attributes, options
@loading = w(true)
@_parser = new ModVersionParser
# Public Methods ###############################################################################
addModVersion: (modVersion)->
if modVersion.modPack isnt this then throw new Error "the mod version must be associated with this mod pack"
return if @modVersions.indexOf(modVersion) isnt -1
@modVersions.push modVersion
@trigger Event.add, modVersion, this
@modVersions.sort (a, b)-> a.compareTo b
@trigger Event.sort, this
@trigger Event.change, this
return this
enableModsForItem: (name)->
for modVersion in @modVersions
continue if modVersion.enabled
@@ -64,13 +73,15 @@ module.exports = class ModPack extends BaseModel
result = {}
item = @findItem slug, includeDisabled:true
if item?
result.modSlug = item.modVersion.slug
result.itemSlug = item.slug
result.itemName = item.name
result.modSlug = item.modVersion.slug
result.modVersion = item.modVersion.version
result.itemSlug = item.slug
result.itemName = item.name
else
result.modSlug = _.slugify RequiredMods[0]
result.itemSlug = slug
result.itemName = @findName slug, includeDisabled:true
result.modSlug = _.slugify DefaultModVersions[0].name
result.modVersion = DefaultModVersions[0].version
result.itemSlug = slug
result.itemName = @findName slug, includeDisabled:true
result.iconUrl = Url.itemIcon result
result.itemUrl = Url.item result
@@ -85,9 +96,6 @@ module.exports = class ModPack extends BaseModel
return false
isLoading: ->
return @loading.inspect().state isnt 'pending'
isValidName: (name, options={})->
options.includeDisabled ?= false
@@ -99,59 +107,6 @@ module.exports = class ModPack extends BaseModel
return false
loadModVersion: (url)->
promise = w.promise (resolve, reject)=>
@trigger Event.load.started, this, url
$.ajax
url: url
dataType: 'text'
success: (data, status, xhr)=>
resolve @onModVersionLoaded(url, data, status, xhr)
error: (xhr, status, error)=>
reject @onModVersionLoadFailed(url, error, status, xhr)
@loading = w.join @loading, promise
loadModVersionData: (data)->
modVersion = @_parser.parse data
@modVersions.push modVersion
@trigger Event.add, modVersion, this
@modVersions.sort (a, b)-> a.compareTo b
@trigger Event.sort, this
modVersion.on Event.change, => @trigger Event.change, this
return modVersion
loadAllModVersions: (urlList)->
for url in urlList
@loadModVersion url
return @loading
# Event Methods ################################################################################
onModVersionLoaded: (url, data, status, xhr)->
try
modVersion = @loadModVersionData data
logger.info "loaded ModVersion from #{url}: #{modVersion}"
@trigger Event.load.succeeded, this, modVersion
@trigger Event.load.finished, this
@trigger Event.change, this
return modVersion
catch e
@onModVersionLoadFailed url, e, status, xhr
onModVersionLoadFailed: (url, error, status, xhr)->
message = if error.stack? then error.stack else error
logger.error "failed to load ModVersion from #{url}: #{message}"
@trigger Event.load.failed, this, error.message
@trigger Event.load.finished, this
return error
# Object Overrides #############################################################################
toString: ->
+26 -4
View File
@@ -5,16 +5,19 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{RequiredMods} = require '../constants'
BaseModel = require './base_model'
{Event} = require '../constants'
{RequiredMods} = require '../constants'
{Url} = require '../constants'
########################################################################################################################
module.exports = class ModVersion extends BaseModel
constructor: (attributes={}, options={})->
if _.isEmpty(attributes.name) then throw new Error 'name cannot be empty'
if _.isEmpty(attributes.version) then throw new Error 'version cannot be empty'
if not attributes.modPack? then throw new Error 'attributes.modPack is required'
if _.isEmpty(attributes.name) then throw new Error 'attributes.name cannot be empty'
if _.isEmpty(attributes.version) then throw new Error 'attributes.version cannot be empty'
attributes.description ?= ''
attributes.enabled ?= true
@@ -23,6 +26,8 @@ module.exports = class ModVersion extends BaseModel
attributes.slug ?= _.slugify attributes.name
super attributes, options
@modPack.addModVersion this
# Public Methods ###############################################################################
addItem: (item)->
@@ -31,6 +36,9 @@ module.exports = class ModVersion extends BaseModel
@items[item.slug] = item
@names[item.slug] = item.name
@trigger Event.add, item, this
@trigger Event.change, this
return this
compareTo: (that)->
@@ -62,6 +70,20 @@ module.exports = class ModVersion extends BaseModel
registerSlug: (slug, name)->
@names[slug] = name
@trigger Event.change + ':names', this, @names
@trigger Event.change, this
return this
# Backbone.Model Overrides #####################################################################
parse: (text)->
ModVersionParser = require './mod_version_parser' # to avoid require cycles
@_parser ?= new ModVersionParser modVersion:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return Url.modVersion modSlug:@slug, modVersion:@version
# Object Overrides #############################################################################
+11 -9
View File
@@ -5,9 +5,9 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
Logger = require '../logger'
V1 = require './mod_version_parsers/v1'
V2 = require './mod_version_parsers/v2'
Logger = require '../logger'
ModVersionParserV1 = require './parser_versions/mod_version_parser_v1'
ModVersionParserV2 = require './parser_versions/mod_version_parser_v2'
########################################################################################################################
@@ -16,11 +16,13 @@ module.exports = class ModVersionParser
@CURRENT_VERSION = '2'
constructor: (options={})->
if not options.modVersion? then throw new Error 'options.modVersion is required'
options.showAllErrors ?= false
@_modVersion = options.modVersion
@_parsers =
'1': new V1 options
'2': new V2 options
'1': new ModVersionParserV1 options
'2': new ModVersionParserV2 options
parse: (data)->
if not data? then throw new Error 'mod description data is missing'
@@ -33,14 +35,14 @@ module.exports = class ModVersionParser
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
oldLevel = logger.level
oldLevel = logger.level
logger.level = Logger.WARNING
result = parser.parse data
result = parser.parse data
logger.level = oldLevel
return result
return @_modVersion
unparse: (modVersion, dataVersion=ModVersionParser.CURRENT_VERSION)->
unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)->
if not modVersion? then throw new Error 'modVersion is required'
parser = @_parsers["#{dataVersion}"]
@@ -1,5 +1,5 @@
###
Crafting Guide - mod_version_parsers/v1.coffee
Crafting Guide - mod_version_parser_v1.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
@@ -12,27 +12,26 @@ Stack = require '../stack'
########################################################################################################################
module.exports = class V1
module.exports = class ModVersionParserV1
constructor: ->
constructor: (options={})->
if not options.modVersion? then throw new Error 'options.modVersion is required'
@_modVersion = options.modVersion
@_errorLocation = 'the header information'
parse: (data)->
return @_parseModVersion data
unparse: (modVersion)->
@modVersion = modVersion
text = @_unparseModVersion modVersion
@modVersion = null
return text
unparse: ->
return @_unparseModVersion()
# Private Methods ##############################################################################
_findOrCreateItem: (name)->
item = @modVersion.findItemByName name
item = @_modVersion.findItemByName name
if not item?
item = new Item modVersion:@modVersion, name:name
@modVersion.registerSlug item.slug, item.name
item = new Item modVersion:@_modVersion, name:name
@_modVersion.registerSlug item.slug, item.name
return item
_parseModVersion: (data)->
@@ -41,9 +40,12 @@ module.exports = class V1
if not data.version? then throw new Error 'version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
@modVersion = modVersion = new ModVersion name:data.name, version:data.version
modVersion.description = data.description or ''
if data.name isnt @_modVersion.name
throw new Error "the data is for #{data.name}, not #{@_modVersion.name} as expected"
if data.version isnt @_modVersion.version
throw new Error "the data is for version #{data.version}, not #{@_modVersion.version} as expected"
@_modVersion.description = data.description or ''
@_parseRawMaterials data.raw_materials
for index in [0...data.recipes.length]
@@ -52,8 +54,7 @@ module.exports = class V1
recipe = @_parseRecipe recipeData
recipe._originalIndex = index
@modVersion = null
return modVersion
return @_modVersion
_parseRawMaterials: (data)->
return unless data? and data.length > 0
@@ -101,7 +102,7 @@ module.exports = class V1
name = data[1]
slug = _.slugify name
@modVersion.registerSlug slug, name
@_modVersion.registerSlug slug, name
return new Stack itemSlug:slug, quantity:data[0]
@@ -190,9 +191,9 @@ module.exports = class V1
else if stackList.length is 1
stack = stackList[0]
if stack.quantity is 1
result.push '"' + @modVersion.findName(stack.itemSlug) + '"'
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"'
else
result.push '[[' + stack.quantity + ', "' + @modVersion.findName(stack.itemSlug) + '"]]'
result.push '[[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]]'
else
result.push '['
@@ -209,9 +210,9 @@ module.exports = class V1
for stack in stacks
result.push ', ' if not firstItem
if stack.quantity is 1
result.push '"' + @modVersion.findName(stack.itemSlug) + '"'
result.push '"' + @_modVersion.findName(stack.itemSlug) + '"'
else
result.push '[' + stack.quantity + ', "' + @modVersion.findName(stack.itemSlug) + '"]'
result.push '[' + stack.quantity + ', "' + @_modVersion.findName(stack.itemSlug) + '"]'
firstItem = false
result.push ']'
@@ -1,5 +1,5 @@
###
Crafting Guide - mod_version_parsers/v2.coffee
Crafting Guide - mod_version_parser_v2.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
@@ -13,11 +13,14 @@ StringBuilder = require '../string_builder'
########################################################################################################################
module.exports = class V2
module.exports = class ModVersionParserV2
constructor: (options={})->
if not options.modVersion? then throw new Error 'options.modVersion is required'
options.showAllErrors ?= false
@showAllErrors = options.showAllErrors
@_modVersion = options.modVersion
@_showAllErrors = options.showAllErrors
# Class Methods ################################################################################
@@ -44,12 +47,11 @@ module.exports = class V2
for command in commands
@_handleErrors @_execute, command
modVersion = @_handleErrors @_buildModVersion, @_modVersionData
return modVersion
return @_handleErrors @_buildModVersion, @_modVersionData, @_modVersion
unparse: (modVersion)->
builder = new StringBuilder context:modVersion
@_unparseModVersion builder, modVersion
unparse: ->
builder = new StringBuilder context:@_modVersion
@_unparseModVersion builder
return builder.toString()
# Private Methods ##############################################################################
@@ -61,7 +63,7 @@ module.exports = class V2
@_handleErrors method, command.args
_parseLine: (line)->
line = line.replace V2.COMMENT, '$1'
line = line.replace ModVersionParserV2.COMMENT, '$1'
line = line.trim()
return [] if line.length is 0
@@ -70,7 +72,7 @@ module.exports = class V2
for linePart in lineParts
continue if linePart.length is 0
match = V2.COMMAND.exec linePart
match = ModVersionParserV2.COMMAND.exec linePart
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
args = []
@@ -86,9 +88,8 @@ module.exports = class V2
callback.apply this, args
catch e
e.message = "line #{@_lineNumber}: #{e.message}"
if not @showAllErrors then throw e
console.error e.message
if not @_showAllErrors then throw e
logger.error e.message
# Command Methods ##############################################################################
@@ -102,7 +103,7 @@ module.exports = class V2
@_recipeData.extras = []
for term in extraTerms
match = V2.STACK.exec term
match = ModVersionParserV2.STACK.exec term
if match?
@_recipeData.extras.push quantity:parseInt(match[1]), name:match[2]
else
@@ -142,7 +143,7 @@ module.exports = class V2
_command_pattern: (pattern='')->
if not @_recipeData? then throw new Error 'cannot declare "pattern" before "recipe"'
if @_recipeData.pattern? then throw new Error 'duplicate declaration of "pattern"'
if not V2.PATTERN.test pattern
if not ModVersionParserV2.PATTERN.test pattern
throw new Error 'a pattern must have 9 digits using 0-9 for items and "." for an empty spot;
spaces are optional'
@@ -151,7 +152,7 @@ module.exports = class V2
_command_quantity: (quantity)->
if not @_recipeData? then throw new Error 'cannot declare "quantity" before "recipe"'
if @_recipeData.quantity? then throw new Error 'duplicate declaration of "quantity"'
if not V2.INTEGER.test(quantity) then throw new Error 'quantity must be an integer'
if not ModVersionParserV2.INTEGER.test(quantity) then throw new Error 'quantity must be an integer'
@_recipeData.quantity = parseInt quantity
@@ -181,18 +182,21 @@ module.exports = class V2
# Object Creation Methods ######################################################################
_buildModVersion: (modVersionData)->
_buildModVersion: (modVersionData, modVersion)->
if not modVersionData.name? then throw new Error 'the "name" declaration is required'
if not modVersionData.version? then throw new Error 'the "version" declaration is required'
if modVersionData.name isnt modVersion.name
throw new Error "modVersionData name (#{modVersionData.name})
must match the ModVersion (#{modVersion.name})"
if modVersionData.version isnt modVersion.version
throw new Error "modVersionData version (#{modVersionData.version})
must match the ModVersion (#{modVersion.version})"
modVersionData.description ?= ''
modVersionData.items ?= []
attributes =
name: modVersionData.name
version: modVersionData.version
description: modVersionData.description
modVersion = new ModVersion attributes
modVersion.description = modVersionData.description
for itemData in modVersionData.items
@_handleErrors @_buildItem, modVersion, itemData
@@ -263,15 +267,15 @@ module.exports = class V2
# Un-parsing Methods ###########################################################################
_unparseModVersion: (builder, modVersion)->
itemList = _.values modVersion.items
_unparseModVersion: (builder)->
itemList = _.values @modVersion.items
itemList.sort (a, b)-> a.compareTo b
builder
.line 'schema: ', 2
.line 'name: ', modVersion.name
.line 'version: ', modVersion.version
.onlyIf modVersion.description?, => builder.line 'description: ', modVersion.description
.line 'name: ', @modVersion.name
.line 'version: ', @modVersion.version
.onlyIf @modVersion.description?, => builder.line 'description: ', @modVersion.description
.line()
.onlyIf itemList.length > 0, =>
builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)