Refactor ModVersion into Mod and ModVersion

* Remove V1 of the mod version parser and shift V2 up to be V1
* Add a parser for mod.cg files, and refactor the the mod-version.cg
  files to push the appropriate content up to the mod.cg
* Remove the concept of "silent" from BaseModel along with logging
  each event as they are fired (which is what really prompted the
  need for it in the first place)
* Move use of the Storage class to be completely the province of the
  controllers most closely related to models affected
* Convert the ModVersionController to the ModController and have it
  take on responsibility for choosing the mod version
* Update BaseModel to support simple accessors for the current state
* Update CraftingPlan to be able to remove uncraftable items
* Implement the Mod model
This commit is contained in:
Andrew Miner
2015-01-18 10:48:12 -08:00
parent 76dc863372
commit d1621bb9f6
39 changed files with 922 additions and 1176 deletions
+7
View File
@@ -0,0 +1,7 @@
schema: 1
author: AlgorithmX2
description: A technical mod which adds an advanced, computer-based storage system
url: http://www.mod-buildcraft.com
version: rv1-stable-1
@@ -1,7 +1,4 @@
schema: 2 schema: 1
name: Applied Energistics 2
version: rv1-stable-1
description: Crafting recipes from Applied Energistics, by AlgorithmX2
item: 128³ Spatial Component item: 128³ Spatial Component
recipe: recipe:
+1 -4
View File
@@ -1,7 +1,4 @@
schema: 2 schema: 1
name: Buildcraft
version: 6.2.6
description: Crafting recipes from BuildCraft, by SpaceToad
item: Advanced Crafting Table item: Advanced Crafting Table
recipe: recipe:
+7
View File
@@ -0,0 +1,7 @@
schema: 1
author: Spacetoad
description: A technical mod which adds power generation, mining, and building systems
url: http://www.mod-buildcraft.com
version: 6.2.6
@@ -1,7 +1,4 @@
schema: 2 schema: 1
name: IC2 Classic
version: 1.111.170-lf
description: Crafting recipes from IndustrialCraft, by Alblaka
item: 10K Cooling Cell item: 10K Cooling Cell
recipe: recipe:
+7
View File
@@ -0,0 +1,7 @@
schema: 1
author: Alblaka
description: A technical mod adding multi-tiered power systems, mining and ore processing, and powered armor
url: http://http://wiki.industrial-craft.net/
version: 1.111.170-lf
+1 -4
View File
@@ -1,7 +1,4 @@
schema: 2 schema: 1
name: Minecraft
version: 1.7.10
description: Crafting recipes from vanilla Minecraft
item: Acacia Wood Planks item: Acacia Wood Planks
recipe: recipe:
+7
View File
@@ -0,0 +1,7 @@
schema: 1
author: Mojang
description: The basic game by itself
url: http://www.minecraft.net
version: 1.7.10
+3 -11
View File
@@ -5,14 +5,8 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
exports.DefaultModVersions = [ # Minecraft must be first
# Current version of Minecraft must be first exports.DefaultMods = [ 'Minecraft', 'Applied Energistics 2', 'Buildcraft', 'IC2 Classic' ]
{ name:'Minecraft', version:'1.7.10' }
{ name:'Applied Energistics 2', version:'rv1-stable-1' }
{ name:'Buildcraft', version:'6.2.6' }
{ name:'IC2 Classic', version:'1.111.170-lf' }
]
exports.Duration = Duration = {} exports.Duration = Duration = {}
Duration.snap = 100 Duration.snap = 100
@@ -41,9 +35,7 @@ exports.Opacity = Opacity = {}
Opacity.hidden = 1e-6 Opacity.hidden = 1e-6
Opacity.shown = 1 Opacity.shown = 1
exports.RequiredMods = [ exports.RequiredMods = [ 'minecraft' ]
'Minecraft'
]
exports.ModelState = ModelState = {} exports.ModelState = ModelState = {}
ModelState.unloaded = 'unloaded' ModelState.unloaded = 'unloaded'
@@ -105,7 +105,7 @@ module.exports = class BaseController extends Backbone.View
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "#{@constructor.name}(#{@cid})" return "#{@constructor.name}.#{@cid}"
# Private Methods ############################################################################## # Private Methods ##############################################################################
@@ -25,8 +25,6 @@ module.exports = class CraftingTableController extends BaseController
@imageLoader = options.imageLoader @imageLoader = options.imageLoader
@modPack = options.modPack @modPack = options.modPack
@model.plan.silent = false
# Event Methods ################################################################################ # Event Methods ################################################################################
onNextClicked: -> onNextClicked: ->
@@ -26,8 +26,8 @@ module.exports = class ItemPageController extends BaseController
options.templateName = 'item_page' options.templateName = 'item_page'
super options super options
@imageLoader = options.imageLoader @_imageLoader = options.imageLoader
@storage = options.storage @_storage = options.storage
# Event Methods ################################################################################ # Event Methods ################################################################################
@@ -37,23 +37,21 @@ module.exports = class ItemPageController extends BaseController
# BaseController Overrides ##################################################################### # BaseController Overrides #####################################################################
onWillRender: -> onWillRender: ->
@storage.register 'crafting-plan', @model.plan, 'includingTools' @_storage.register 'crafting-plan', @model.plan, 'includingTools'
@model.modPack.on Event.add, (modVersion)=>
@storage.register "mod-version:#{modVersion.slug}", modVersion, 'enabled'
super super
onDidRender: -> onDidRender: ->
@wantController = @addChild InventoryController, '.want', @wantController = @addChild InventoryController, '.want',
editable: true editable: true
icon: '/images/fishing_rod.png' icon: '/images/fishing_rod.png'
imageLoader: @imageLoader imageLoader: @_imageLoader
model: @model.plan.want model: @model.plan.want
modPack: @model.modPack modPack: @model.modPack
title: 'Items you want' title: 'Items you want'
@haveController = @addChild InventoryController, '.have', @haveController = @addChild InventoryController, '.have',
editable: true, editable: true,
imageLoader: @imageLoader imageLoader: @_imageLoader
model: @model.plan.have model: @model.plan.have
modPack: @model.modPack modPack: @model.modPack
nameFinder: new NameFinder @model.modPack, includeGatherable:true nameFinder: new NameFinder @model.modPack, includeGatherable:true
@@ -62,17 +60,20 @@ module.exports = class ItemPageController extends BaseController
@needController = @addChild InventoryController, '.need', @needController = @addChild InventoryController, '.need',
editable: false editable: false
icon: '/images/boots.png' icon: '/images/boots.png'
imageLoader: @imageLoader imageLoader: @_imageLoader
model: @model.plan.need model: @model.plan.need
modPack: @model.modPack modPack: @model.modPack
title: "Items you'll need" title: "Items you'll need"
@craftingTableController = @addChild CraftingTableController, '.view__crafting_table', @craftingTableController = @addChild CraftingTableController, '.view__crafting_table',
imageLoader: @imageLoader imageLoader: @_imageLoader
model: @model.table model: @model.table
modPack: @model.modPack modPack: @model.modPack
@modPackController = @addChild ModPackController, '.view__mod_pack', model:@model.modPack @modPackController = @addChild ModPackController, '.view__mod_pack',
model: @model.modPack
plan: @model.plan
storage: @_storage
@$('.want .toolbar').append '<label><input class="includeTools" type="checkbox"> include tools</label>' @$('.want .toolbar').append '<label><input class="includeTools" type="checkbox"> include tools</label>'
@$includeToolsBox = @$('.includeTools') @$includeToolsBox = @$('.includeTools')
@@ -0,0 +1,69 @@
###
Crafting Guide - mod_controller.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
{Event} = require '../constants'
Mod = require '../models/mod'
{RequiredMods} = require '../constants'
########################################################################################################################
module.exports = class ModController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
if not options.plan? then throw new Error 'options.plan is required'
options.templateName = 'mod'
super options
@_plan = options.plan
@_storage = options.storage
# Event Methods ################################################################################
onEnabledChanged: ->
return unless @rendered
enabled = @$(':checked').length > 0
if enabled
@model.activeVersion = Mod.Version.Latest
else
@model.activeVersion = Mod.Version.None
@_plan.removeUncraftableItems()
# BaseController Overrides #####################################################################
onWillRender: ->
if not @model.isLoaded then @model.fetch()
if @_storage? then @_storage.register "mod:#{@model.slug}", @model, 'activeVersion'
@model.on Event.change + ':activeModVersion', (mod, modVersion)=>
if modVersion? and not modVersion.isLoaded then modVersion.fetch()
onDidRender: ->
@$enabled = @$('td:nth-child(1) input')
@$name = @$('td:nth-child(2) p')
@$description = @$('td:nth-child(3) p')
super
refresh: ->
if @model.slug in RequiredMods
@$enabled.attr 'checked', 'checked'
@$enabled.attr 'disabled', 'disabled'
else
@$enabled.removeAttr 'disabled'
if @model.activeVersion is Mod.Version.None
@$enabled.removeAttr 'checked'
else
@$enabled.attr 'checked', 'checked'
@$name.html "#{@model.name}"
@$description.html "#{@model.description}"
# Backbone.View Overrides ######################################################################
events:
'change input[type="checkbox"]': 'onEnabledChanged'
@@ -6,21 +6,24 @@ All rights reserved.
### ###
BaseController = require './base_controller' BaseController = require './base_controller'
{DefaultModVersions} = require '../constants' {DefaultMods} = require '../constants'
{Duration} = require '../constants' {Duration} = require '../constants'
ModVersion = require '../models/mod_version' Mod = require '../models/mod'
ModVersionController = require './mod_version_controller' ModController = require './mod_controller'
######################################################################################################################## ########################################################################################################################
module.exports = class ModPackController extends BaseController module.exports = class ModPackController extends BaseController
constructor: (options={})-> constructor: (options={})->
if not options.model? then throw new Error "options.model is required" if not options.model? then throw new Error 'options.model is required'
if not options.plan? then throw new Error 'options.plan is required'
options.templateName = 'mod_pack' options.templateName = 'mod_pack'
super options super options
@_controllers = [] @_controllers = []
@_plan = options.plan
@_storage = options.storage
# Event Methods ################################################################################ # Event Methods ################################################################################
@@ -31,10 +34,8 @@ module.exports = class ModPackController extends BaseController
# BaseController Overrides ##################################################################### # BaseController Overrides #####################################################################
onWillRender: -> onWillRender: ->
for attributes in DefaultModVersions for name in DefaultMods
modVersion = new ModVersion attributes @model.addMod new Mod name:name
modVersion.fetch()
@model.addModVersion modVersion
onDidRender: -> onDidRender: ->
@$table = @$('table') @$table = @$('table')
@@ -48,14 +49,14 @@ module.exports = class ModPackController extends BaseController
return return
index = 0 index = 0
modVersions = @model.getModVersions() mods = @model.getMods()
while index < Math.min @_controllers.length, modVersions.length while index < Math.min @_controllers.length, mods.length
controller = @_controllers[index] controller = @_controllers[index]
controller.model = modVersions[index] controller.model = mods[index]
index++ index++
while @_controllers.length < modVersions.length while @_controllers.length < mods.length
controller = new ModVersionController model:modVersions[index] controller = new ModController model:mods[index], plan:@_plan, storage:@_storage
controller.render() controller.render()
@_controllers.push controller @_controllers.push controller
controller.$el.hide duration:0 controller.$el.hide duration:0
@@ -63,7 +64,7 @@ module.exports = class ModPackController extends BaseController
controller.$el.slideDown duration:Duration.normal controller.$el.slideDown duration:Duration.normal
index++ index++
while @_controllers.length > modVersions.length while @_controllers.length > mods.length
controller = @_controllers.pop() controller = @_controllers.pop()
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove() controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
@@ -1,49 +0,0 @@
###
Crafting Guide - mod_version_controller.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
{RequiredMods} = require '../constants'
########################################################################################################################
module.exports = class ModVersionController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error "options.model is required"
options.templateName = 'mod_version'
super options
# Event Methods ################################################################################
onEnabledChanged: ->
return unless @rendered
@model.enabled = @$(':checked').length > 0
# BaseController Overrides #####################################################################
onDidRender: ->
@$enabled = @$('td:nth-child(1) input')
@$name = @$('td:nth-child(2) p')
@$description = @$('td:nth-child(3) p')
super
refresh: ->
if @model.name in RequiredMods
@$enabled.attr 'checked', 'checked'
@$enabled.attr 'disabled', 'disabled'
else
@$enabled.removeAttr 'disabled'
if @model.enabled then @$enabled.attr('checked', 'checked') else @$enabled.removeAttr('checked')
@$name.html "#{@model.name} (#{@model.version})"
@$description.html "#{@model.description}"
# Backbone.View Overrides ######################################################################
events:
'change input[type="checkbox"]': 'onEnabledChanged'
+11 -12
View File
@@ -13,7 +13,6 @@ All rights reserved.
module.exports = class BaseModel extends Backbone.Model module.exports = class BaseModel extends Backbone.Model
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
options.silent ?= true
super attributes, options super attributes, options
makeGetter = (name)-> return -> @get name makeGetter = (name)-> return -> @get name
@@ -22,7 +21,6 @@ module.exports = class BaseModel extends Backbone.Model
continue if name is 'id' continue if name is 'id'
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name) Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
@silent = options.silent
@state = ModelState.unloaded @state = ModelState.unloaded
@on 'request', => @state = ModelState.loading @on 'request', => @state = ModelState.loading
@@ -31,27 +29,33 @@ module.exports = class BaseModel extends Backbone.Model
@loading = null @loading = null
# Public Methods ############################################################################### Object.defineProperties this,
isUnloaded: { get:-> @state is ModelState.unloaded }
isLoading: { get:-> @state is ModelState.loading }
isLoaded: { get:-> @state is ModelState.loaded }
isError: { get:-> @state is ModelState.error }
# Event Methods ################################################################################ # Event Methods ################################################################################
onLoadSucceeded: (text, status, xhr)-> onLoadSucceeded: (text, status, xhr)->
try try
@set @parse text @set @parse text
@trigger Event.change, this
@trigger Event.sync, this, text @trigger Event.sync, this, text
logger.info "#{@constructor.name}.#{@cid} loaded successfully"
catch e catch e
logger.error "A parsing error occured: #{e.stack}" logger.error "A parsing error occured: #{e.stack}"
@onLoadFailed e.message, 'parsing failed', xhr @onLoadFailed e.message, 'parsing failed', xhr
onLoadFailed: (error, status, xhr)-> onLoadFailed: (error, status, xhr)->
logger.error "#{@constructor.name} (#{@cid}) failed to load: status:#{status}, message:#{error}" logger.error "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
@trigger Event.error, this, error @trigger Event.error, this, error
# Backbone.Model Overrides ##################################################################### # Backbone.Model Overrides #####################################################################
fetch: -> fetch: ->
url = @url() url = @url()
logger.info "#{@constructor.name} (#{@cid}) reading from url: #{url}" logger.info "#{@constructor.name}.#{@cid} reading from url: #{url}"
@trigger Event.request, this @trigger Event.request, this
@loading = w.promise (resolve, reject)=> @loading = w.promise (resolve, reject)=>
@@ -68,14 +72,9 @@ module.exports = class BaseModel extends Backbone.Model
return JSON.parse text return JSON.parse text
sync: (method, model)-> sync: (method, model)->
throw new Error "#{@constructor.name} (#{@cid}) is not permitted to #{method}" throw new Error "#{@constructor.name}.#{@cid} is not permitted to #{method}"
trigger: (name)->
return if @silent
logger.trace "#{@constructor.name}.#{@cid} triggered a \"#{name}\" event"
super
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "#{@constructor.name} (#{@cid})" return "#{@constructor.name}.#{@cid}"
+13 -8
View File
@@ -23,29 +23,26 @@ module.exports = class CraftingPlan extends BaseModel
@need = new Inventory @need = new Inventory
@result = new Inventory @result = new Inventory
@clear silent:true @clear()
@have.on Event.change, => @craft() @have.on Event.change, => @craft()
@want.on Event.change, => @craft() @want.on Event.change, => @craft()
@modPack.on Event.add, => @craft() @modPack.on Event.change, => @craft()
@on Event.change + ':includingTools', => @craft() @on Event.change + ':includingTools', => @craft()
# Public Methods ############################################################################### # Public Methods ###############################################################################
clear: (options={})-> clear: (options={})->
options.silent ?= false
@steps = [] @steps = []
@need.clear() @need.clear()
@result.clear() @result.clear()
@trigger 'change', this unless options.silent @trigger 'change', this
return this return this
craft: -> craft: ->
@clear silent:true @clear()
@need.silent = @result.silent = true
@result.addInventory @have @result.addInventory @have
@@ -59,11 +56,19 @@ module.exports = class CraftingPlan extends BaseModel
@_removeExtraSteps() @_removeExtraSteps()
@result.addInventory @want @result.addInventory @want
@need.silent = @result.silent = false
@need.trigger 'change', @need @need.trigger 'change', @need
@result.trigger 'change', @result @result.trigger 'change', @result
@trigger 'change', this @trigger 'change', this
removeUncraftableItems: ->
toRemove = []
@want.each (stack)=>
item = @modPack.findItem stack.slug
if not item? then toRemove.push stack.slug
for slug in toRemove
@want.remove slug
# Event Methods ################################################################################ # Event Methods ################################################################################
onIncludingToolsChanged: -> onIncludingToolsChanged: ->
+5 -6
View File
@@ -15,7 +15,7 @@ module.exports = class Inventory extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
super attributes, options super attributes, options
@clear silent:true @clear()
Object.defineProperties this, Object.defineProperties this,
isEmpty: { get:-> @_slugs.length is 0 } isEmpty: { get:-> @_slugs.length is 0 }
@@ -29,19 +29,16 @@ module.exports = class Inventory extends BaseModel
return this return this
addInventory: (inventory)-> addInventory: (inventory)->
@silent = true
inventory.each (stack)=> @_add stack.slug, stack.quantity inventory.each (stack)=> @_add stack.slug, stack.quantity
@silent = false
@trigger Event.change, this @trigger Event.change, this
return this return this
clear: (options={})-> clear: (options={})->
options.silent ?= false
@_stacks = {} @_stacks = {}
@_slugs = [] @_slugs = []
@trigger Event.change, this unless options.silent @trigger Event.change, this
clone: -> clone: ->
inventory = new Inventory inventory = new Inventory
@@ -75,11 +72,13 @@ module.exports = class Inventory extends BaseModel
return 0 unless stack? return 0 unless stack?
return stack.quantity return stack.quantity
remove: (slug, quantity=1)-> remove: (slug, quantity=null)->
return if quantity is 0 return if quantity is 0
stack = @_stacks[slug] stack = @_stacks[slug]
if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory" if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
quantity ?= stack.quantity
if stack.quantity < quantity if stack.quantity < quantity
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{slug} in this inventory" throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{slug} in this inventory"
+1 -1
View File
@@ -22,7 +22,7 @@ module.exports = class ItemPage extends BaseModel
attributes.table ?= new CraftingTable plan:attributes.plan attributes.table ?= new CraftingTable plan:attributes.plan
super attributes, options super attributes, options
@modPack.on Event.add, (modVersion)=> modVersion.once 'sync', => @_consumeParams() @modPack.on Event.add + ':mod', (mod)=> mod.on 'sync', => @_consumeParams()
@plan.on Event.change, => @_updateLocation() @plan.on Event.change, => @_updateLocation()
# Private Methods ############################################################################## # Private Methods ##############################################################################
+122 -2
View File
@@ -6,6 +6,8 @@ All rights reserved.
### ###
BaseModel = require './base_model' BaseModel = require './base_model'
{Event} = require '../constants'
{RequiredMods} = require '../constants'
{Url} = require '../constants' {Url} = require '../constants'
######################################################################################################################## ########################################################################################################################
@@ -14,12 +16,130 @@ module.exports = class Mod extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required' if not attributes.name? then throw new Error 'attributes.name is required'
attributes.id = attributes.slug = _.slugify attributes.name attributes.author ?= ''
attributes.description ?= ''
attributes.primaryUrl ?= null
attributes.slug ?= _.slugify attributes.name
super attributes, options super attributes, options
@_activeModVersion = null
@_activeVersion = null
@_modVersions = []
Object.defineProperties this,
'activeModVersion': { get:-> @_activeModVersion }
'activeVersion': { get:@getActiveVersion, set:@setActiveVersion }
'enabled': { get:-> @_activeModVersion? }
# Class Methods ##################################################################################
@Version =
None: 'none'
Latest: 'latest'
# Public Methods #################################################################################
compareTo: (that)->
thisRequired = this.slug in RequiredMods
thatRequired = that.slug in RequiredMods
if thisRequired isnt thatRequired
return -1 if thisRequired
return +1 if thatRequired
else
if this.name isnt that.name
return if this.name < that.name then -1 else +1
return 0
# ModVersion Proxy Methods #####################################################################
eachItem: (callback)->
return unless @_activeModVersion?
@_activeModVersion.eachItem callback
eachName: (callback)->
return unless @_activeModVersion?
@_activeModVersion.eachName callback
findItem: (slug)->
return unless @_activeModVersion?
@_activeModVersion.findItem slug
findItemByName: (name)->
return unless @_activeModVersion?
@_activeModVersion.findItemByName name
findName: (slug)->
return unless @_activeModVersion?
@_activeModVersion.findName slug
# Property Methods #############################################################################
addModVersion: (modVersion)->
return unless modVersion?
return if @_modVersions.indexOf(modVersion) isnt -1
@_modVersions.push modVersion
@listenTo modVersion, Event.change, => @trigger Event.change, this
modVersion.mod = this
@trigger Event.add + ':modVersion', modVersion, this
@trigger Event.change + ':version', modVersion, this
@trigger Event.change, this
if not @activeVersion? then @activeVersion = modVersion.version
if modVersion.version is @_activeVersion then @_activateModVersion modVersion
return this
eachModVersion: (callback)->
for modVersion in @_modVersions
callback modVersion
getActiveVersion: ->
return @_activeVersion
setActiveVersion: (version)->
version ?= Mod.Version.None
if version is Mod.Version.Latest then version = _.last(@_modVersions).version
if version is Mod.Version.None
@_activeVersion = version
@_activateModVersion null
@trigger Event.change + ':activeVersion', this, @_activeVersion
@trigger Event.change, this
else
for modVersion in @_modVersions
if version is modVersion.version
@_activateModVersion modVersion
break
@_activeVersion = version
@trigger Event.change + ':activeVersion', this, @_activeVersion
@trigger Event.change, this
# Backbone.View Overrides ###################################################################### # Backbone.View Overrides ######################################################################
parse: (response)-> parse: (text)->
ModParser = require './mod_parser' # to avoid require cycles
@_parser ?= new ModParser model:this
@_parser.parse text
return null # prevent calling `set`
url: -> url: ->
return Url.mod modSlug:@slug return Url.mod modSlug:@slug
# Private Methods ##############################################################################
_activateModVersion: (modVersion)->
if @_activeModVersion? then @stopListening @_activeModVersion
@_activeModVersion = modVersion
@trigger Event.change + ':activeModVersion', this, @_activeModVersion
logger.verbose "#{@name} switched to version #{@_activeVersion}"
if @_activeModVersion?
@listenTo @_activeModVersion, 'all', -> @trigger.apply this, arguments
+34 -39
View File
@@ -18,52 +18,47 @@ module.exports = class ModPack extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
super attributes, options super attributes, options
@_modVersions = [] @_mods = []
# Public Methods ############################################################################### # Public Methods ###############################################################################
findItem: (slug, options={})-> findItem: (slug)->
options.includeDisabled ?= false for mod in @_mods
continue unless mod.enabled
for modVersion in @_modVersions item = mod.findItem slug
continue unless modVersion.enabled or options.includeDisabled
item = modVersion.findItem slug
return item if item? return item if item?
return null return null
findItemByName: (name, options={})-> findItemByName: (name)->
options.includeDisabled ?= false
slug = _.slugify name slug = _.slugify name
for modVersion in @_modVersions for mod in @_mods
continue unless modVersion.enabled or options.includeDisabled continue unless mod.enabled?
item = modVersion.findItem slug item = mod.findItem slug
return item if item? return item if item?
return null return null
findName: (slug, options={})-> findName: (slug)->
options.includeDisabled ?= false for mod in @_mods
continue unless mod.enabled
for modVersion in @_modVersions name = mod.findName slug
continue unless modVersion.enabled or options.includeDisabled
name = modVersion.findName slug
return name if name return name if name
return null return null
findItemDisplay: (slug)-> findItemDisplay: (slug)->
result = {} result = {}
item = @findItem slug, includeDisabled:true item = @findItem slug
if item? if item?
result.modSlug = item.modVersion.slug result.modSlug = item.modVersion.modSlug
result.modVersion = item.modVersion.version result.modVersion = item.modVersion.version
result.slug = item.slug result.slug = item.slug
result.itemName = item.name result.itemName = item.name
else else
result.modSlug = _.slugify DefaultModVersions[0].name result.modSlug = @_mods[0].slug
result.modVersion = DefaultModVersions[0].version result.modVersion = @_mods[0].activeVersion
result.slug = slug result.slug = slug
result.itemName = @findName slug, includeDisabled:true result.itemName = @findName slug, includeDisabled:true
@@ -71,38 +66,38 @@ module.exports = class ModPack extends BaseModel
result.itemUrl = Url.item result result.itemUrl = Url.item result
return result return result
isValidName: (name, options={})-> isValidName: (name)->
options.includeDisabled ?= false
slug = _.slugify name slug = _.slugify name
for modVersion in @_modVersions for mod in @_mods
continue unless modVersion.enabled or options.includeDisabled continue unless mod.enabled
name = modVersion.findName slug name = mod.findName slug
return true if name return true if name
return false return false
# Property Methods ############################################################################# # Property Methods #############################################################################
addModVersion: (modVersion)-> addMod: (mod)->
return if @_modVersions.indexOf(modVersion) isnt -1 return if @_mods.indexOf(mod) isnt -1
@_modVersions.push modVersion @_mods.push mod
@trigger Event.add, modVersion, this @listenTo mod, Event.change, => @trigger Event.change, this
@trigger Event.add + ':mod', mod, this
@_modVersions.sort (a, b)-> a.compareTo b @_mods.sort (a, b)-> a.compareTo b
@trigger Event.sort + ':mod', this
@trigger Event.change, this @trigger Event.change, this
return this return this
eachModVersion: (callback)-> eachMod: (callback)->
for modVersion in @_modVersions for mod in @_mods
callback modVersion callback mod
getModVersions: -> getMods: ->
return @_modVersions[..] return @_mods[..]
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "ModPack (#{@cid}) {modVersions:#{@_modVersions.length} items}" return "ModPack (#{@cid}) {modVersions:«#{@_mods.length} items»}"
+19
View File
@@ -0,0 +1,19 @@
###
Crafting Guide - mod_parser.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
VersionedParserBase = require './versioned_parser_base'
ModParserV1 = require './parser_versions/mod_parser_v1'
########################################################################################################################
module.exports = class ModParser extends VersionedParserBase
# VersionedParserBase Overrides ################################################################
_createParsers: (options)->
return result =
'1': new ModParserV1 options
+8 -27
View File
@@ -17,12 +17,8 @@ Item = require './item'
module.exports = class ModVersion extends BaseModel module.exports = class ModVersion extends BaseModel
constructor: (attributes={}, options={})-> constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required' if not attributes.modSlug? then throw new Error 'attributes.modSlug is required'
if not attributes.version? then throw new Error 'attributes.version is required' if not attributes.version? then throw new Error 'attributes.version is required'
attributes.description ?= ''
attributes.enabled ?= true
attributes.slug ?= _.slugify attributes.name
super attributes, options super attributes, options
@_items = {} @_items = {}
@@ -40,19 +36,13 @@ module.exports = class ModVersion extends BaseModel
return this return this
compareTo: (that)-> compareTo: (that)->
if this.name is that.name then return 0 if this.mod? and that.mod?
return this.mod.compareTo that.mod
thisRequired = this.name in RequiredMods if this.modSlug isnt that.modSlug
thatRequired = that.name in RequiredMods return if this.modSlug < that.modSlug then -1 else +1
if thisRequired and thatRequired return 0
return if this.name < that.name then -1 else +1
else if thisRequired
return -1
else if thatRequired
return +1
else
return if this.name < that.name then -1 else +1
eachItem: (callback)-> eachItem: (callback)->
for slug in @_slugs for slug in @_slugs
@@ -87,27 +77,18 @@ module.exports = class ModVersion extends BaseModel
# Backbone.Model Overrides ##################################################################### # Backbone.Model Overrides #####################################################################
parse: (text)-> parse: (text)->
currentSilent = @silent
@silent = true
ModVersionParser = require './mod_version_parser' # to avoid require cycles ModVersionParser = require './mod_version_parser' # to avoid require cycles
@_parser ?= new ModVersionParser model:this @_parser ?= new ModVersionParser model:this
@_parser.parse text @_parser.parse text
@silent = currentSilent
@trigger Event.change, this
return null # prevent calling `set` return null # prevent calling `set`
url: -> url: ->
return Url.modVersion modSlug:@slug, modVersion:@version return Url.modVersion modSlug:@modSlug, modVersion:@version
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "ModVersion (#{@cid}) { return "ModVersion (#{@cid}) {
enabled:#{@enabled}, modSlug:#{@modSlug}, version:#{@version}, items:#{_.keys(@_items).length} items
name:#{@name},
version:#{@version},
items:#{_.keys(@_items).length} items
}" }"
+5 -44
View File
@@ -5,54 +5,15 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
Logger = require '../logger' VersionedParserBase = require './versioned_parser_base'
ModVersionParserV1 = require './parser_versions/mod_version_parser_v1' ModVersionParserV1 = require './parser_versions/mod_version_parser_v1'
ModVersionParserV2 = require './parser_versions/mod_version_parser_v2'
######################################################################################################################## ########################################################################################################################
module.exports = class ModVersionParser module.exports = class ModVersionParser extends VersionedParserBase
@CURRENT_VERSION = '2' # VersionedParserBase Overrides ################################################################
constructor: (options={})-> _createParsers: (options)->
if not options.model? then throw new Error 'options.model is required' return result =
options.showAllErrors ?= false
@_model = options.model
@_parsers =
'1': new ModVersionParserV1 options '1': new ModVersionParserV1 options
'2': new ModVersionParserV2 options
parse: (data)->
if not data? then throw new Error 'mod description data is missing'
if @_isJson data
parser = @_parsers['1']
data = JSON.parse data
else
parser = @_parsers['2']
if not parser? then throw new Error "cannot parse version #{data.dataVersion} mod descriptions"
parser.parse data
return @_model
unparse: (dataVersion=ModVersionParser.CURRENT_VERSION)->
if not modVersion? then throw new Error 'modVersion is required'
parser = @_parsers["#{dataVersion}"]
if not parser? then throw new Error "version #{dataVersion} is not supported"
return parser.unparse modVersion
# Private Methods ##############################################################################
_isJson: (data)->
i = 0
while i < data.length
continue if data[i] is '\n'
continue if data[i] is '\r'
return true if data[i] is '{'
return false
+7 -7
View File
@@ -31,7 +31,7 @@ module.exports = class NameFinder
names = @_findNames nameHint names = @_findNames nameHint
names.sort (a, b)-> names.sort (a, b)->
c = a.modVersion.compareTo b.modVersion c = a.mod.compareTo b.mod
if c isnt 0 then return c if c isnt 0 then return c
return 0 if a.label is b.label return 0 if a.label is b.label
@@ -59,21 +59,21 @@ module.exports = class NameFinder
names = [] names = []
nameMap = {} nameMap = {}
@modPack.eachModVersion (modVersion)=> @modPack.eachMod (mod)=>
return unless modVersion.enabled or @includeDisabledMods return unless mod.enabled or @includeDisabledMods
modVersion.eachName (name, slug)=> mod.eachName (name, slug)=>
return if nameMap[name] return if nameMap[name]
item = modVersion.findItem slug item = mod.findItem slug
if not @includeGatherable if not @includeGatherable
return unless item? and (not item.isGatherable) return unless item? and (not item.isGatherable)
scanName = "#{modVersion.name} : #{name}" scanName = "#{mod.name} : #{name}"
if nameHint? if nameHint?
return unless @_isMatch scanName.toLowerCase(), nameHint return unless @_isMatch scanName.toLowerCase(), nameHint
nameMap[name] = name nameMap[name] = name
names.push value:name, label:scanName, modVersion:modVersion names.push value:name, label:scanName, mod:mod
return names return names
@@ -1,5 +1,5 @@
### ###
Crafting Guide - command_parser_base.coffee Crafting Guide - command_parser_version_base.coffee
Copyright (c) 2015 by Redwood Labs Copyright (c) 2015 by Redwood Labs
All rights reserved. All rights reserved.
@@ -7,7 +7,7 @@ All rights reserved.
######################################################################################################################## ########################################################################################################################
module.exports = class CommandParserBase module.exports = class CommandParserVersionBase
constructor: (options={})-> constructor: (options={})->
if not options.model? then throw new Error 'options.model is required' if not options.model? then throw new Error 'options.model is required'
@@ -51,6 +51,8 @@ module.exports = class CommandParserBase
_unparseModel: (builder, model)-> _unparseModel: (builder, model)->
throw new Error 'Subclasses must override this method' throw new Error 'Subclasses must override this method'
_command_schema: -> # do nothing
# Private Methods ############################################################################## # Private Methods ##############################################################################
_execute: (command)-> _execute: (command)->
@@ -60,7 +62,7 @@ module.exports = class CommandParserBase
@_handleErrors method, command.args @_handleErrors method, command.args
_parseLine: (line)-> _parseLine: (line)->
line = line.replace CommandParserBase.COMMENT, '$1' line = line.replace CommandParserVersionBase.COMMENT, '$1'
line = line.trim() line = line.trim()
return [] if line.length is 0 return [] if line.length is 0
@@ -69,7 +71,7 @@ module.exports = class CommandParserBase
for linePart in lineParts for linePart in lineParts
continue if linePart.length is 0 continue if linePart.length is 0
match = CommandParserBase.COMMAND.exec linePart match = CommandParserVersionBase.COMMAND.exec linePart
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\"" if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
args = [] args = []
@@ -0,0 +1,63 @@
###
Crafting Guide - mod_parser_v2.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
CommandParserVersionBase = require './command_parser_version_base'
Mod = require '../mod'
ModVersion = require '../mod_version'
########################################################################################################################
module.exports = class ModParserV2 extends CommandParserVersionBase
# CommandParserVersionBase Overrides ###########################################################
_buildModel: (rawData, model)->
@_buildMod rawData, model
_unparseModel: (builder, model)->
@_unparseMod builder, model
# Command Methods ##############################################################################
_command_author: (authorParts...)->
if @_rawData.author? then throw new Error 'duplicate declaration of "author"'
author = authorParts.join ''
if author.length is 0 then throw new Error '"author" cannot be empty, but may be omitted'
@_rawData.author = author
_command_description: (descriptionParts...)->
if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
description = descriptionParts.join ', '
if description.length is 0 then throw new Error '"description" cannot be empty, but may be omitted'
@_rawData.description = description
_command_url: (url='')->
if @_rawData.url? then throw new Error 'duplicate declaration of "url"'
if url.length is 0 then throw new Error 'url cannot be empty'
@_rawData.url = url
_command_version: (version='')->
if version.length is 0 then throw new Error 'version cannot be empty'
@_rawData.versions ?= []
@_rawData.versions.push version
# Object Building Methods ######################################################################
_buildMod: (rawData, model)->
if not rawData.url? then throw new Error 'the "url" declaration is required'
if not rawData.versions? then throw new Error 'at least one "version" declaration is required'
model.author = rawData.author if rawData.author?
model.description = rawData.description if rawData.description?
model.primaryUrl = rawData.url
for version in rawData.versions
model.addModVersion new ModVersion modSlug:model.slug, version:version
@@ -1,243 +1,253 @@
### ###
Crafting Guide - mod_version_parser_v1.coffee Crafting Guide - mod_version_parser_v2.coffee
Copyright (c) 2014-2015 by Redwood Labs Copyright (c) 2014-2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
CommandParserVersionBase = require './command_parser_version_base'
Item = require '../item' Item = require '../item'
ModVersion = require '../mod_version' ModVersion = require '../mod_version'
Recipe = require '../recipe' Recipe = require '../recipe'
Stack = require '../stack' Stack = require '../stack'
StringBuilder = require '../string_builder'
######################################################################################################################## ########################################################################################################################
module.exports = class ModVersionParserV1 module.exports = class ModVersionParserV1 extends CommandParserVersionBase
constructor: (options={})-> # Class Methods ################################################################################
if not options.model? then throw new Error 'options.model is required'
@_model = options.model
@_errorLocation = 'the header information'
parse: (data)-> @INTEGER = /[0-9]+/
return @_parseModVersion data
unparse: -> @PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
return @_unparseModVersion()
# Private Methods ############################################################################## @STACK = /^([0-9]+) +(.*)$/
_computeDefaultPattern: (input)-> # CommandParserVersionBase Overrides ###########################################################
itemCount = input.length
slotCount = _.reduce input, ((total, stack)-> total + stack.quantity), 0
return '... .0. ...' if itemCount is 1 and slotCount is 1 _buildModel: (rawData, model)->
return '00. 00. ...' if itemCount is 1 and slotCount is 4 @_buildModVersion rawData, model
return '000 000 000' if itemCount is 1 and slotCount is 9
result = ['.', '.', '.', '.', '.', '.', '.', '.', '.'] _unparseModel: (builder, model)->
indexes = [4, 7, 1, 3, 5, 6, 8, 0, 2] @_unparseModVersion builder, model
for i in [0...input.length] # Command Methods ##############################################################################
stack = input[i]
for j in [0...stack.quantity]
index = indexes.shift()
result[index] = "#{i}"
pattern = result.join '' _command_extras: (extraTerms...)->
pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3' if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
return pattern if @_recipeData.extras? then throw new Error 'duplicate declaration of "extras"'
@_recipeData.extras = []
for term in extraTerms
match = ModVersionParserV1.STACK.exec term
if match?
@_recipeData.extras.push quantity:parseInt(match[1]), name:match[2]
else
@_recipeData.extras.push quantity:1, name:term
_command_gatherable: (gatherable)->
if not @_itemData? then throw new Error 'cannot declare "gatherable" before "item"'
if @_itemData.gatherable? then throw new Error 'duplicate declaration of "gatherable"'
if not (gatherable in ['yes', 'no']) then throw new Error 'gatherable must be either "yes" or "no"'
@_itemData.gatherable = (gatherable is 'yes')
_command_item: (name='')->
if not name.length > 0 then throw new Error 'the item name cannot be empty'
@_itemData = name:name, line:@_lineNumber
@_rawData.items ?= []
@_rawData.items.push @_itemData
@_recipeData = null
_command_input: (inputNames...)->
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
if @_recipeData.input? then throw new Error 'duplicate declaration of "input"'
@_recipeData.input = []
for name in inputNames
if name.length is 0 then throw new Error 'input names cannot be empty'
@_recipeData.input.push name
_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 ModVersionParserV1.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'
@_recipeData.pattern = pattern
_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 ModVersionParserV1.INTEGER.test(quantity) then throw new Error 'quantity must be an integer'
@_recipeData.quantity = parseInt quantity
_command_recipe: ->
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
@_recipeData = line:@_lineNumber
@_itemData.recipes ?= []
@_itemData.recipes.push @_recipeData
_command_tools: (toolNames...)->
if not @_recipeData? then throw new Error 'cannot declare "tools" before "recipe"'
if @_recipeData.tools? then throw new Error 'duplicate declaration of "tools"'
@_recipeData.tools = []
for name in toolNames
if name.length is 0 then throw new Error 'tool names cannot be empty'
@_recipeData.tools.push name
# Object Creation Methods ######################################################################
_buildModVersion: (modVersionData, modVersion)->
modVersionData.items ?= []
for itemData in modVersionData.items
@_handleErrors @_buildItem, modVersion, itemData
return modVersion
_buildItem: (modVersion, itemData)->
@_lineNumber = itemData.line
itemData.gatherable ?= false
itemData.recipes ?= []
item = new Item name:itemData.name, isGatherable:itemData.gatherable
modVersion.addItem item
for recipeData in itemData.recipes
@_handleErrors @_buildRecipe, modVersion, item, recipeData
_findOrCreateItem: (name)->
item = @_model.findItemByName name
if not item?
item = new Item name:name
@_model.addItem item
return item return item
# Parsing Methods ############################################################################## _buildRecipe: (modVersion, item, recipeData)->
@_lineNumber = recipeData.line
if not recipeData.input? then throw new Error 'the "input" declaration is required'
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
_parseModVersion: (data)-> recipeData.quantity ?= 1
if not data? then throw new Error 'mod description data is missing' recipeData.extras ?= []
if not data.name? then throw new Error 'name is required' recipeData.tools ?= []
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'
if data.name isnt @_model.name inputStacks = []
throw new Error "the data is for #{data.name}, not #{@_model.name} as expected" for name in recipeData.input
if data.version isnt @_model.version slug = _.slugify name
throw new Error "the data is for version #{data.version}, not #{@_model.version} as expected" modVersion.registerSlug slug, name
inputStacks.push new Stack slug:slug, quantity:0
@_model.description = data.description or '' for c in recipeData.pattern
@_parseRawMaterials data.raw_materials continue if c is '.'
continue if c is ' '
stack = inputStacks[parseInt(c)]
if not stack? then throw new Error "there is no input #{c} in this recipe"
stack.quantity += 1
for index in [0...data.recipes.length] for i in [0...inputStacks.length]
@_errorLocation = "recipe #{index + 1}" stack = inputStacks[i]
recipeData = data.recipes[index] if stack.quantity is 0
recipe = @_parseRecipe recipeData name = modVersion.findName stack.slug
recipe._originalIndex = index throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
return @_model outputStacks = [ new Stack slug:item.slug, quantity:recipeData.quantity ]
for extraData in recipeData.extras
slug = _.slugify extraData.name
modVersion.registerSlug slug, extraData.name
outputStacks.push new Stack slug:slug, quantity:extraData.quantity
_parseRawMaterials: (data)-> toolStacks = []
return unless data? and data.length > 0 for name in recipeData.tools
slug = _.slugify name
results = [] modVersion.registerSlug slug, name
for name in data toolStacks.push new Stack slug:slug, quantity:1
item = @_findOrCreateItem name
item.isGatherable = true
results.push item
_parseRecipe: (data)->
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
data.output = if _.isArray(data.output) then data.output else [data.output]
names = (e for e in _.flatten(data.output) when _.isString(e))
if names.length is 0 then throw new Error "#{@_errorLocation} has an empty output list"
item = @_findOrCreateItem names[0]
@_errorLocation = "recipe for #{item.name}"
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
data.tools ?= []
attributes = attributes =
item: item, input: inputStacks
output: @_parseStackList(data.output, field:'output', canBeEmpty:false) name: item.name
input: @_parseStackList(data.input, field:'input', canBeEmpty:true) pattern: recipeData.pattern
tools: @_parseStackList(data.tools, field:'tools', canBeEmpty:true) output: outputStacks
attributes.pattern = data.pattern or @_computeDefaultPattern attributes.input tools: toolStacks
recipe = new Recipe attributes recipe = new Recipe attributes
item.addRecipe recipe
return recipe return recipe
_parseStack: (data, options={})->
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
if not data? then throw new Error "#{errorBase} is missing"
if _.isString(data) then data = [1, data]
if not _.isArray(data) then throw new Error "#{errorBase} must be an array"
if data.length is 1 then data.unshift 1
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
name = data[1]
slug = _.slugify name
@_model.registerSlug slug, name
return new Stack slug:slug, quantity:data[0]
_parseStackList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
if data.length is 0 and not options.canBeEmpty
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
result = []
for index in [0...data.length]
stackData = data[index]
result.push @_parseStack stackData, field:options.field, index:index
return result
# Un-parsing Methods ########################################################################### # Un-parsing Methods ###########################################################################
_unparseModVersion: (modVersion)-> _unparseModVersion: (builder, modVersion)->
result = [] itemList = _.values modVersion.items
result.push '{\n' itemList.sort (a, b)-> a.compareTo b
result.push ' "dataVersion": 1,\n'
result.push ' "name": "' + modVersion.name + '",\n'
result.push ' "version": "' + modVersion.version + '",\n'
if modVersion.description.length > 0
result.push ' "description": "' + modVersion.description + '",\n'
rawMaterials = (item.name for slug, item of modVersion.items when item.isGatherable) builder
rawMaterials.sort() .line 'schema: ', 2
if rawMaterials.length > 0 .line 'name: ', modVersion.name
result.push ' "raw_materials": [\n' .line 'version: ', modVersion.version
firstItem = true .onlyIf modVersion.description?, => builder.line 'description: ', modVersion.description
for material in rawMaterials .line()
if not firstItem then result.push ',\n' .onlyIf itemList.length > 0, =>
result.push ' "' + material + '"' builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)
firstItem = false .outdent()
result.push '\n ],\n'
result.push ' "recipes": [\n' _unparseItem: (builder, item)->
builder
.line 'item: ', item.name
.indent()
.onlyIf item.isGatherable, => builder.line 'gatherable: yes'
.onlyIf item.recipes.length > 0, =>
builder.loop item.recipes, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r)
.outdent()
items = (item for slug, item of modVersion.items when item.isCraftable) _unparseRecipe: (builder, recipe)->
items.sort (a, b)-> a.compareTo b inputNames = (builder.context.findName(stack.slug) for stack in recipe.input)
inputNames.sort()
firstItem = true patternMap = {'.', '.'}
for item in items for i in [0...recipe.input.length]
for recipe in item.recipes stack = recipe.input[i]
result.push if firstItem then ' {\n' else ' }, {\n' patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.slug)}"
@_unparseRecipe recipe, result
firstItem = false
result.push ' }\n'
result.push ' ]\n' pattern = recipe.pattern or recipe.defaultPattern
result.push '}' newPattern = []
for c in pattern.split('')
newPattern.push patternMap[c]
newPattern = newPattern.join ''
newPattern = newPattern.replace /(...)(...)(...)/, '$1 $2 $3'
return result.join '' quantity = recipe.output[0].quantity
_unparseRecipe: (recipe, result=[])-> extraOutputs = recipe.output[0...recipe.output.length]
result.push ' "output": ' extraOutputs.shift()
@_unparseStackList recipe.output, result, sort:false
if recipe.input.length > 0 builder
result.push ',\n' .line 'recipe:'
result.push ' "input": ' .indent()
@_unparseStackList recipe.input, result .onlyIf extraOutputs.length > 0, =>
builder
.push 'extras: '
.call => @_unparseStackList builder, extraOutputs
.line()
.push 'input: '
.loop inputNames
.line()
.line 'pattern: ', newPattern
.onlyIf quantity > 1, => builder.line 'quantity: ', quantity
.onlyIf recipe.tools.length > 0, =>
builder
.push 'tools: '
.call => @_unparseStackList builder, recipe.tools
.line()
.outdent()
if recipe.pattern? _unparseStackList: (builder, stackList)->
result.push ',\n' if stackList.length is 1 and stackList[0].quantity is 1
result.push ' "pattern": "' builder.push builder.context.findName(stackList[0].slug)
result.push recipe.pattern
result.push '"'
if recipe.tools.length > 0
result.push ',\n'
result.push ' "tools": '
@_unparseStackList recipe.tools, result
result.push '\n'
return result
_unparseStackList: (stackList, result, options={})->
options.sort ?= true
if stackList.length is 0
result.push '[]'
else if stackList.length is 1
stack = stackList[0]
if stack.quantity is 1
result.push '"' + @_model.findName(stack.slug) + '"'
else else
result.push '[[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]]' builder.loop stackList, onEach:(b, stack)=>
else builder
result.push '[' .onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
.push builder.context.findName stack.slug
stacks = stackList.slice()
if options.sort
stacks.sort (a, b)->
if a.quantity isnt b.quantity
return if a.quantity > b.quantity then -1 else +1
if a.slug isnt b.slug
return if a.slug < b.slug then -1 else +1
return 0
firstItem = true
for stack in stacks
result.push ', ' if not firstItem
if stack.quantity is 1
result.push '"' + @_model.findName(stack.slug) + '"'
else
result.push '[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]'
firstItem = false
result.push ']'
return result
@@ -1,284 +0,0 @@
###
Crafting Guide - mod_version_parser_v2.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
CommandParserBase = require '../command_parser_base'
Item = require '../item'
ModVersion = require '../mod_version'
Recipe = require '../recipe'
Stack = require '../stack'
StringBuilder = require '../string_builder'
########################################################################################################################
module.exports = class ModVersionParserV2 extends CommandParserBase
# Class Methods ################################################################################
@INTEGER = /[0-9]+/
@PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
@STACK = /^([0-9]+) +(.*)$/
# CommandParserBase Overrides ##################################################################
_buildModel: (rawData, model)->
@_buildModVersion rawData, model
_unparseModel: (builder, model)->
@_unparseModVersion builder, model
# Command Methods ##############################################################################
_command_description: (descriptionParts...)->
if @_rawData.description? then throw new Error 'duplicate declaration of "description"'
@_rawData.description = descriptionParts.join ', '
_command_extras: (extraTerms...)->
if not @_recipeData? then throw new Error 'cannot declare "extras" before "recipe"'
if @_recipeData.extras? then throw new Error 'duplicate declaration of "extras"'
@_recipeData.extras = []
for term in extraTerms
match = ModVersionParserV2.STACK.exec term
if match?
@_recipeData.extras.push quantity:parseInt(match[1]), name:match[2]
else
@_recipeData.extras.push quantity:1, name:term
_command_gatherable: (gatherable)->
if not @_itemData? then throw new Error 'cannot declare "gatherable" before "item"'
if @_itemData.gatherable? then throw new Error 'duplicate declaration of "gatherable"'
if not (gatherable in ['yes', 'no']) then throw new Error 'gatherable must be either "yes" or "no"'
@_itemData.gatherable = (gatherable is 'yes')
_command_item: (name='')->
if not name.length > 0 then throw new Error 'the item name cannot be empty'
@_itemData = name:name, line:@_lineNumber
@_rawData.items ?= []
@_rawData.items.push @_itemData
@_recipeData = null
_command_name: (name='')->
if @_rawData.name? then throw new Error 'duplicate declaration of "name"'
if not name.length > 0 then throw new Error 'the mod name cannot be empty'
@_rawData.name = name
_command_input: (inputNames...)->
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
if @_recipeData.input? then throw new Error 'duplicate declaration of "input"'
@_recipeData.input = []
for name in inputNames
if name.length is 0 then throw new Error 'input names cannot be empty'
@_recipeData.input.push name
_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 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'
@_recipeData.pattern = pattern
_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 ModVersionParserV2.INTEGER.test(quantity) then throw new Error 'quantity must be an integer'
@_recipeData.quantity = parseInt quantity
_command_recipe: ->
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
@_recipeData = line:@_lineNumber
@_itemData.recipes ?= []
@_itemData.recipes.push @_recipeData
_command_schema: -> # do nothing
_command_tools: (toolNames...)->
if not @_recipeData? then throw new Error 'cannot declare "tools" before "recipe"'
if @_recipeData.tools? then throw new Error 'duplicate declaration of "tools"'
@_recipeData.tools = []
for name in toolNames
if name.length is 0 then throw new Error 'tool names cannot be empty'
@_recipeData.tools.push name
_command_version: (version='')->
if @_rawData.version? then throw new Error 'duplicate declaration of "version"'
if version.length is 0 then throw new Error 'version cannot be empty'
@_rawData.version = version
# Object Creation Methods ######################################################################
_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 ?= []
modVersion.description = modVersionData.description
for itemData in modVersionData.items
@_handleErrors @_buildItem, modVersion, itemData
return modVersion
_buildItem: (modVersion, itemData)->
@_lineNumber = itemData.line
itemData.gatherable ?= false
itemData.recipes ?= []
item = new Item name:itemData.name, isGatherable:itemData.gatherable
modVersion.addItem item
for recipeData in itemData.recipes
@_handleErrors @_buildRecipe, modVersion, item, recipeData
return item
_buildRecipe: (modVersion, item, recipeData)->
@_lineNumber = recipeData.line
if not recipeData.input? then throw new Error 'the "input" declaration is required'
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
recipeData.quantity ?= 1
recipeData.extras ?= []
recipeData.tools ?= []
inputStacks = []
for name in recipeData.input
slug = _.slugify name
modVersion.registerSlug slug, name
inputStacks.push new Stack slug:slug, quantity:0
for c in recipeData.pattern
continue if c is '.'
continue if c is ' '
stack = inputStacks[parseInt(c)]
if not stack? then throw new Error "there is no input #{c} in this recipe"
stack.quantity += 1
for i in [0...inputStacks.length]
stack = inputStacks[i]
if stack.quantity is 0
name = modVersion.findName stack.slug
throw new Error "#{name} is an input for this recipe, but it is not in the pattern"
outputStacks = [ new Stack slug:item.slug, quantity:recipeData.quantity ]
for extraData in recipeData.extras
slug = _.slugify extraData.name
modVersion.registerSlug slug, extraData.name
outputStacks.push new Stack slug:slug, quantity:extraData.quantity
toolStacks = []
for name in recipeData.tools
slug = _.slugify name
modVersion.registerSlug slug, name
toolStacks.push new Stack slug:slug, quantity:1
attributes =
input: inputStacks
name: item.name
pattern: recipeData.pattern
output: outputStacks
tools: toolStacks
recipe = new Recipe attributes
item.addRecipe recipe
return recipe
# Un-parsing Methods ###########################################################################
_unparseModVersion: (builder, modVersion)->
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()
.onlyIf itemList.length > 0, =>
builder.loop itemList, delimiter:'\n', onEach:(b, i)=> @_unparseItem(b, i)
.outdent()
_unparseItem: (builder, item)->
builder
.line 'item: ', item.name
.indent()
.onlyIf item.isGatherable, => builder.line 'gatherable: yes'
.onlyIf item.recipes.length > 0, =>
builder.loop item.recipes, delimiter:'', onEach:(b, r)=> @_unparseRecipe(b, r)
.outdent()
_unparseRecipe: (builder, recipe)->
inputNames = (builder.context.findName(stack.slug) for stack in recipe.input)
inputNames.sort()
patternMap = {'.', '.'}
for i in [0...recipe.input.length]
stack = recipe.input[i]
patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.slug)}"
pattern = recipe.pattern or recipe.defaultPattern
newPattern = []
for c in pattern.split('')
newPattern.push patternMap[c]
newPattern = newPattern.join ''
newPattern = newPattern.replace /(...)(...)(...)/, '$1 $2 $3'
quantity = recipe.output[0].quantity
extraOutputs = recipe.output[0...recipe.output.length]
extraOutputs.shift()
builder
.line 'recipe:'
.indent()
.onlyIf extraOutputs.length > 0, =>
builder
.push 'extras: '
.call => @_unparseStackList builder, extraOutputs
.line()
.push 'input: '
.loop inputNames
.line()
.line 'pattern: ', newPattern
.onlyIf quantity > 1, => builder.line 'quantity: ', quantity
.onlyIf recipe.tools.length > 0, =>
builder
.push 'tools: '
.call => @_unparseStackList builder, recipe.tools
.line()
.outdent()
_unparseStackList: (builder, stackList)->
if stackList.length is 1 and stackList[0].quantity is 1
builder.push builder.context.findName(stackList[0].slug)
else
builder.loop stackList, onEach:(b, stack)=>
builder
.onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
.push builder.context.findName stack.slug
@@ -0,0 +1,50 @@
###
Crafting Guide - versioned_parser_base.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class VersionedParserBase
constructor: (options={})->
@_parsers = @_createParsers options
@_currentSchema = _.chain(@_parsers).keys().last().value()
# Class Members ################################################################################
@SCHEMA = /schema: *([0-9]+)/
# Public Methods ###############################################################################
parse: (text)->
return unless text?
schema = @_identifySchema text
parser = @_parsers[schema]
if not parser? then throw new Error "schema version #{schema} is not supported"
parser.parse text
return @_model
unparse: (schema=null)->
schema ?= @_currentSchema
if not modVersion? then throw new Error 'modVersion is required'
parser = @_parsers["#{schema}"]
if not parser? then throw new Error "version #{schema} is not supported"
return parser.unparse()
# Overridable Methods ##########################################################################
_createParsers: (options)->
throw new Error 'subclasses must override this method'
_identifySchema: (text)->
match = VersionedParserBase.SCHEMA.exec text
if not match? then throw new Error 'missing "schema" declaration'
return match[1]
@@ -1,5 +1,5 @@
//- //-
//- Crafting Guide - mod_version.jade //- Crafting Guide - mod.jade
//- //-
//- Copyright (c) 2014-2015 by Redwood Labs //- Copyright (c) 2014-2015 by Redwood Labs
//- All rights reserved. //- All rights reserved.
+7 -5
View File
@@ -6,8 +6,9 @@ All rights reserved.
### ###
CraftingPlan = require '../src/scripts/models/crafting_plan' CraftingPlan = require '../src/scripts/models/crafting_plan'
ModVersion = require '../src/scripts/models/mod_version' Mod = require '../src/scripts/models/mod'
ModPack = require '../src/scripts/models/mod_pack' ModPack = require '../src/scripts/models/mod_pack'
ModVersion = require '../src/scripts/models/mod_version'
######################################################################################################################## ########################################################################################################################
@@ -18,9 +19,10 @@ modPack = plan = null
describe 'CraftingPlan', -> describe 'CraftingPlan', ->
beforeEach -> beforeEach ->
modVersion = new ModVersion name:'Minecraft', version:'1.7.10' mod = new Mod name:'Minecraft'
modVersion.parse """ mod.addModVersion new ModVersion modSlug:mod.slug, version:'1.7.10'
schema:2; name:Minecraft; version:1.7.10 mod.activeModVersion.parse """
schema:1
item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4 item:Oak Plank; recipe:; input:Oak Log; pattern:... .0. ...; quantity:4
item:Stick; recipe:; input:Oak Plank; pattern:... .0. .0.; quantity:4 item:Stick; recipe:; input:Oak Plank; pattern:... .0. .0.; quantity:4
@@ -30,7 +32,7 @@ describe 'CraftingPlan', ->
item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table item:Iron Sword; recipe:; input:Iron Ingot, Stick; pattern:.0. .0. .1.; tools:Crafting Table
""" """
modPack = new ModPack modPack = new ModPack
modPack.addModVersion modVersion modPack.addMod mod
plan = new CraftingPlan modPack:modPack, includingTools:false plan = new CraftingPlan modPack:modPack, includingTools:false
+2 -2
View File
@@ -135,9 +135,9 @@ describe 'Inventory', ->
expect(-> inventory.remove('wool', 10)).to.throw Error, expect(-> inventory.remove('wool', 10)).to.throw Error,
'cannot remove 10: only 4 wool in this inventory' 'cannot remove 10: only 4 wool in this inventory'
it 'removes a single item by default', -> it 'removes all items by default', ->
inventory.remove 'wool' inventory.remove 'wool'
inventory._stacks.wool.quantity.should.equal 3 expect(inventory._stacks.wool).to.be.empty
it 'removes a quantity above 1', -> it 'removes a quantity above 1', ->
inventory.remove 'wool', 3 inventory.remove 'wool', 3
+30
View File
@@ -0,0 +1,30 @@
###
Crafting Guide - mod.test.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
Mod = require '../src/scripts/models/mod'
########################################################################################################################
mod = null
########################################################################################################################
describe 'Mod', ->
beforeEach -> mod = new Mod name:'Test'
describe 'compareTo', ->
it 'lists required mods first', ->
minecraft = new Mod name:'Minecraft'
mod.compareTo(minecraft).should.equal +1
minecraft.compareTo(mod).should.equal -1
it 'sorts by name second', ->
buildcraft = new Mod name:'Buildcraft'
mod.compareTo(buildcraft).should.equal +1
buildcraft.compareTo(mod).should.equal -1
+20 -22
View File
@@ -6,6 +6,7 @@ All rights reserved.
### ###
Item = require '../src/scripts/models/item' Item = require '../src/scripts/models/item'
Mod = require '../src/scripts/models/mod'
ModPack = require '../src/scripts/models/mod_pack' ModPack = require '../src/scripts/models/mod_pack'
ModVersion = require '../src/scripts/models/mod_version' ModVersion = require '../src/scripts/models/mod_version'
@@ -18,23 +19,28 @@ buildcraft = industrialCraft = minecraft = modPack = null
describe 'ModPack', -> describe 'ModPack', ->
beforeEach -> beforeEach ->
minecraft = new ModVersion name:'Minecraft', version:'1.7.10', enabled:true minecraft = new Mod name:'Minecraft'
minecraft.addItem new Item name:'Wool' minecraft.addModVersion new ModVersion modSlug:minecraft.slug, version:'1.7.10'
minecraft.addItem new Item name:'Bed', recipes:[''] minecraft.activeModVersion.addItem new Item name:'Wool'
minecraft.registerSlug 'iron_chestplate', 'Iron Chestplate' minecraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
minecraft.activeModVersion.registerSlug 'iron_chestplate', 'Iron Chestplate'
buildcraft = new ModVersion name:'Buildcraft', version:'6.2.6', enabled:false buildcraft = new Mod name:'Buildcraft'
buildcraft.addItem new Item name:'Stone Gear', recipes:[''] buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6'
buildcraft.addItem new Item name:'Bed', recipes:[''] buildcraft.activeModVersion.addItem new Item name:'Stone Gear', recipes:['']
buildcraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
buildcraft.activeVersion = Mod.Version.None
industrialCraft = new ModVersion name:'Industrial Craft', version:'2.0', enabled:false industrialCraft = new Mod name:'Industrial Craft'
industrialCraft.addItem new Item name:'Resin' industrialCraft.addModVersion new ModVersion modSlug:industrialCraft.slug, version:'2.0'
industrialCraft.addItem new Item name:'Rubber' industrialCraft.activeModVersion.addItem new Item name:'Resin'
industrialCraft.activeModVersion.addItem new Item name:'Rubber'
industrialCraft.activeVersion = Mod.Version.None
modPack = new ModPack modPack = new ModPack
modPack.addModVersion minecraft modPack.addMod minecraft
modPack.addModVersion buildcraft modPack.addMod buildcraft
modPack.addModVersion industrialCraft modPack.addMod industrialCraft
describe 'findItemByName', -> describe 'findItemByName', ->
@@ -46,10 +52,6 @@ describe 'ModPack', ->
item = modPack.findItemByName 'Stone Gear' item = modPack.findItemByName 'Stone Gear'
expect(item).to.be.null expect(item).to.be.null
it "doesn't ignore mod versions when include disabled is requested", ->
item = modPack.findItemByName 'Stone Gear', includeDisabled:true
item.name.should.equal 'Stone Gear'
describe 'findItemDisplay', -> describe 'findItemDisplay', ->
it 'returns all data for a regular Minecraft item', -> it 'returns all data for a regular Minecraft item', ->
@@ -60,17 +62,13 @@ describe 'ModPack', ->
display.modSlug.should.equal 'minecraft' display.modSlug.should.equal 'minecraft'
it 'returns all data for an item in an enabled mod', -> it 'returns all data for an item in an enabled mod', ->
buildcraft.enabled = true buildcraft.activeVersion = '6.2.6'
display = modPack.findItemDisplay 'stone_gear' display = modPack.findItemDisplay 'stone_gear'
display.iconUrl.should.equal '/data/buildcraft/6.2.6/images/stone_gear.png' display.iconUrl.should.equal '/data/buildcraft/6.2.6/images/stone_gear.png'
display.itemUrl.should.equal '/item/Stone%20Gear' display.itemUrl.should.equal '/item/Stone%20Gear'
display.itemName.should.equal 'Stone Gear' display.itemName.should.equal 'Stone Gear'
display.modSlug.should.equal 'buildcraft' display.modSlug.should.equal 'buildcraft'
it 'returns data even for a disabled mod', ->
display = modPack.findItemDisplay 'stone_gear'
display.itemName.should.equal 'Stone Gear'
it 'assumes an unfound item is from Minecraft', -> it 'assumes an unfound item is from Minecraft', ->
display = modPack.findItemDisplay 'iron_chestplate' display = modPack.findItemDisplay 'iron_chestplate'
display.iconUrl.should.equal '/data/minecraft/1.7.10/images/iron_chestplate.png' display.iconUrl.should.equal '/data/minecraft/1.7.10/images/iron_chestplate.png'
+4 -21
View File
@@ -16,20 +16,15 @@ modVersion = null
describe 'ModVersion', -> describe 'ModVersion', ->
beforeEach -> modVersion = new ModVersion name:'Test', version:'0.0' beforeEach -> modVersion = new ModVersion modSlug:'test', version:'0.0'
describe 'constructor', -> describe 'constructor', ->
it 'requires a mod name', -> it 'requires a mod slug', ->
expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.name is required' expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.modSlug is required'
it 'requires a mod version', -> it 'requires a mod version', ->
expect(-> new ModVersion name:'Test').to.throw Error, 'attributes.version is required' expect(-> new ModVersion modSlug:'test').to.throw Error, 'attributes.version is required'
it 'supplies default values', ->
modVersion.description.should.equal ''
modVersion.enabled.should.be.true
modVersion.slug.should.equal 'test'
describe 'addItem', -> describe 'addItem', ->
@@ -45,18 +40,6 @@ describe 'ModVersion', ->
modVersion.addItem new Item name:'Wool' modVersion.addItem new Item name:'Wool'
modVersion._items.wool.modVersion.should.equal modVersion modVersion._items.wool.modVersion.should.equal modVersion
describe 'compareTo', ->
it 'lists required mods first', ->
minecraft = new ModVersion name:'Minecraft', version:'1.7.10'
modVersion.compareTo(minecraft).should.equal +1
minecraft.compareTo(modVersion).should.equal -1
it 'sorts by name second', ->
buildcraft = new ModVersion name:'Buildcraft', version:'3.0'
modVersion.compareTo(buildcraft).should.equal +1
buildcraft.compareTo(modVersion).should.equal -1
describe 'findItemByName', -> describe 'findItemByName', ->
it 'locates items by slugified name', -> it 'locates items by slugified name', ->
@@ -1,159 +1,209 @@
### ###
Crafting Guide - mod_version_parser_v1.test.coffee Crafting Guide - mod_version_parser_v1.test.coffee
Copyright (c) 2014-2015 by Redwood Labs Copyright (c) 2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
Item = require '../../src/scripts/models/item'
ModVersion = require '../../src/scripts/models/mod_version' ModVersion = require '../../src/scripts/models/mod_version'
ModVersionParserV1 = require '../../src/scripts/models/parser_versions/mod_version_parser_v1' ModVersionParserV1 = require '../../src/scripts/models/parser_versions/mod_version_parser_v1'
######################################################################################################################## ########################################################################################################################
modVersion = parser = null baseText = modVersion = parser = null
######################################################################################################################## ########################################################################################################################
describe "ModVersionParserV1", -> describe 'ModVersionParserV1', ->
beforeEach -> beforeEach ->
modVersion = new ModVersion name:'Test', version:'0.0' modVersion = new ModVersion modSlug:'test', version:'0.0'
parser = new ModVersionParserV1 model:modVersion parser = new ModVersionParserV1 model:modVersion
describe '_parseModVersion', -> describe 'Item', ->
it 'requires a mod_name', -> it 'allows multiple recipes', ->
data = version:'1.0', items:[] recipes = "item: Charlie;
expect(-> parser._parseModVersion data).to.throw Error, 'name is required' recipe:; input:Alpha; pattern:... .0. ...;
recipe:; input:Bravo; pattern:... 0.0 ...;"
modVersion = parser.parse recipes
recipes = modVersion._items.charlie._recipes
recipes[0].input[0].slug.should.equal 'alpha'
recipes[1].input[0].slug.should.equal 'bravo'
it 'requires a mod_version', -> describe 'name', ->
data = name:'Empty', items:[]
expect(-> parser._parseModVersion data).to.throw Error, 'version is required'
it 'can parse an empty modVersion', -> it 'adds the name when present', ->
data = modVersion = parser.parse 'item: Charlie'
name: 'Test' modVersion._items.charlie.name.should.equal 'Charlie'
version: '0.0'
recipes: []
modVersion = parser._parseModVersion data
modVersion.name.should.equal 'Test'
modVersion.version.should.equal '0.0'
it 'can parse a non-empty mod version', -> it 'requires a non-empty name', ->
data = func = -> parser.parse 'item: \n'
name: 'Test' expect(func).to.throw Error, 'cannot be empty'
version: '0.0'
recipes: [
{ input:'Sugar Cane', output:'Sugar' }
{ input:[[3, 'Wool'], [3, 'Planks']], tools:'Crafting Table', output:'Bed' }
]
modVersion = parser._parseModVersion data
modVersion.name.should.equal 'Test'
modVersion.version.should.equal '0.0'
modVersion._slugs.should.eql ['bed', 'crafting_table', 'planks', 'sugar', 'sugar_cane', 'wool']
describe '_parseRawMaterials', -> describe 'gatherable', ->
it 'skips the section when missing', -> it 'adds "gatherable" when present', ->
parser._parseRawMaterials null modVersion = parser.parse 'item: Alpha Bravo; gatherable: yes'
_.keys(modVersion._items).length.should.equal 0 modVersion._items.alpha_bravo.isGatherable.should.be.true
it 'skips the section when empty', -> it 'does not allow a duplicate "gatherable" declaration', ->
parser._parseRawMaterials [] func = -> parser.parse 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
_.keys(modVersion._items).length.should.equal 0 expect(func).to.throw Error, 'duplicate'
it 'adds items marked as gatherable', -> it 'requires "gatherable" to be "yes" or "no"', ->
parser._parseRawMaterials ['Wool'] func = -> parser.parse 'item: Alpha Bravo; gatherable: true'
modVersion._items['wool'].isGatherable.should.be.true expect(func).to.throw Error, 'gatherable must be'
it 'marks an existing item as gatherable', -> it 'does not allow "gatherable" before "item"', ->
modVersion.addItem new Item name:'Wool' func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
modVersion._items['wool'].isGatherable.should.be.false expect(func).to.throw Error, '"gatherable" before "item"'
parser._parseRawMaterials ['Wool']
modVersion._items['wool'].isGatherable.should.be.true
it 'registers the names of the items', -> describe 'Recipe', ->
parser._parseRawMaterials ['Wool']
modVersion._names['wool'].should.equal 'Wool'
describe '_parseRecipe', -> beforeEach -> baseText = 'item: Charlie; '
it 'requires output to be defined', -> describe 'input', ->
parser._errorLocation = 'boat'
test = -> parser._parseRecipe {input:'wool'}
expect(test).to.throw Error, 'boat is missing output'
it 'requires input to be defined', -> it 'adds "input" when present', ->
test = -> parser._parseRecipe {output:'wool'} modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
expect(test).to.throw Error, 'recipe for wool is missing input' slugs = (s.slug for s in modVersion._items.charlie._recipes[0].input)
slugs.should.eql ['alpha', 'bravo', 'charlie']
it 'can parse a regular recipe', -> it 'requires an "input" declaration', ->
data = func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
output: 'bed' expect(func).to.throw Error, 'the "input" declaration is required'
input: [[3, 'planks'], [3, 'wool']]
tools: 'crafting table'
recipe = parser._parseRecipe data
(stack.slug for stack in recipe.output).should.eql ['bed']
(stack.slug for stack in recipe.input).sort().should.eql ['planks', 'wool']
(stack.slug for stack in recipe.tools).should.eql ['crafting_table']
it 'can parse a recipe without tools', -> it 'does not allow a duplicate "input" declaration', ->
recipe = parser._parseRecipe {output:'sugar', input:'sugar cane'} func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:....0....; input:Bravo'
(stack.slug for stack in recipe.output).should.eql ['sugar'] expect(func).to.throw Error, 'duplicate declaration of "input"'
(stack.slug for stack in recipe.input).sort().should.eql ['sugar_cane']
(stack.slug for stack in recipe.tools).should.eql []
it 'registers all names', -> it 'does not allow "input" before "recipe"', ->
data = func = -> parser.parse baseText + 'input:Alpha, Bravo; recipe:; pattern:....0....'
output: 'Bed' expect(func).to.throw Error, 'cannot declare "input" before "recipe"'
input: [[3, 'Oak Wood Planks'], [3, 'Wool']]
tools: 'Crafting Table'
parser._parseRecipe data
modVersion._slugs.should.eql ['bed', 'crafting_table', 'oak_wood_planks', 'wool']
describe '_parseStack', -> it 'registers slugs for each input name', ->
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
modVersion._slugs.should.eql ['charlie', 'delta', 'echo', 'foxtrot']
it 'requires the array to have at least one element', -> describe 'pattern', ->
parser._errorLocation = 'boat'
options = index:1, field:'output'
expect(-> parser._parseStack([], options)).to.throw Error,
"output element 1 for boat must have at least one element"
it 'can fill in a missing number', -> it 'adds "pattern" when present', ->
stack = parser._parseStack 'boat' modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
stack.slug.should.equal 'boat' modVersion._items.charlie._recipes[0].pattern.should.equal '... .0. .1.'
it 'requires a "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
expect(func).to.throw Error, 'the "pattern" declaration is required'
it 'does not allow a duplicate "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:....0..1.; pattern:01.......'
expect(func).to.throw Error, 'duplicate declaration of "pattern"'
it 'requires pattern to be the right length', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:000'
expect(func).to.throw Error, 'a pattern must have'
it 'requires pattern to only use proper characters', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:abc def ghi'
expect(func).to.throw Error, 'a pattern must have'
it 'requires pattern to only refer to existing items', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:... 010 ...'
expect(func).to.throw Error, 'there is no input 1 in this recipe'
it 'requires all items to appear in the pattern', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: 000 0.0 000'
expect(func).to.throw Error, 'Bravo is an input'
it 'computes the input stack sizes from the pattern', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
recipe = modVersion._items.charlie._recipes[0]
recipe.input[0].quantity.should.equal 1
recipe.input[1].quantity.should.equal 3
recipe.input[2].quantity.should.equal 2
it 'does not allow "pattern" before "recipe"', ->
func = -> parser.parse baseText + 'pattern:... .0. ...; recipe:; inputs:Alpha'
expect(func).to.throw Error, 'cannot declare "pattern" before "recipe"'
describe 'quantity', ->
beforeEach ->
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
it 'adds "quantity" when present', ->
modVersion = parser.parse baseText + 'quantity: 2'
modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 2
it 'does not allow a duplicate "quantity" declaration', ->
func = -> parser.parse baseText + 'quantity:1; quantity:2'
expect(func).to.throw Error, 'duplicate declaration of "quantity"'
it 'requires quantity to be an integer', ->
func = -> parser.parse baseText + 'quantity:ten'
expect(func).to.throw Error, 'quantity must be an integer'
it 'assumes a quantity of 1 by default', ->
modVersion = parser.parse baseText
modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 1
it 'does not allow "quantity" before recipe', ->
func = -> parser.parse 'item:Bravo; quantity:12; recipe:;'
expect(func).to.throw Error, 'cannot declare "quantity" before "recipe"'
describe 'output', ->
beforeEach ->
baseText = 'item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
it 'adds a single item as the default output', ->
modVersion = parser.parse baseText
stack = modVersion._items.bravo._recipes[0].output[0]
stack.slug.should.equal 'bravo'
stack.quantity.should.equal 1 stack.quantity.should.equal 1
stack2 = parser._parseStack ['boat'] it 'can add multiple extras with quantities', ->
stack2.slug.should.equal 'boat' modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
stack2.quantity.should.equal 1 output = modVersion._items.bravo._recipes[0].output
output[0].slug.should.equal 'bravo'
output[0].quantity.should.equal 1
output[1].slug.should.equal 'delta'
output[1].quantity.should.equal 2
output[2].slug.should.equal 'echo'
output[2].quantity.should.equal 4
it 'requires the data to start with a number', -> it 'does not allow "extras" before "recipe"', ->
parser._errorLocation = 'boat' func = -> parser.parse 'item:Bravo; extras:Charlie'
options = index:1, field:'output' expect(func).to.throw Error, 'cannot declare "extras" before "recipe"'
expect(-> parser._parseStack(['2', 'wool'], options)).to.throw Error,
"output element 1 for boat must start with a number"
it 'can parse a basic item', -> it 'registers slugs for each output name', ->
stack = parser._parseStack [2, 'wool'] modVersion = parser.parse baseText + 'extras:Delta, Echo'
stack.constructor.name.should.equal 'Stack' modVersion._slugs.should.eql ['bravo', 'charlie', 'delta', 'echo']
describe '_parseStackList', -> it 'does not allow a duplicate "extras" declaration', ->
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
expect(func).to.throw Error, 'duplicate declaration of "extras"'
it 'can promote a single item to a list', -> describe 'tools', ->
list = parser._parseStackList 'boat'
(stack.slug for stack in list).should.eql ['boat']
it 'can require a list to be non-empty', -> beforeEach ->
parser._errorLocation = 'boat' baseText = 'item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
options = field:'output', canBeEmpty:false
expect(-> parser._parseStackList [], options).to.throw Error, 'output for boat cannot be empty'
it 'can allow an empty list', -> it 'can add a single tool', ->
list = parser._parseStackList [], canBeEmpty:true modVersion = parser.parse baseText + 'tools: Furnace'
list.length.should.equal 0 modVersion._items.bravo._recipes[0].tools[0].slug.should.equal 'furnace'
it 'can parse a non-empty list', -> it 'can add multiple tools', ->
list = parser._parseStackList [[3, 'plank'], [3, 'wool']] modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
(stack.slug for stack in list).sort().should.eql ['plank', 'wool'] tools = modVersion._items.bravo._recipes[0].tools
tools[0].slug.should.equal 'crafting_table'
tools[1].slug.should.equal 'furnace'
it 'registers slugs for each tool name', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
modVersion._slugs.should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
it 'does not allow a duplicate "tools" declaration', ->
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
expect(func).to.throw Error, 'duplicate declaration of "tools"'
@@ -1,265 +0,0 @@
###
Crafting Guide - mod_version_parsers/v2.test.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
ModVersion = require '../../src/scripts/models/mod_version'
ModVersionParserV2 = require '../../src/scripts/models/parser_versions/mod_version_parser_v2'
########################################################################################################################
baseText = modVersion = parser = null
########################################################################################################################
describe 'ModVersionParserV2', ->
beforeEach ->
modVersion = new ModVersion name:'Test', version:'0.0'
parser = new ModVersionParserV2 model:modVersion
describe 'Item', ->
beforeEach -> baseText = 'name:Test; version:0.0; '
it 'allows multiple recipes', ->
recipes = "item: Charlie;
recipe:; input:Alpha; pattern:... .0. ...;
recipe:; input:Bravo; pattern:... 0.0 ...;"
modVersion = parser.parse baseText + recipes
recipes = modVersion._items.charlie._recipes
recipes[0].input[0].slug.should.equal 'alpha'
recipes[1].input[0].slug.should.equal 'bravo'
describe 'name', ->
it 'adds the name when present', ->
modVersion = parser.parse baseText + 'item: Charlie'
modVersion._items.charlie.name.should.equal 'Charlie'
it 'requires a non-empty name', ->
func = -> parser.parse baseText + 'item: \n'
expect(func).to.throw Error, 'cannot be empty'
describe 'gatherable', ->
it 'adds "gatherable" when present', ->
modVersion = parser.parse baseText + 'item: Alpha Bravo; gatherable: yes'
modVersion._items.alpha_bravo.isGatherable.should.be.true
it 'does not allow a duplicate "gatherable" declaration', ->
func = -> parser.parse baseText + 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
expect(func).to.throw Error, 'duplicate'
it 'requires "gatherable" to be "yes" or "no"', ->
func = -> parser.parse baseText + 'item: Alpha Bravo; gatherable: true'
expect(func).to.throw Error, 'gatherable must be'
it 'does not allow "gatherable" before "item"', ->
func = -> parser.parse baseText + 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
expect(func).to.throw Error, '"gatherable" before "item"'
describe 'ModVersion', ->
it 'allows declarations in any order', ->
modVersion = parser.parse 'item: Alpha; version:0.0; name:Test'
modVersion.name.should.equal 'Test'
modVersion.version.should.equal '0.0'
modVersion._items.alpha.name.should.equal 'Alpha'
it 'does not allow duplicate item declarations', ->
func = -> parser.parse 'version:0.0; name:Test; item:Charlie; item:Charlie'
expect(func).to.throw Error, 'duplicate item for Charlie'
it 'allows multiple items', ->
modVersion = parser.parse 'name:Test; version:0.0; item:Bravo; item:Charlie'
_.keys(modVersion._items).sort().should.eql ['bravo', 'charlie']
describe 'name', ->
it 'adds "name" when present', ->
modVersion = parser.parse 'name:Test; version:0.0'
modVersion.name.should.equal 'Test'
it 'does not allow a duplicate "name" declaration', ->
func = -> parser.parse 'name:Test; version:0.0; name:Charlie'
expect(func).to.throw Error, 'duplicate declaration of "name"'
it 'requires a "name" declaration', ->
func = -> parser.parse 'version:1; item:Alpha'
expect(func).to.throw Error, 'the "name" declaration is required'
describe 'version', ->
it 'adds "version" when present', ->
modVersion = parser.parse 'name:Test; version:0.0'
modVersion.version.should.equal '0.0'
it 'does not allow a duplicate "version" declaration', ->
func = -> parser.parse 'name:Test; version:0.0; item:Charlie; version:2'
expect(func).to.throw Error, 'duplicate declaration of "version"'
it 'requires a "version" declaration', ->
func = -> parser.parse 'name:Test; item:Charlie'
expect(func).to.throw Error, 'the "version" declaration is required'
describe 'description', ->
it 'adds "description" when present', ->
modVersion = parser.parse 'name:Test; version:0.0; description:Charlie Delta'
modVersion.description.should.equal 'Charlie Delta'
it 'does not allow a duplicate "description" declaration', ->
func = -> parser.parse 'name:Alpha; version:1; description:Bravo; description:Charlie'
expect(func).to.throw Error, 'duplicate declaration of "description"'
describe 'Recipe', ->
beforeEach -> baseText = 'name:Test; version:0.0; item: Charlie; '
describe 'input', ->
it 'adds "input" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern: ... 012 ...'
slugs = (s.slug for s in modVersion._items.charlie._recipes[0].input)
slugs.should.eql ['alpha', 'bravo', 'charlie']
it 'requires an "input" declaration', ->
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
expect(func).to.throw Error, 'the "input" declaration is required'
it 'does not allow a duplicate "input" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:....0....; input:Bravo'
expect(func).to.throw Error, 'duplicate declaration of "input"'
it 'does not allow "input" before "recipe"', ->
func = -> parser.parse baseText + 'input:Alpha, Bravo; recipe:; pattern:....0....'
expect(func).to.throw Error, 'cannot declare "input" before "recipe"'
it 'registers slugs for each input name', ->
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
modVersion._slugs.should.eql ['charlie', 'delta', 'echo', 'foxtrot']
describe 'pattern', ->
it 'adds "pattern" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
modVersion._items.charlie._recipes[0].pattern.should.equal '... .0. .1.'
it 'requires a "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
expect(func).to.throw Error, 'the "pattern" declaration is required'
it 'does not allow a duplicate "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:....0..1.; pattern:01.......'
expect(func).to.throw Error, 'duplicate declaration of "pattern"'
it 'requires pattern to be the right length', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:000'
expect(func).to.throw Error, 'a pattern must have'
it 'requires pattern to only use proper characters', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:abc def ghi'
expect(func).to.throw Error, 'a pattern must have'
it 'requires pattern to only refer to existing items', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:... 010 ...'
expect(func).to.throw Error, 'there is no input 1 in this recipe'
it 'requires all items to appear in the pattern', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: 000 0.0 000'
expect(func).to.throw Error, 'Bravo is an input'
it 'computes the input stack sizes from the pattern', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo, Charlie; pattern:111 .0. 2.2'
recipe = modVersion._items.charlie._recipes[0]
recipe.input[0].quantity.should.equal 1
recipe.input[1].quantity.should.equal 3
recipe.input[2].quantity.should.equal 2
it 'does not allow "pattern" before "recipe"', ->
func = -> parser.parse baseText + 'pattern:... .0. ...; recipe:; inputs:Alpha'
expect(func).to.throw Error, 'cannot declare "pattern" before "recipe"'
describe 'quantity', ->
beforeEach ->
baseText = 'name:Test; version:0.0; item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
it 'adds "quantity" when present', ->
modVersion = parser.parse baseText + 'quantity: 2'
modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 2
it 'does not allow a duplicate "quantity" declaration', ->
func = -> parser.parse baseText + 'quantity:1; quantity:2'
expect(func).to.throw Error, 'duplicate declaration of "quantity"'
it 'requires quantity to be an integer', ->
func = -> parser.parse baseText + 'quantity:ten'
expect(func).to.throw Error, 'quantity must be an integer'
it 'assumes a quantity of 1 by default', ->
modVersion = parser.parse baseText
modVersion._items.charlie._recipes[0].output[0].quantity.should.equal 1
it 'does not allow "quantity" before recipe', ->
func = -> parser.parse 'name:Alpha; version:1; item:Bravo; quantity:12; recipe:;'
expect(func).to.throw Error, 'cannot declare "quantity" before "recipe"'
describe 'output', ->
beforeEach ->
baseText = 'name:Test; version:0.0; item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
it 'adds a single item as the default output', ->
modVersion = parser.parse baseText
stack = modVersion._items.bravo._recipes[0].output[0]
stack.slug.should.equal 'bravo'
stack.quantity.should.equal 1
it 'can add multiple extras with quantities', ->
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
output = modVersion._items.bravo._recipes[0].output
output[0].slug.should.equal 'bravo'
output[0].quantity.should.equal 1
output[1].slug.should.equal 'delta'
output[1].quantity.should.equal 2
output[2].slug.should.equal 'echo'
output[2].quantity.should.equal 4
it 'does not allow "extras" before "recipe"', ->
func = -> parser.parse 'name:Alpha; version:1; item:Bravo; extras:Charlie'
expect(func).to.throw Error, 'cannot declare "extras" before "recipe"'
it 'registers slugs for each output name', ->
modVersion = parser.parse baseText + 'extras:Delta, Echo'
modVersion._slugs.should.eql ['bravo', 'charlie', 'delta', 'echo']
it 'does not allow a duplicate "extras" declaration', ->
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
expect(func).to.throw Error, 'duplicate declaration of "extras"'
describe 'tools', ->
beforeEach ->
baseText = 'name:Test; version:0.0; item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
it 'can add a single tool', ->
modVersion = parser.parse baseText + 'tools: Furnace'
modVersion._items.bravo._recipes[0].tools[0].slug.should.equal 'furnace'
it 'can add multiple tools', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
tools = modVersion._items.bravo._recipes[0].tools
tools[0].slug.should.equal 'crafting_table'
tools[1].slug.should.equal 'furnace'
it 'registers slugs for each tool name', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
modVersion._slugs.should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
it 'does not allow a duplicate "tools" declaration', ->
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
expect(func).to.throw Error, 'duplicate declaration of "tools"'
+1 -1
View File
@@ -31,10 +31,10 @@ mocha.setup 'bdd'
require './crafting_plan.test' require './crafting_plan.test'
require './inventory.test' require './inventory.test'
require './inventory_parser.test' require './inventory_parser.test'
require './mod.test'
require './mod_pack.test' require './mod_pack.test'
require './mod_version.test' require './mod_version.test'
require './parser_versions/mod_version_parser_v1.test' require './parser_versions/mod_version_parser_v1.test'
require './parser_versions/mod_version_parser_v2.test'
require './string_builder.test' require './string_builder.test'
mocha.checkLeaks() mocha.checkLeaks()