From d1621bb9f61eafc3ee01caccabe5e33c733f2a8c Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Sun, 18 Jan 2015 10:48:12 -0800 Subject: [PATCH] Refactor ModVersion into Mod and ModVersion * Remove V1 of the mod version parser and shift V2 up to be V1 * Add a parser for mod.cg files, and refactor the the mod-version.cg files to push the appropriate content up to the mod.cg * Remove the concept of "silent" from BaseModel along with logging each event as they are fired (which is what really prompted the need for it in the first place) * Move use of the Storage class to be completely the province of the controllers most closely related to models affected * Convert the ModVersionController to the ModController and have it take on responsibility for choosing the mod version * Update BaseModel to support simple accessors for the current state * Update CraftingPlan to be able to remove uncraftable items * Implement the Mod model --- src/data/applied_energistics_2/mod.cg | 7 + .../rv1-stable-1/mod-version.cg | 5 +- src/data/buildcraft/6.2.6/mod-version.cg | 5 +- src/data/buildcraft/mod.cg | 7 + .../ic2_classic/1.111.170-lf/mod-version.cg | 5 +- src/data/ic2_classic/mod.cg | 7 + src/data/minecraft/1.7.10/mod-version.cg | 5 +- src/data/minecraft/mod.cg | 7 + src/scripts/constants.coffee | 14 +- .../controllers/base_controller.coffee | 2 +- .../crafting_table_controller.coffee | 2 - .../controllers/item_page_controller.coffee | 21 +- src/scripts/controllers/mod_controller.coffee | 69 +++ .../controllers/mod_pack_controller.coffee | 35 +- .../controllers/mod_version_controller.coffee | 49 --- src/scripts/models/base_model.coffee | 23 +- src/scripts/models/crafting_plan.coffee | 21 +- src/scripts/models/inventory.coffee | 11 +- src/scripts/models/item_page.coffee | 4 +- src/scripts/models/mod.coffee | 128 +++++- src/scripts/models/mod_pack.coffee | 77 ++-- src/scripts/models/mod_parser.coffee | 19 + src/scripts/models/mod_version.coffee | 35 +- src/scripts/models/mod_version_parser.coffee | 51 +-- src/scripts/models/name_finder.coffee | 14 +- .../command_parser_version_base.coffee} | 10 +- .../parser_versions/mod_parser_v1.coffee | 63 +++ .../mod_version_parser_v1.coffee | 406 +++++++++--------- .../mod_version_parser_v2.coffee | 284 ------------ .../models/versioned_parser_base.coffee | 50 +++ src/templates/{mod_version.jade => mod.jade} | 2 +- test/crafting_plan.test.coffee | 12 +- test/inventory.test.coffee | 4 +- test/mod.test.coffee | 30 ++ test/mod_pack.test.coffee | 42 +- test/mod_version.test.coffee | 25 +- .../mod_version_parser_v1.test.coffee | 280 +++++++----- .../mod_version_parser_v2.test.coffee | 265 ------------ test/test.coffee | 2 +- 39 files changed, 922 insertions(+), 1176 deletions(-) create mode 100644 src/data/applied_energistics_2/mod.cg create mode 100644 src/data/buildcraft/mod.cg create mode 100644 src/data/ic2_classic/mod.cg create mode 100644 src/data/minecraft/mod.cg create mode 100644 src/scripts/controllers/mod_controller.coffee delete mode 100644 src/scripts/controllers/mod_version_controller.coffee create mode 100644 src/scripts/models/mod_parser.coffee rename src/scripts/models/{command_parser_base.coffee => parser_versions/command_parser_version_base.coffee} (90%) create mode 100644 src/scripts/models/parser_versions/mod_parser_v1.coffee delete mode 100644 src/scripts/models/parser_versions/mod_version_parser_v2.coffee create mode 100644 src/scripts/models/versioned_parser_base.coffee rename src/templates/{mod_version.jade => mod.jade} (78%) create mode 100644 test/mod.test.coffee delete mode 100644 test/parser_versions/mod_version_parser_v2.test.coffee 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()