diff --git a/src/data/applied_energistics_2/mod.cg b/src/data/applied_energistics_2/mod.cg
new file mode 100644
index 000000000..bb11209a5
--- /dev/null
+++ b/src/data/applied_energistics_2/mod.cg
@@ -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
diff --git a/src/data/applied_energistics_2/rv1-stable-1/mod-version.cg b/src/data/applied_energistics_2/rv1-stable-1/mod-version.cg
index ead19ea91..ecd00abdf 100644
--- a/src/data/applied_energistics_2/rv1-stable-1/mod-version.cg
+++ b/src/data/applied_energistics_2/rv1-stable-1/mod-version.cg
@@ -1,7 +1,4 @@
-schema: 2
-name: Applied Energistics 2
-version: rv1-stable-1
-description: Crafting recipes from Applied Energistics, by AlgorithmX2
+schema: 1
item: 128³ Spatial Component
recipe:
diff --git a/src/data/buildcraft/6.2.6/mod-version.cg b/src/data/buildcraft/6.2.6/mod-version.cg
index 639318640..cd2f3c70a 100644
--- a/src/data/buildcraft/6.2.6/mod-version.cg
+++ b/src/data/buildcraft/6.2.6/mod-version.cg
@@ -1,7 +1,4 @@
-schema: 2
-name: Buildcraft
-version: 6.2.6
-description: Crafting recipes from BuildCraft, by SpaceToad
+schema: 1
item: Advanced Crafting Table
recipe:
diff --git a/src/data/buildcraft/mod.cg b/src/data/buildcraft/mod.cg
new file mode 100644
index 000000000..c70796ac6
--- /dev/null
+++ b/src/data/buildcraft/mod.cg
@@ -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
diff --git a/src/data/ic2_classic/1.111.170-lf/mod-version.cg b/src/data/ic2_classic/1.111.170-lf/mod-version.cg
index 12352feec..e3081c683 100644
--- a/src/data/ic2_classic/1.111.170-lf/mod-version.cg
+++ b/src/data/ic2_classic/1.111.170-lf/mod-version.cg
@@ -1,7 +1,4 @@
-schema: 2
-name: IC2 Classic
-version: 1.111.170-lf
-description: Crafting recipes from IndustrialCraft, by Alblaka
+schema: 1
item: 10K Cooling Cell
recipe:
diff --git a/src/data/ic2_classic/mod.cg b/src/data/ic2_classic/mod.cg
new file mode 100644
index 000000000..274b9e31a
--- /dev/null
+++ b/src/data/ic2_classic/mod.cg
@@ -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
diff --git a/src/data/minecraft/1.7.10/mod-version.cg b/src/data/minecraft/1.7.10/mod-version.cg
index 42f5e8b7d..80456e7eb 100644
--- a/src/data/minecraft/1.7.10/mod-version.cg
+++ b/src/data/minecraft/1.7.10/mod-version.cg
@@ -1,7 +1,4 @@
-schema: 2
-name: Minecraft
-version: 1.7.10
-description: Crafting recipes from vanilla Minecraft
+schema: 1
item: Acacia Wood Planks
recipe:
diff --git a/src/data/minecraft/mod.cg b/src/data/minecraft/mod.cg
new file mode 100644
index 000000000..26a8e4795
--- /dev/null
+++ b/src/data/minecraft/mod.cg
@@ -0,0 +1,7 @@
+schema: 1
+
+author: Mojang
+description: The basic game by itself
+url: http://www.minecraft.net
+
+version: 1.7.10
diff --git a/src/scripts/constants.coffee b/src/scripts/constants.coffee
index c7e6bcfbf..2028d9b27 100644
--- a/src/scripts/constants.coffee
+++ b/src/scripts/constants.coffee
@@ -5,14 +5,8 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
-exports.DefaultModVersions = [
- # Current version of Minecraft must be first
- { 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' }
-]
+# Minecraft must be first
+exports.DefaultMods = [ 'Minecraft', 'Applied Energistics 2', 'Buildcraft', 'IC2 Classic' ]
exports.Duration = Duration = {}
Duration.snap = 100
@@ -41,9 +35,7 @@ exports.Opacity = Opacity = {}
Opacity.hidden = 1e-6
Opacity.shown = 1
-exports.RequiredMods = [
- 'Minecraft'
-]
+exports.RequiredMods = [ 'minecraft' ]
exports.ModelState = ModelState = {}
ModelState.unloaded = 'unloaded'
diff --git a/src/scripts/controllers/base_controller.coffee b/src/scripts/controllers/base_controller.coffee
index db4c5c187..cd1865934 100644
--- a/src/scripts/controllers/base_controller.coffee
+++ b/src/scripts/controllers/base_controller.coffee
@@ -105,7 +105,7 @@ module.exports = class BaseController extends Backbone.View
# Object Overrides #############################################################################
toString: ->
- return "#{@constructor.name}(#{@cid})"
+ return "#{@constructor.name}.#{@cid}"
# Private Methods ##############################################################################
diff --git a/src/scripts/controllers/crafting_table_controller.coffee b/src/scripts/controllers/crafting_table_controller.coffee
index 59ba056be..0b8d2074f 100644
--- a/src/scripts/controllers/crafting_table_controller.coffee
+++ b/src/scripts/controllers/crafting_table_controller.coffee
@@ -25,8 +25,6 @@ module.exports = class CraftingTableController extends BaseController
@imageLoader = options.imageLoader
@modPack = options.modPack
- @model.plan.silent = false
-
# Event Methods ################################################################################
onNextClicked: ->
diff --git a/src/scripts/controllers/item_page_controller.coffee b/src/scripts/controllers/item_page_controller.coffee
index 01fc32d48..20f98347e 100644
--- a/src/scripts/controllers/item_page_controller.coffee
+++ b/src/scripts/controllers/item_page_controller.coffee
@@ -26,8 +26,8 @@ module.exports = class ItemPageController extends BaseController
options.templateName = 'item_page'
super options
- @imageLoader = options.imageLoader
- @storage = options.storage
+ @_imageLoader = options.imageLoader
+ @_storage = options.storage
# Event Methods ################################################################################
@@ -37,23 +37,21 @@ module.exports = class ItemPageController extends BaseController
# BaseController Overrides #####################################################################
onWillRender: ->
- @storage.register 'crafting-plan', @model.plan, 'includingTools'
- @model.modPack.on Event.add, (modVersion)=>
- @storage.register "mod-version:#{modVersion.slug}", modVersion, 'enabled'
+ @_storage.register 'crafting-plan', @model.plan, 'includingTools'
super
onDidRender: ->
@wantController = @addChild InventoryController, '.want',
editable: true
icon: '/images/fishing_rod.png'
- imageLoader: @imageLoader
+ imageLoader: @_imageLoader
model: @model.plan.want
modPack: @model.modPack
title: 'Items you want'
@haveController = @addChild InventoryController, '.have',
editable: true,
- imageLoader: @imageLoader
+ imageLoader: @_imageLoader
model: @model.plan.have
modPack: @model.modPack
nameFinder: new NameFinder @model.modPack, includeGatherable:true
@@ -62,17 +60,20 @@ module.exports = class ItemPageController extends BaseController
@needController = @addChild InventoryController, '.need',
editable: false
icon: '/images/boots.png'
- imageLoader: @imageLoader
+ imageLoader: @_imageLoader
model: @model.plan.need
modPack: @model.modPack
title: "Items you'll need"
@craftingTableController = @addChild CraftingTableController, '.view__crafting_table',
- imageLoader: @imageLoader
+ imageLoader: @_imageLoader
model: @model.table
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 ''
@$includeToolsBox = @$('.includeTools')
diff --git a/src/scripts/controllers/mod_controller.coffee b/src/scripts/controllers/mod_controller.coffee
new file mode 100644
index 000000000..f5183bfb2
--- /dev/null
+++ b/src/scripts/controllers/mod_controller.coffee
@@ -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'
diff --git a/src/scripts/controllers/mod_pack_controller.coffee b/src/scripts/controllers/mod_pack_controller.coffee
index 8c8d03f29..7cdd5c9fe 100644
--- a/src/scripts/controllers/mod_pack_controller.coffee
+++ b/src/scripts/controllers/mod_pack_controller.coffee
@@ -5,22 +5,25 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
-BaseController = require './base_controller'
-{DefaultModVersions} = require '../constants'
-{Duration} = require '../constants'
-ModVersion = require '../models/mod_version'
-ModVersionController = require './mod_version_controller'
+BaseController = require './base_controller'
+{DefaultMods} = require '../constants'
+{Duration} = require '../constants'
+Mod = require '../models/mod'
+ModController = require './mod_controller'
########################################################################################################################
module.exports = class ModPackController extends BaseController
constructor: (options={})->
- if not options.model? then throw new Error "options.model is required"
- options.templateName = 'mod_pack'
+ 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'
super options
@_controllers = []
+ @_plan = options.plan
+ @_storage = options.storage
# Event Methods ################################################################################
@@ -31,10 +34,8 @@ module.exports = class ModPackController extends BaseController
# BaseController Overrides #####################################################################
onWillRender: ->
- for attributes in DefaultModVersions
- modVersion = new ModVersion attributes
- modVersion.fetch()
- @model.addModVersion modVersion
+ for name in DefaultMods
+ @model.addMod new Mod name:name
onDidRender: ->
@$table = @$('table')
@@ -48,14 +49,14 @@ module.exports = class ModPackController extends BaseController
return
index = 0
- modVersions = @model.getModVersions()
- while index < Math.min @_controllers.length, modVersions.length
+ mods = @model.getMods()
+ while index < Math.min @_controllers.length, mods.length
controller = @_controllers[index]
- controller.model = modVersions[index]
+ controller.model = mods[index]
index++
- while @_controllers.length < modVersions.length
- controller = new ModVersionController model:modVersions[index]
+ while @_controllers.length < mods.length
+ controller = new ModController model:mods[index], plan:@_plan, storage:@_storage
controller.render()
@_controllers.push controller
controller.$el.hide duration:0
@@ -63,7 +64,7 @@ module.exports = class ModPackController extends BaseController
controller.$el.slideDown duration:Duration.normal
index++
- while @_controllers.length > modVersions.length
+ while @_controllers.length > mods.length
controller = @_controllers.pop()
controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove()
diff --git a/src/scripts/controllers/mod_version_controller.coffee b/src/scripts/controllers/mod_version_controller.coffee
deleted file mode 100644
index e1a1df0cd..000000000
--- a/src/scripts/controllers/mod_version_controller.coffee
+++ /dev/null
@@ -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'
diff --git a/src/scripts/models/base_model.coffee b/src/scripts/models/base_model.coffee
index e6a174853..307b30bdb 100644
--- a/src/scripts/models/base_model.coffee
+++ b/src/scripts/models/base_model.coffee
@@ -13,7 +13,6 @@ All rights reserved.
module.exports = class BaseModel extends Backbone.Model
constructor: (attributes={}, options={})->
- options.silent ?= true
super attributes, options
makeGetter = (name)-> return -> @get name
@@ -22,7 +21,6 @@ module.exports = class BaseModel extends Backbone.Model
continue if name is 'id'
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
- @silent = options.silent
@state = ModelState.unloaded
@on 'request', => @state = ModelState.loading
@@ -31,27 +29,33 @@ module.exports = class BaseModel extends Backbone.Model
@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 ################################################################################
onLoadSucceeded: (text, status, xhr)->
try
@set @parse text
+ @trigger Event.change, this
@trigger Event.sync, this, text
+ logger.info "#{@constructor.name}.#{@cid} loaded successfully"
catch e
logger.error "A parsing error occured: #{e.stack}"
@onLoadFailed e.message, 'parsing failed', xhr
onLoadFailed: (error, status, xhr)->
- logger.error "#{@constructor.name} (#{@cid}) failed to load: status:#{status}, message:#{error}"
+ logger.error "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
@trigger Event.error, this, error
# Backbone.Model Overrides #####################################################################
fetch: ->
url = @url()
- logger.info "#{@constructor.name} (#{@cid}) reading from url: #{url}"
+ logger.info "#{@constructor.name}.#{@cid} reading from url: #{url}"
@trigger Event.request, this
@loading = w.promise (resolve, reject)=>
@@ -68,14 +72,9 @@ module.exports = class BaseModel extends Backbone.Model
return JSON.parse text
sync: (method, model)->
- 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
+ throw new Error "#{@constructor.name}.#{@cid} is not permitted to #{method}"
# Object Overrides #############################################################################
toString: ->
- return "#{@constructor.name} (#{@cid})"
+ return "#{@constructor.name}.#{@cid}"
diff --git a/src/scripts/models/crafting_plan.coffee b/src/scripts/models/crafting_plan.coffee
index 4d2faa0c5..5d5dce99a 100644
--- a/src/scripts/models/crafting_plan.coffee
+++ b/src/scripts/models/crafting_plan.coffee
@@ -23,29 +23,26 @@ module.exports = class CraftingPlan extends BaseModel
@need = new Inventory
@result = new Inventory
- @clear silent:true
+ @clear()
@have.on Event.change, => @craft()
@want.on Event.change, => @craft()
- @modPack.on Event.add, => @craft()
+ @modPack.on Event.change, => @craft()
@on Event.change + ':includingTools', => @craft()
# Public Methods ###############################################################################
clear: (options={})->
- options.silent ?= false
-
@steps = []
@need.clear()
@result.clear()
- @trigger 'change', this unless options.silent
+ @trigger 'change', this
return this
craft: ->
- @clear silent:true
- @need.silent = @result.silent = true
+ @clear()
@result.addInventory @have
@@ -59,11 +56,19 @@ module.exports = class CraftingPlan extends BaseModel
@_removeExtraSteps()
@result.addInventory @want
- @need.silent = @result.silent = false
@need.trigger 'change', @need
@result.trigger 'change', @result
@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 ################################################################################
onIncludingToolsChanged: ->
diff --git a/src/scripts/models/inventory.coffee b/src/scripts/models/inventory.coffee
index a060d5d02..fbc83d313 100644
--- a/src/scripts/models/inventory.coffee
+++ b/src/scripts/models/inventory.coffee
@@ -15,7 +15,7 @@ module.exports = class Inventory extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
- @clear silent:true
+ @clear()
Object.defineProperties this,
isEmpty: { get:-> @_slugs.length is 0 }
@@ -29,19 +29,16 @@ module.exports = class Inventory extends BaseModel
return this
addInventory: (inventory)->
- @silent = true
inventory.each (stack)=> @_add stack.slug, stack.quantity
- @silent = false
@trigger Event.change, this
return this
clear: (options={})->
- options.silent ?= false
@_stacks = {}
@_slugs = []
- @trigger Event.change, this unless options.silent
+ @trigger Event.change, this
clone: ->
inventory = new Inventory
@@ -75,11 +72,13 @@ module.exports = class Inventory extends BaseModel
return 0 unless stack?
return stack.quantity
- remove: (slug, quantity=1)->
+ remove: (slug, quantity=null)->
return if quantity is 0
stack = @_stacks[slug]
if not stack? then throw new Error "cannot remove #{slug} since it is not in this inventory"
+
+ quantity ?= stack.quantity
if stack.quantity < quantity
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{slug} in this inventory"
diff --git a/src/scripts/models/item_page.coffee b/src/scripts/models/item_page.coffee
index 0ce0ebc67..826e7ee89 100644
--- a/src/scripts/models/item_page.coffee
+++ b/src/scripts/models/item_page.coffee
@@ -22,7 +22,7 @@ module.exports = class ItemPage extends BaseModel
attributes.table ?= new CraftingTable plan:attributes.plan
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()
# Private Methods ##############################################################################
@@ -44,4 +44,4 @@ module.exports = class ItemPage extends BaseModel
slug = if _.isArray(list[0]) then list[0][1] else list[0]
router.navigate "/item/#{slug}"
else
- router.navigate "/"
\ No newline at end of file
+ router.navigate "/"
diff --git a/src/scripts/models/mod.coffee b/src/scripts/models/mod.coffee
index 0dbc90c63..4cf91b55c 100644
--- a/src/scripts/models/mod.coffee
+++ b/src/scripts/models/mod.coffee
@@ -5,8 +5,10 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
-BaseModel = require './base_model'
-{Url} = require '../constants'
+BaseModel = require './base_model'
+{Event} = require '../constants'
+{RequiredMods} = require '../constants'
+{Url} = require '../constants'
########################################################################################################################
@@ -14,12 +16,130 @@ module.exports = class Mod extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required'
- attributes.id = attributes.slug = _.slugify attributes.name
+ attributes.author ?= ''
+ attributes.description ?= ''
+ attributes.primaryUrl ?= null
+ attributes.slug ?= _.slugify attributes.name
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 ######################################################################
- 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: ->
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
diff --git a/src/scripts/models/mod_pack.coffee b/src/scripts/models/mod_pack.coffee
index 6157bb27a..8afdfe508 100644
--- a/src/scripts/models/mod_pack.coffee
+++ b/src/scripts/models/mod_pack.coffee
@@ -18,91 +18,86 @@ module.exports = class ModPack extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
- @_modVersions = []
+ @_mods = []
# Public Methods ###############################################################################
- findItem: (slug, options={})->
- options.includeDisabled ?= false
-
- for modVersion in @_modVersions
- continue unless modVersion.enabled or options.includeDisabled
- item = modVersion.findItem slug
+ findItem: (slug)->
+ for mod in @_mods
+ continue unless mod.enabled
+ item = mod.findItem slug
return item if item?
return null
- findItemByName: (name, options={})->
- options.includeDisabled ?= false
+ findItemByName: (name)->
slug = _.slugify name
- for modVersion in @_modVersions
- continue unless modVersion.enabled or options.includeDisabled
- item = modVersion.findItem slug
+ for mod in @_mods
+ continue unless mod.enabled?
+ item = mod.findItem slug
return item if item?
return null
- findName: (slug, options={})->
- options.includeDisabled ?= false
-
- for modVersion in @_modVersions
- continue unless modVersion.enabled or options.includeDisabled
- name = modVersion.findName slug
+ findName: (slug)->
+ for mod in @_mods
+ continue unless mod.enabled
+ name = mod.findName slug
return name if name
return null
findItemDisplay: (slug)->
result = {}
- item = @findItem slug, includeDisabled:true
+ item = @findItem slug
if item?
- result.modSlug = item.modVersion.slug
+ result.modSlug = item.modVersion.modSlug
result.modVersion = item.modVersion.version
- result.slug = item.slug
+ result.slug = item.slug
result.itemName = item.name
else
- result.modSlug = _.slugify DefaultModVersions[0].name
- result.modVersion = DefaultModVersions[0].version
- result.slug = slug
+ result.modSlug = @_mods[0].slug
+ result.modVersion = @_mods[0].activeVersion
+ result.slug = slug
result.itemName = @findName slug, includeDisabled:true
result.iconUrl = Url.itemIcon result
result.itemUrl = Url.item result
return result
- isValidName: (name, options={})->
- options.includeDisabled ?= false
-
+ isValidName: (name)->
slug = _.slugify name
- for modVersion in @_modVersions
- continue unless modVersion.enabled or options.includeDisabled
- name = modVersion.findName slug
+ for mod in @_mods
+ continue unless mod.enabled
+ name = mod.findName slug
return true if name
return false
# Property Methods #############################################################################
- addModVersion: (modVersion)->
- return if @_modVersions.indexOf(modVersion) isnt -1
+ addMod: (mod)->
+ return if @_mods.indexOf(mod) isnt -1
- @_modVersions.push modVersion
- @trigger Event.add, modVersion, this
+ @_mods.push mod
+ @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
return this
- eachModVersion: (callback)->
- for modVersion in @_modVersions
- callback modVersion
+ eachMod: (callback)->
+ for mod in @_mods
+ callback mod
- getModVersions: ->
- return @_modVersions[..]
+ getMods: ->
+ return @_mods[..]
# Object Overrides #############################################################################
toString: ->
- return "ModPack (#{@cid}) {modVersions:#{@_modVersions.length} items}"
+ return "ModPack (#{@cid}) {modVersions:«#{@_mods.length} items»}"
diff --git a/src/scripts/models/mod_parser.coffee b/src/scripts/models/mod_parser.coffee
new file mode 100644
index 000000000..e28aa34d7
--- /dev/null
+++ b/src/scripts/models/mod_parser.coffee
@@ -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
diff --git a/src/scripts/models/mod_version.coffee b/src/scripts/models/mod_version.coffee
index 13735d3f4..0d21622dc 100644
--- a/src/scripts/models/mod_version.coffee
+++ b/src/scripts/models/mod_version.coffee
@@ -17,12 +17,8 @@ Item = require './item'
module.exports = class ModVersion extends BaseModel
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'
-
- attributes.description ?= ''
- attributes.enabled ?= true
- attributes.slug ?= _.slugify attributes.name
super attributes, options
@_items = {}
@@ -40,19 +36,13 @@ module.exports = class ModVersion extends BaseModel
return this
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
- thatRequired = that.name in RequiredMods
+ if this.modSlug isnt that.modSlug
+ return if this.modSlug < that.modSlug then -1 else +1
- if thisRequired and thatRequired
- 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
+ return 0
eachItem: (callback)->
for slug in @_slugs
@@ -87,27 +77,18 @@ module.exports = class ModVersion extends BaseModel
# Backbone.Model Overrides #####################################################################
parse: (text)->
- currentSilent = @silent
- @silent = true
-
ModVersionParser = require './mod_version_parser' # to avoid require cycles
@_parser ?= new ModVersionParser model:this
@_parser.parse text
- @silent = currentSilent
- @trigger Event.change, this
-
return null # prevent calling `set`
url: ->
- return Url.modVersion modSlug:@slug, modVersion:@version
+ return Url.modVersion modSlug:@modSlug, modVersion:@version
# Object Overrides #############################################################################
toString: ->
return "ModVersion (#{@cid}) {
- enabled:#{@enabled},
- name:#{@name},
- version:#{@version},
- items:#{_.keys(@_items).length} items
+ modSlug:#{@modSlug}, version:#{@version}, items:#{_.keys(@_items).length} items
}"
diff --git a/src/scripts/models/mod_version_parser.coffee b/src/scripts/models/mod_version_parser.coffee
index 88ee65228..41b3dbe07 100644
--- a/src/scripts/models/mod_version_parser.coffee
+++ b/src/scripts/models/mod_version_parser.coffee
@@ -5,54 +5,15 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
-Logger = require '../logger'
-ModVersionParserV1 = require './parser_versions/mod_version_parser_v1'
-ModVersionParserV2 = require './parser_versions/mod_version_parser_v2'
+VersionedParserBase = require './versioned_parser_base'
+ModVersionParserV1 = require './parser_versions/mod_version_parser_v1'
########################################################################################################################
-module.exports = class ModVersionParser
+module.exports = class ModVersionParser extends VersionedParserBase
- @CURRENT_VERSION = '2'
+ # VersionedParserBase Overrides ################################################################
- constructor: (options={})->
- if not options.model? then throw new Error 'options.model is required'
- options.showAllErrors ?= false
-
- @_model = options.model
- @_parsers =
+ _createParsers: (options)->
+ return result =
'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
diff --git a/src/scripts/models/name_finder.coffee b/src/scripts/models/name_finder.coffee
index 12e39d30e..f745a6567 100644
--- a/src/scripts/models/name_finder.coffee
+++ b/src/scripts/models/name_finder.coffee
@@ -31,7 +31,7 @@ module.exports = class NameFinder
names = @_findNames nameHint
names.sort (a, b)->
- c = a.modVersion.compareTo b.modVersion
+ c = a.mod.compareTo b.mod
if c isnt 0 then return c
return 0 if a.label is b.label
@@ -59,21 +59,21 @@ module.exports = class NameFinder
names = []
nameMap = {}
- @modPack.eachModVersion (modVersion)=>
- return unless modVersion.enabled or @includeDisabledMods
+ @modPack.eachMod (mod)=>
+ return unless mod.enabled or @includeDisabledMods
- modVersion.eachName (name, slug)=>
+ mod.eachName (name, slug)=>
return if nameMap[name]
- item = modVersion.findItem slug
+ item = mod.findItem slug
if not @includeGatherable
return unless item? and (not item.isGatherable)
- scanName = "#{modVersion.name} : #{name}"
+ scanName = "#{mod.name} : #{name}"
if nameHint?
return unless @_isMatch scanName.toLowerCase(), nameHint
nameMap[name] = name
- names.push value:name, label:scanName, modVersion:modVersion
+ names.push value:name, label:scanName, mod:mod
return names
diff --git a/src/scripts/models/command_parser_base.coffee b/src/scripts/models/parser_versions/command_parser_version_base.coffee
similarity index 90%
rename from src/scripts/models/command_parser_base.coffee
rename to src/scripts/models/parser_versions/command_parser_version_base.coffee
index ce2e85468..d3634b845 100644
--- a/src/scripts/models/command_parser_base.coffee
+++ b/src/scripts/models/parser_versions/command_parser_version_base.coffee
@@ -1,5 +1,5 @@
###
-Crafting Guide - command_parser_base.coffee
+Crafting Guide - command_parser_version_base.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
@@ -7,7 +7,7 @@ All rights reserved.
########################################################################################################################
-module.exports = class CommandParserBase
+module.exports = class CommandParserVersionBase
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
@@ -51,6 +51,8 @@ module.exports = class CommandParserBase
_unparseModel: (builder, model)->
throw new Error 'Subclasses must override this method'
+ _command_schema: -> # do nothing
+
# Private Methods ##############################################################################
_execute: (command)->
@@ -60,7 +62,7 @@ module.exports = class CommandParserBase
@_handleErrors method, command.args
_parseLine: (line)->
- line = line.replace CommandParserBase.COMMENT, '$1'
+ line = line.replace CommandParserVersionBase.COMMENT, '$1'
line = line.trim()
return [] if line.length is 0
@@ -69,7 +71,7 @@ module.exports = class CommandParserBase
for linePart in lineParts
continue if linePart.length is 0
- match = CommandParserBase.COMMAND.exec linePart
+ match = CommandParserVersionBase.COMMAND.exec linePart
if not match? then throw new Error "Expected : , but found: \"#{linePart}\""
args = []
diff --git a/src/scripts/models/parser_versions/mod_parser_v1.coffee b/src/scripts/models/parser_versions/mod_parser_v1.coffee
new file mode 100644
index 000000000..bec7d0b03
--- /dev/null
+++ b/src/scripts/models/parser_versions/mod_parser_v1.coffee
@@ -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
diff --git a/src/scripts/models/parser_versions/mod_version_parser_v1.coffee b/src/scripts/models/parser_versions/mod_version_parser_v1.coffee
index d6d4b9c8a..a568fa2ad 100644
--- a/src/scripts/models/parser_versions/mod_version_parser_v1.coffee
+++ b/src/scripts/models/parser_versions/mod_version_parser_v1.coffee
@@ -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
All rights reserved.
###
-Item = require '../item'
-ModVersion = require '../mod_version'
-Recipe = require '../recipe'
-Stack = require '../stack'
+CommandParserVersionBase = require './command_parser_version_base'
+Item = require '../item'
+ModVersion = require '../mod_version'
+Recipe = require '../recipe'
+Stack = require '../stack'
+StringBuilder = require '../string_builder'
########################################################################################################################
-module.exports = class ModVersionParserV1
+module.exports = class ModVersionParserV1 extends CommandParserVersionBase
- constructor: (options={})->
- if not options.model? then throw new Error 'options.model is required'
- @_model = options.model
- @_errorLocation = 'the header information'
+ # Class Methods ################################################################################
- parse: (data)->
- return @_parseModVersion data
+ @INTEGER = /[0-9]+/
- unparse: ->
- return @_unparseModVersion()
+ @PATTERN = /^[0-9.]{3} ?[0-9.]{3} ?[0-9.]{3}$/
- # Private Methods ##############################################################################
+ @STACK = /^([0-9]+) +(.*)$/
- _computeDefaultPattern: (input)->
- itemCount = input.length
- slotCount = _.reduce input, ((total, stack)-> total + stack.quantity), 0
+ # CommandParserVersionBase Overrides ###########################################################
- return '... .0. ...' if itemCount is 1 and slotCount is 1
- return '00. 00. ...' if itemCount is 1 and slotCount is 4
- return '000 000 000' if itemCount is 1 and slotCount is 9
+ _buildModel: (rawData, model)->
+ @_buildModVersion rawData, model
- result = ['.', '.', '.', '.', '.', '.', '.', '.', '.']
- indexes = [4, 7, 1, 3, 5, 6, 8, 0, 2]
+ _unparseModel: (builder, model)->
+ @_unparseModVersion builder, model
- for i in [0...input.length]
- stack = input[i]
- for j in [0...stack.quantity]
- index = indexes.shift()
- result[index] = "#{i}"
+ # Command Methods ##############################################################################
- pattern = result.join ''
- pattern = pattern.replace /(...)(...)(...)/, '$1 $2 $3'
- return pattern
+ _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 = 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
- # 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)->
- if not data? then throw new Error 'mod description data is missing'
- if not data.name? then throw new Error 'name is required'
- 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'
+ recipeData.quantity ?= 1
+ recipeData.extras ?= []
+ recipeData.tools ?= []
- if data.name isnt @_model.name
- throw new Error "the data is for #{data.name}, not #{@_model.name} as expected"
- if data.version isnt @_model.version
- throw new Error "the data is for version #{data.version}, not #{@_model.version} as expected"
+ inputStacks = []
+ for name in recipeData.input
+ slug = _.slugify name
+ modVersion.registerSlug slug, name
+ inputStacks.push new Stack slug:slug, quantity:0
- @_model.description = data.description or ''
- @_parseRawMaterials data.raw_materials
+ 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 index in [0...data.recipes.length]
- @_errorLocation = "recipe #{index + 1}"
- recipeData = data.recipes[index]
- recipe = @_parseRecipe recipeData
- recipe._originalIndex = index
+ 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"
- 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)->
- return unless data? and data.length > 0
-
- results = []
- for name in data
- 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 ?= []
+ toolStacks = []
+ for name in recipeData.tools
+ slug = _.slugify name
+ modVersion.registerSlug slug, name
+ toolStacks.push new Stack slug:slug, quantity:1
attributes =
- item: item,
- output: @_parseStackList(data.output, field:'output', canBeEmpty:false)
- input: @_parseStackList(data.input, field:'input', canBeEmpty:true)
- tools: @_parseStackList(data.tools, field:'tools', canBeEmpty:true)
- attributes.pattern = data.pattern or @_computeDefaultPattern attributes.input
+ input: inputStacks
+ name: item.name
+ pattern: recipeData.pattern
+ output: outputStacks
+ tools: toolStacks
recipe = new Recipe attributes
+ item.addRecipe 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 ###########################################################################
- _unparseModVersion: (modVersion)->
- result = []
- result.push '{\n'
- 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'
+ _unparseModVersion: (builder, modVersion)->
+ itemList = _.values modVersion.items
+ itemList.sort (a, b)-> a.compareTo b
- rawMaterials = (item.name for slug, item of modVersion.items when item.isGatherable)
- rawMaterials.sort()
- if rawMaterials.length > 0
- result.push ' "raw_materials": [\n'
- firstItem = true
- for material in rawMaterials
- if not firstItem then result.push ',\n'
- result.push ' "' + material + '"'
- firstItem = false
- result.push '\n ],\n'
+ 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()
- 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)
- items.sort (a, b)-> a.compareTo b
+ _unparseRecipe: (builder, recipe)->
+ inputNames = (builder.context.findName(stack.slug) for stack in recipe.input)
+ inputNames.sort()
- firstItem = true
- for item in items
- for recipe in item.recipes
- result.push if firstItem then ' {\n' else ' }, {\n'
- @_unparseRecipe recipe, result
- firstItem = false
- result.push ' }\n'
+ patternMap = {'.', '.'}
+ for i in [0...recipe.input.length]
+ stack = recipe.input[i]
+ patternMap["#{i}"] = "#{inputNames.indexOf builder.context.findName(stack.slug)}"
- result.push ' ]\n'
- result.push '}'
+ 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'
- return result.join ''
+ quantity = recipe.output[0].quantity
- _unparseRecipe: (recipe, result=[])->
- result.push ' "output": '
- @_unparseStackList recipe.output, result, sort:false
+ extraOutputs = recipe.output[0...recipe.output.length]
+ extraOutputs.shift()
- if recipe.input.length > 0
- result.push ',\n'
- result.push ' "input": '
- @_unparseStackList recipe.input, result
+ 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()
- if recipe.pattern?
- result.push ',\n'
- result.push ' "pattern": "'
- 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
- result.push '[[' + stack.quantity + ', "' + @_model.findName(stack.slug) + '"]]'
+ _unparseStackList: (builder, stackList)->
+ if stackList.length is 1 and stackList[0].quantity is 1
+ builder.push builder.context.findName(stackList[0].slug)
else
- result.push '['
-
- 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
+ builder.loop stackList, onEach:(b, stack)=>
+ builder
+ .onlyIf stack.quantity > 1, => builder.push stack.quantity, ' '
+ .push builder.context.findName stack.slug
diff --git a/src/scripts/models/parser_versions/mod_version_parser_v2.coffee b/src/scripts/models/parser_versions/mod_version_parser_v2.coffee
deleted file mode 100644
index c5e2f721a..000000000
--- a/src/scripts/models/parser_versions/mod_version_parser_v2.coffee
+++ /dev/null
@@ -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
diff --git a/src/scripts/models/versioned_parser_base.coffee b/src/scripts/models/versioned_parser_base.coffee
new file mode 100644
index 000000000..aea488191
--- /dev/null
+++ b/src/scripts/models/versioned_parser_base.coffee
@@ -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]
diff --git a/src/templates/mod_version.jade b/src/templates/mod.jade
similarity index 78%
rename from src/templates/mod_version.jade
rename to src/templates/mod.jade
index 42c554cc1..0754aa94d 100644
--- a/src/templates/mod_version.jade
+++ b/src/templates/mod.jade
@@ -1,5 +1,5 @@
//-
-//- Crafting Guide - mod_version.jade
+//- Crafting Guide - mod.jade
//-
//- Copyright (c) 2014-2015 by Redwood Labs
//- All rights reserved.
diff --git a/test/crafting_plan.test.coffee b/test/crafting_plan.test.coffee
index ff2278585..94af06007 100644
--- a/test/crafting_plan.test.coffee
+++ b/test/crafting_plan.test.coffee
@@ -6,8 +6,9 @@ All rights reserved.
###
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'
+ModVersion = require '../src/scripts/models/mod_version'
########################################################################################################################
@@ -18,9 +19,10 @@ modPack = plan = null
describe 'CraftingPlan', ->
beforeEach ->
- modVersion = new ModVersion name:'Minecraft', version:'1.7.10'
- modVersion.parse """
- schema:2; name:Minecraft; version:1.7.10
+ mod = new Mod name:'Minecraft'
+ mod.addModVersion new ModVersion modSlug:mod.slug, version:'1.7.10'
+ mod.activeModVersion.parse """
+ schema:1
item:Oak Plank; recipe:; input:Oak Log; pattern:... .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
"""
modPack = new ModPack
- modPack.addModVersion modVersion
+ modPack.addMod mod
plan = new CraftingPlan modPack:modPack, includingTools:false
diff --git a/test/inventory.test.coffee b/test/inventory.test.coffee
index c49f605e2..c056a76e9 100644
--- a/test/inventory.test.coffee
+++ b/test/inventory.test.coffee
@@ -135,9 +135,9 @@ describe 'Inventory', ->
expect(-> inventory.remove('wool', 10)).to.throw Error,
'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._stacks.wool.quantity.should.equal 3
+ expect(inventory._stacks.wool).to.be.empty
it 'removes a quantity above 1', ->
inventory.remove 'wool', 3
diff --git a/test/mod.test.coffee b/test/mod.test.coffee
new file mode 100644
index 000000000..9b11f3c05
--- /dev/null
+++ b/test/mod.test.coffee
@@ -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
diff --git a/test/mod_pack.test.coffee b/test/mod_pack.test.coffee
index 8d872754c..d1fb0f408 100644
--- a/test/mod_pack.test.coffee
+++ b/test/mod_pack.test.coffee
@@ -6,6 +6,7 @@ All rights reserved.
###
Item = require '../src/scripts/models/item'
+Mod = require '../src/scripts/models/mod'
ModPack = require '../src/scripts/models/mod_pack'
ModVersion = require '../src/scripts/models/mod_version'
@@ -18,23 +19,28 @@ buildcraft = industrialCraft = minecraft = modPack = null
describe 'ModPack', ->
beforeEach ->
- minecraft = new ModVersion name:'Minecraft', version:'1.7.10', enabled:true
- minecraft.addItem new Item name:'Wool'
- minecraft.addItem new Item name:'Bed', recipes:['']
- minecraft.registerSlug 'iron_chestplate', 'Iron Chestplate'
+ minecraft = new Mod name:'Minecraft'
+ minecraft.addModVersion new ModVersion modSlug:minecraft.slug, version:'1.7.10'
+ minecraft.activeModVersion.addItem new Item name:'Wool'
+ 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.addItem new Item name:'Stone Gear', recipes:['']
- buildcraft.addItem new Item name:'Bed', recipes:['']
+ buildcraft = new Mod name:'Buildcraft'
+ buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6'
+ 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.addItem new Item name:'Resin'
- industrialCraft.addItem new Item name:'Rubber'
+ industrialCraft = new Mod name:'Industrial Craft'
+ industrialCraft.addModVersion new ModVersion modSlug:industrialCraft.slug, version:'2.0'
+ industrialCraft.activeModVersion.addItem new Item name:'Resin'
+ industrialCraft.activeModVersion.addItem new Item name:'Rubber'
+ industrialCraft.activeVersion = Mod.Version.None
modPack = new ModPack
- modPack.addModVersion minecraft
- modPack.addModVersion buildcraft
- modPack.addModVersion industrialCraft
+ modPack.addMod minecraft
+ modPack.addMod buildcraft
+ modPack.addMod industrialCraft
describe 'findItemByName', ->
@@ -46,10 +52,6 @@ describe 'ModPack', ->
item = modPack.findItemByName 'Stone Gear'
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', ->
it 'returns all data for a regular Minecraft item', ->
@@ -60,17 +62,13 @@ describe 'ModPack', ->
display.modSlug.should.equal 'minecraft'
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.iconUrl.should.equal '/data/buildcraft/6.2.6/images/stone_gear.png'
display.itemUrl.should.equal '/item/Stone%20Gear'
display.itemName.should.equal 'Stone Gear'
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', ->
display = modPack.findItemDisplay 'iron_chestplate'
display.iconUrl.should.equal '/data/minecraft/1.7.10/images/iron_chestplate.png'
diff --git a/test/mod_version.test.coffee b/test/mod_version.test.coffee
index a5fee6f41..6914517aa 100644
--- a/test/mod_version.test.coffee
+++ b/test/mod_version.test.coffee
@@ -16,20 +16,15 @@ modVersion = null
describe 'ModVersion', ->
- beforeEach -> modVersion = new ModVersion name:'Test', version:'0.0'
+ beforeEach -> modVersion = new ModVersion modSlug:'test', version:'0.0'
describe 'constructor', ->
- it 'requires a mod name', ->
- expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.name is required'
+ it 'requires a mod slug', ->
+ expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.modSlug is required'
it 'requires a mod version', ->
- expect(-> new ModVersion name:'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'
+ expect(-> new ModVersion modSlug:'test').to.throw Error, 'attributes.version is required'
describe 'addItem', ->
@@ -45,18 +40,6 @@ describe 'ModVersion', ->
modVersion.addItem new Item name:'Wool'
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', ->
it 'locates items by slugified name', ->
diff --git a/test/parser_versions/mod_version_parser_v1.test.coffee b/test/parser_versions/mod_version_parser_v1.test.coffee
index a42c04102..35e30a5e7 100644
--- a/test/parser_versions/mod_version_parser_v1.test.coffee
+++ b/test/parser_versions/mod_version_parser_v1.test.coffee
@@ -1,159 +1,209 @@
###
Crafting Guide - mod_version_parser_v1.test.coffee
-Copyright (c) 2014-2015 by Redwood Labs
+Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
-Item = require '../../src/scripts/models/item'
ModVersion = require '../../src/scripts/models/mod_version'
ModVersionParserV1 = require '../../src/scripts/models/parser_versions/mod_version_parser_v1'
########################################################################################################################
-modVersion = parser = null
+baseText = modVersion = parser = null
########################################################################################################################
-describe "ModVersionParserV1", ->
+describe 'ModVersionParserV1', ->
beforeEach ->
- modVersion = new ModVersion name:'Test', version:'0.0'
- parser = new ModVersionParserV1 model:modVersion
+ modVersion = new ModVersion modSlug:'test', version:'0.0'
+ parser = new ModVersionParserV1 model:modVersion
- describe '_parseModVersion', ->
+ describe 'Item', ->
- it 'requires a mod_name', ->
- data = version:'1.0', items:[]
- expect(-> parser._parseModVersion data).to.throw Error, 'name is required'
+ it 'allows multiple recipes', ->
+ recipes = "item: Charlie;
+ 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', ->
- data = name:'Empty', items:[]
- expect(-> parser._parseModVersion data).to.throw Error, 'version is required'
+ describe 'name', ->
- it 'can parse an empty modVersion', ->
- data =
- name: 'Test'
- version: '0.0'
- recipes: []
- modVersion = parser._parseModVersion data
- modVersion.name.should.equal 'Test'
- modVersion.version.should.equal '0.0'
+ it 'adds the name when present', ->
+ modVersion = parser.parse 'item: Charlie'
+ modVersion._items.charlie.name.should.equal 'Charlie'
- it 'can parse a non-empty mod version', ->
- data =
- name: 'Test'
- 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']
+ it 'requires a non-empty name', ->
+ func = -> parser.parse 'item: \n'
+ expect(func).to.throw Error, 'cannot be empty'
- describe '_parseRawMaterials', ->
+ describe 'gatherable', ->
- it 'skips the section when missing', ->
- parser._parseRawMaterials null
- _.keys(modVersion._items).length.should.equal 0
+ it 'adds "gatherable" when present', ->
+ modVersion = parser.parse 'item: Alpha Bravo; gatherable: yes'
+ modVersion._items.alpha_bravo.isGatherable.should.be.true
- it 'skips the section when empty', ->
- parser._parseRawMaterials []
- _.keys(modVersion._items).length.should.equal 0
+ it 'does not allow a duplicate "gatherable" declaration', ->
+ func = -> parser.parse 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
+ expect(func).to.throw Error, 'duplicate'
- it 'adds items marked as gatherable', ->
- parser._parseRawMaterials ['Wool']
- modVersion._items['wool'].isGatherable.should.be.true
+ it 'requires "gatherable" to be "yes" or "no"', ->
+ func = -> parser.parse 'item: Alpha Bravo; gatherable: true'
+ expect(func).to.throw Error, 'gatherable must be'
- it 'marks an existing item as gatherable', ->
- modVersion.addItem new Item name:'Wool'
- modVersion._items['wool'].isGatherable.should.be.false
- parser._parseRawMaterials ['Wool']
- modVersion._items['wool'].isGatherable.should.be.true
+ it 'does not allow "gatherable" before "item"', ->
+ func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
+ expect(func).to.throw Error, '"gatherable" before "item"'
- it 'registers the names of the items', ->
- parser._parseRawMaterials ['Wool']
- modVersion._names['wool'].should.equal 'Wool'
+ describe 'Recipe', ->
- describe '_parseRecipe', ->
+ beforeEach -> baseText = 'item: Charlie; '
- it 'requires output to be defined', ->
- parser._errorLocation = 'boat'
- test = -> parser._parseRecipe {input:'wool'}
- expect(test).to.throw Error, 'boat is missing output'
+ describe 'input', ->
- it 'requires input to be defined', ->
- test = -> parser._parseRecipe {output:'wool'}
- expect(test).to.throw Error, 'recipe for wool is missing 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 'can parse a regular recipe', ->
- data =
- output: 'bed'
- 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 'requires an "input" declaration', ->
+ func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
+ expect(func).to.throw Error, 'the "input" declaration is required'
- it 'can parse a recipe without tools', ->
- recipe = parser._parseRecipe {output:'sugar', input:'sugar cane'}
- (stack.slug for stack in recipe.output).should.eql ['sugar']
- (stack.slug for stack in recipe.input).sort().should.eql ['sugar_cane']
- (stack.slug for stack in recipe.tools).should.eql []
+ 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 'registers all names', ->
- data =
- output: 'Bed'
- input: [[3, 'Oak Wood Planks'], [3, 'Wool']]
- tools: 'Crafting Table'
- parser._parseRecipe data
- modVersion._slugs.should.eql ['bed', 'crafting_table', 'oak_wood_planks', 'wool']
+ 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"'
- 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', ->
- 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"
+ describe 'pattern', ->
- it 'can fill in a missing number', ->
- stack = parser._parseStack 'boat'
- stack.slug.should.equal 'boat'
- stack.quantity.should.equal 1
+ 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.'
- stack2 = parser._parseStack ['boat']
- stack2.slug.should.equal 'boat'
- stack2.quantity.should.equal 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 'requires the data to start with a number', ->
- parser._errorLocation = 'boat'
- options = index:1, field:'output'
- expect(-> parser._parseStack(['2', 'wool'], options)).to.throw Error,
- "output element 1 for boat must start with a number"
+ 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 'can parse a basic item', ->
- stack = parser._parseStack [2, 'wool']
- stack.constructor.name.should.equal 'Stack'
+ 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'
- describe '_parseStackList', ->
+ 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 'can promote a single item to a list', ->
- list = parser._parseStackList 'boat'
- (stack.slug for stack in list).should.eql ['boat']
+ 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 'can require a list to be non-empty', ->
- parser._errorLocation = 'boat'
- options = field:'output', canBeEmpty:false
- expect(-> parser._parseStackList [], options).to.throw Error, 'output for boat cannot be empty'
+ 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 'can allow an empty list', ->
- list = parser._parseStackList [], canBeEmpty:true
- list.length.should.equal 0
+ 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 'can parse a non-empty list', ->
- list = parser._parseStackList [[3, 'plank'], [3, 'wool']]
- (stack.slug for stack in list).sort().should.eql ['plank', 'wool']
+ 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
+
+ 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 '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 = '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"'
diff --git a/test/parser_versions/mod_version_parser_v2.test.coffee b/test/parser_versions/mod_version_parser_v2.test.coffee
deleted file mode 100644
index c39465f90..000000000
--- a/test/parser_versions/mod_version_parser_v2.test.coffee
+++ /dev/null
@@ -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"'
diff --git a/test/test.coffee b/test/test.coffee
index 70dc883f8..f1b209ceb 100644
--- a/test/test.coffee
+++ b/test/test.coffee
@@ -31,10 +31,10 @@ mocha.setup 'bdd'
require './crafting_plan.test'
require './inventory.test'
require './inventory_parser.test'
+require './mod.test'
require './mod_pack.test'
require './mod_version.test'
require './parser_versions/mod_version_parser_v1.test'
-require './parser_versions/mod_version_parser_v2.test'
require './string_builder.test'
mocha.checkLeaks()