diff --git a/decode.py b/decode.py index e6a1516..c89e2c4 100755 --- a/decode.py +++ b/decode.py @@ -1,6 +1,8 @@ #!/usr/bin/env python import sys import os +import zipfile +import shutil libdir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib') sys.path.append(libdir) @@ -14,7 +16,8 @@ def exclude_sets(cardset): return cardset == 'Unglued' or cardset == 'Unhinged' or cardset == 'Celebration' def main(fname, oname = None, verbose = True, - gatherer = False, for_forum = False, creativity = False, norarity = False): + gatherer = False, for_forum = False, for_mse = False, + creativity = False, norarity = False): cards = [] valid = 0 invalid = 0 @@ -107,9 +110,19 @@ def main(fname, oname = None, verbose = True, namediff = Namediff() def writecards(writer): + if for_mse: + # have to prepend a massive chunk of formatting info + writer.write(utils.mse_prepend) for card in cards: - writer.write((card.format(gatherer = gatherer, for_forum = for_forum)).encode('utf-8')) + if for_mse: + writer.write(card.to_mse().encode('utf-8')) + else: + writer.write(card.format(gatherer = gatherer, + for_forum = for_forum).encode('utf-8')) + if creativity: + if for_mse: + writer.write('\tnotes:\n\t\t'.encode('utf-8')) writer.write('~~ closest cards ~~\n'.encode('utf-8')) nearest = cbow.nearest(card) for dist, cardname in nearest: @@ -126,11 +139,31 @@ def main(fname, oname = None, verbose = True, writer.write((cardname + ': ' + str(dist) + '\n').encode('utf-8')) writer.write('\n'.encode('utf-8')) + if for_mse: + # more formatting info + writer.write('version control:\n\ttype: none\napprentice code: ') + if oname: if verbose: print 'Writing output to: ' + oname with open(oname, 'w') as ofile: writecards(ofile) + if for_mse: + # Copy whatever output file is produced, name the copy 'set' (yes, no extension). + if os.path.isfile('set'): + print 'ERROR: tried to overwrite existing file "set" - aborting.' + return + shutil.copyfile(oname, 'set') + # Use the freaky mse extension instead of zip. + with zipfile.ZipFile(oname+'.mse-set', mode='w') as zf: + try: + # Zip up the set file into oname.mse-set. + zf.write('set') + finally: + if verbose: + print 'Made an MSE set file called ' + oname + '.mse-set.' + # The set file is useless outside the .mse-set, delete it. + os.remove('set') else: writecards(sys.stdout) sys.stdout.flush() @@ -154,9 +187,10 @@ if __name__ == '__main__': help='the card format has no rarity field; use for legacy input') parser.add_argument('-v', '--verbose', action='store_true', help='verbose output') + parser.add_argument('-mse', '--mse', action='store_true', help='use Magic Set Editor 2 encoding; will output as .mse-set file') args = parser.parse_args() main(args.infile, args.outfile, verbose = args.verbose, - gatherer = args.gatherer, for_forum = args.forum, creativity = args.creativity, - norarity = args.norarity) + gatherer = args.gatherer, for_forum = args.forum, for_mse = args.mse, + creativity = args.creativity, norarity = args.norarity) exit(0) diff --git a/lib/cardlib.py b/lib/cardlib.py index 718155a..4b88cb7 100644 --- a/lib/cardlib.py +++ b/lib/cardlib.py @@ -17,6 +17,11 @@ try: import textwrap import nltk.data sent_tokenizer = nltk.data.load('tokenizers/punkt/english.pickle') + # This could me made smarter - MSE will capitalize for us after :, + # but we still need to capitalize the first english component of an activation + # cost that starts with symbols, such as {2U}, *R*emove a +1/+1 counter from @: etc. + def cap(s): + return s[:1].capitalize() + s[1:] # This crazy thing is actually invoked as an unpass, so newlines are still # encoded. def sentencecase(s): @@ -26,11 +31,26 @@ try: for line in lines: if line: sentences = sent_tokenizer.tokenize(line) - clines += [' '.join([sent.capitalize() for sent in sentences])] + clines += [' '.join([cap(sent) for sent in sentences])] return utils.newline.join(clines).replace(utils.reserved_marker, utils.x_marker) except ImportError: + # non-nltk implementation provided by PAK90 + def uppercaseNewLineAndFullstop(string): + # ok, let's capitalize every letter after a full stop and newline. + # first let's find all indices of '.' and '\n' + indices = [0] # initialise with 0, since we always want to capitalise the first letter. + newlineIndices = [0] # also need to keep track of pure newlines (for planeswalkers). + for i in range (len(string)): + if string[i] == '\\': + indices.append(i + 1) # we want the index of the letter after the \n, so add one. + newlineIndices.append(i + 1) + if string[i] == '.' or string[i] == "=": # also handle the choice bullets. + indices.append(i + 2) # we want the index of the letter after the ., so we need to count the space as well. + indexSet = set(indices) # convert it to a set for the next part; the capitalisation. + return "".join(c.upper() if i in indexSet else c for i, c in enumerate(string)) + def sentencecase(s): - return s + return uppercaseNewLineAndFullstop(s) # These are used later to determine what the fields of the Card object are called. # Define them here because they have nothing to do with the actual format. @@ -109,6 +129,7 @@ def fields_check_valid(fields): else: return not field_pt in fields + # These functions take a bunch of source data in some format and turn # it into nicely labeled fields that we know how to initialize a card from. # Both return a dict that maps field names to lists of possible values, @@ -141,7 +162,7 @@ def fields_check_valid(fields): # layout - string # rarity - string # flavor - string -# artis - string +# artist - string # number - string # multiverseid - number # variations - list @@ -530,7 +551,7 @@ class Card: return outstr - def format(self, gatherer = False, for_forum = False): + def format(self, gatherer = False, for_forum = False, for_mse = False): outstr = '' if gatherer: cardname = titlecase(self.__dict__[field_name]) @@ -663,6 +684,111 @@ class Card: return outstr + def to_mse(self): + outstr = '' + + # need a 'card' string first + outstr += 'card:\n' + + cardname = titlecase(self.__dict__[field_name]) + outstr += '\tname: ' + cardname + '\n' + + if self.__dict__[field_rarity]: + if self.__dict__[field_rarity] in utils.json_rarity_unmap: + rarity = utils.json_rarity_unmap[self.__dict__[field_rarity]] + else: + rarity = self.__dict__[field_rarity] + outstr += '\trarity: ' + rarity.lower() + '\n' + #if not self.parsed: + # outstr += ' _UNPARSED_' + #if not self.valid: + # outstr += ' _INVALID_' + + if not self.__dict__[field_cost].none: + outstr += '\tcasting cost: ' + self.__dict__[field_cost].format().replace('{','').replace('}','') + outstr += '\n' + + outstr += '\tsuper type: ' + ' '.join(self.__dict__[field_supertypes] + + self.__dict__[field_types]).title() + '\n' + if self.__dict__[field_subtypes]: + outstr += '\tsub type: ' + ' '.join(self.__dict__[field_subtypes]).title() + '\n' + + if self.__dict__[field_text].text: + mtext = self.__dict__[field_text].text + mtext = transforms.text_unpass_1_choice(mtext, delimit = False) + mtext = transforms.text_unpass_2_counters(mtext) + mtext = transforms.text_unpass_3_unary(mtext) + mtext = transforms.text_unpass_4_symbols(mtext, False) + mtext = sentencecase(mtext) + # I don't really want these MSE specific passes in transforms, + # but they could be pulled out separately somewhere else in here. + mtext = mtext.replace(utils.this_marker, '' + + utils.this_marker + '') + mtext = transforms.text_unpass_5_cardname(mtext, cardname) + mtext = transforms.text_unpass_6_newlines(mtext) + newtext = Manatext('') + newtext.text = mtext + newtext.costs = self.__dict__[field_text].costs + newtext = newtext.format() + + #NOT NEEDED newtext = newtext.replace(utils.this_marker, cardname) # first let's put the cardname where all the @s are. + + + # newtext = newtext.replace(utils.counter_rename + ".", "countered.") # then replace any 'uncast' at the end of a sentence with 'countered'. + # newtext = newtext.replace(utils.dash_marker, u'\u2014') # also replace the ~ with a u2014 for choices. + # newtext = newtext.replace(utils.counter_rename, "counter") # then replace all the mid-sentence 'uncast' with 'counter'. + # newtext = newtext.replace('{','').replace('}','') # now we encase mana/tap symbols with the correct tags for mse. + # linecount = newtext.count('\n') + 1 # adding 1 because no newlines means 1 line, 1 newline means 2 lines etc. + + # newtext = sentencecase(newtext) # make all the things uppercase! + + # # done after uppercasing everything because string[i] == u2022 doesn't work apparently. + # newtext = newtext.replace(utils.bullet_marker, u'\u2022') # replace the = with a u2022. + + # used later + linecount = newtext.count('\n') + 1 # adding 1 because no newlines means 1 line, 1 newline means 2 lines etc. + + # actually really important + newtext = newtext.replace('{','').replace('}','') # now we encase mana/tap symbols with the correct tags for mse. + + newlineIndices = [0] # also need to keep track of pure newlines (for planeswalkers). + for i in range (len(newtext)): + if newtext[i] == '\n': + newlineIndices.append(i + 1) + + # need to do Special Things if it's a planeswalker. + if "planeswalker" in str(self.__dict__[field_types]): # for some reason this is in types, not supertypes... + outstr += '\tstylesheet: m15-planeswalker\n' # set the proper card style for a 3-line walker. + + # set up the loyalty cost fields using regex to find how many there are. + i = 0 + lcost_regex = r'[-+]?\d+: ' # 1+ figures, might be 0. + for costs in re.findall(lcost_regex, newtext): + i += 1 + outstr += '\tloyalty cost ' + str(i) + ': ' + costs + '\n' + # sub out the loyalty costs. + newtext = re.sub(lcost_regex, '', newtext) + + #newtext = sentencecase(newtext) # we need to uppercase again; previous uppercase call didn't work due to loyalty costs being there. + + if self.__dict__[field_loyalty]: + outstr += '\tloyalty: ' + utils.from_unary(self.__dict__[field_loyalty]) + '\n' + + newtext = newtext.replace('\n','\n\t\t') + outstr += '\trule text:\n\t\t' + newtext + '\n' + + if self.__dict__[field_pt]: + ptstring = utils.from_unary(self.__dict__[field_pt]).split('/') + if (len(ptstring) > 1): #really don't want to be accessing anything nonexistent. + outstr += '\tpower: ' + ptstring[0] + '\n' + outstr += '\ttoughness: ' + ptstring[1] + '\n' + #outstr += '\n' + + # now append all the other useless fields that the setfile expects. + outstr += '\thas styling: false\n\tnotes:\n\ttime created:2015-07-20 22:53:07\n\ttime modified:2015-07-20 22:53:08\n\textra data:\n\timage:\n\tcard code text:\n\tcopyright:\n\timage 2:\n\tcopyright 2: ' + + return outstr + def vectorize(self): ld = '(' rd = ')' diff --git a/lib/utils.py b/lib/utils.py index bf51e6a..a2fc877 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -6,6 +6,9 @@ import re import config +# special chunk of text that Magic Set Editor 2 requires at the start of all set files. +mse_prepend = 'mse version: 0.3.8\ngame: magic\nstylesheet: m15\nset info:\n\tsymbol:\nstyling:\n\tmagic-m15:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay:\n\tmagic-m15-clear:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-extra-improved:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\tpt box symbols: magic-pt-symbols-extra.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-planeswalker:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-planeswalker-promo-black:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-promo-dka:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-token-clear:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker-4abil:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker-clear:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker-promo-black:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n' + # separators cardsep = config.cardsep fieldsep = config.fieldsep