def conquest_evolution_description(evolution, _=_): """Crafts a human-readable description from a `conquest_pokemon_evolution` row object. """ chunks = [] # Trigger if evolution.recruiting_ko_required: chunks.append('Score a KO that makes a warrior offer to join your army') elif evolution.item_id is not None: chunks.append('Win a battle') else: chunks.append('Perform any action') # Conditions if evolution.kingdom_id is not None: chunks.append(h.literal(_(u'in {0}')).format( h.HTML.a(evolution.kingdom.name, href=url(controller='dex_conquest', action='kingdoms', name=evolution.kingdom.name.lower())))) if evolution.item_id is not None: chunks.append(h.literal(_(u'with {article} {item} equipped')).format( article=article(evolution.item.name), item=item_link(evolution.item, include_icon=False))) if evolution.required_stat_id is not None: chunks.append(_(u'with at least {number} {stat} afterwards').format( number=evolution.minimum_stat, stat=evolution.stat.name)) if evolution.minimum_link is not None: chunks.append(_(u'with at least {0}% link afterwards').format( evolution.minimum_link)) if evolution.warrior_gender_id is not None: chunks.append(_(u'{0} warriors only').format(evolution.gender.identifier)) return h.literal(u', ').join(chunks)
def pokemon_icon(pokemon, alt=True): if pokemon.is_default: return h.literal('<span class="sprite-icon sprite-icon-%d"></span>' % pokemon.species.id) alt_text = pokemon.name if alt else u'' if pokemon_has_media(pokemon.default_form, 'icons', 'png'): return pokemon_form_image(pokemon.default_form, prefix='icons', alt=alt_text) return pokedex_img('pokemon/icons/0.png', title=pokemon.species.name, alt=alt_text)
def conquest_evolution_description(evolution, _=_): """Crafts a human-readable description from a `conquest_pokemon_evolution` row object. """ chunks = [] # Trigger if evolution.recruiting_ko_required: chunks.append( 'Score a KO that makes a warrior offer to join your army') elif evolution.item_id is not None: chunks.append('Win a battle') else: chunks.append('Perform any action') # Conditions if evolution.kingdom_id is not None: chunks.append( h.literal(_(u'in {0}')).format( h.HTML.a(evolution.kingdom.name, href=url(controller='dex_conquest', action='kingdoms', name=evolution.kingdom.name.lower())))) if evolution.item_id is not None: chunks.append( h.literal(_(u'with {article} {item} equipped')).format( article=article(evolution.item.name), item=item_link(evolution.item, include_icon=False))) if evolution.required_stat_id is not None: chunks.append( _(u'with at least {number} {stat} afterwards').format( number=evolution.minimum_stat, stat=evolution.stat.name)) if evolution.minimum_link is not None: chunks.append( _(u'with at least {0}% link afterwards').format( evolution.minimum_link)) if evolution.warrior_gender_id is not None: chunks.append( _(u'{0} warriors only').format(evolution.gender.identifier)) return h.literal(u', ').join(chunks)
def apply_move_template(template, move): u"""`template` should be a string.Template object. Uses safe_substitute to inject some fields from the move into the template, just like the above. """ d = dict( id=move.id, name=move.name, type=move.type.name, damage_class=move.damage_class.name, pp=move.pp, power=move.power, accuracy=move.accuracy, priority=move.priority, effect_chance=move.effect_chance, effect=move.move_effect.short_effect, ) return h.literal(template.safe_substitute(d))
def render_flavor_text(flavor_text, literal=False): """Makes flavor text suitable for HTML presentation. If `literal` is false, collapses broken lines into single lines. If `literal` is true, linebreaks are preserved exactly as they are in the games. """ # n.b.: \u00ad is soft hyphen # Somehow, the games occasionally have \n\f, which makes no sense at all # and wouldn't render in-game anyway. Fix this flavor_text = flavor_text.replace('\n\f', '\f') if literal: # Page breaks become two linebreaks. # Soft hyphens become real hyphens. # Newlines become linebreaks. html = flavor_text.replace(u'\f', u'<br><br>') \ .replace(u'\u00ad', u'-') \ .replace(u'\n', u'<br>') else: # Page breaks are treated just like newlines. # Soft hyphens followed by newlines vanish. # Letter-hyphen-newline becomes letter-hyphen, to preserve real # hyphenation. # Any other newline becomes a space. html = flavor_text.replace(u'\f', u'\n') \ .replace(u'\u00ad\n', u'') \ .replace(u'\u00ad', u'') \ .replace(u' -\n', u' - ') \ .replace(u'-\n', u'-') \ .replace(u'\n', u' ') # Collapse adjacent spaces and strip trailing whitespace. html = u' '.join(html.split()) return h.literal(html)
def render_flavor_text(flavor_text, literal=False): """Makes flavor text suitable for HTML presentation. If `literal` is false, collapses broken lines into single lines. If `literal` is true, linebreaks are preserved exactly as they are in the games. """ # n.b.: \u00ad is soft hyphen # Somehow, the games occasionally have \n\f, which makes no sense at all # and wouldn't render in-game anyway. Fix this flavor_text = flavor_text.replace("\n\f", "\f") if literal: # Page breaks become two linebreaks. # Soft hyphens become real hyphens. # Newlines become linebreaks. html = flavor_text.replace(u"\f", u"<br><br>").replace(u"\u00ad", u"-").replace(u"\n", u"<br>") else: # Page breaks are treated just like newlines. # Soft hyphens followed by newlines vanish. # Letter-hyphen-newline becomes letter-hyphen, to preserve real # hyphenation. # Any other newline becomes a space. html = ( flavor_text.replace(u"\f", u"\n") .replace(u"\u00ad\n", u"") .replace(u"\u00ad", u"") .replace(u" -\n", u" - ") .replace(u"-\n", u"-") .replace(u"\n", u" ") ) return h.literal(html)
def evolution_description(evolution, _=_): """Crafts a human-readable description from a `pokemon_evolution` row object. """ chunks = [] # Trigger if evolution.trigger.identifier == u'level-up': chunks.append(_(u'Level up')) elif evolution.trigger.identifier == u'trade': chunks.append(_(u'Trade')) elif evolution.trigger.identifier == u'use-item': chunks.append(h.literal(_(u"Use {article} {item}")).format( article=article(evolution.trigger_item.name, _=_), item=item_link(evolution.trigger_item, include_icon=False))) elif evolution.trigger.identifier == u'shed': chunks.append( _(u"Evolve {from_pokemon} ({to_pokemon} will consume " u"a Poké Ball and appear in a free party slot)").format( from_pokemon=evolution.evolved_species.parent_species.name, to_pokemon=evolution.evolved_species.name)) else: chunks.append(_(u'Do something')) # Conditions if evolution.gender: chunks.append(_(u"{0}s only").format(evolution.gender)) if evolution.time_of_day: chunks.append(_(u"during the {0}").format(evolution.time_of_day)) if evolution.minimum_level: chunks.append(_(u"starting at level {0}").format(evolution.minimum_level)) if evolution.location_id: chunks.append(h.literal(_(u"around {0}")).format( h.HTML.a(evolution.location.name, href=url(controller='dex', action='locations', name=evolution.location.name.lower())))) if evolution.held_item_id: chunks.append(h.literal(_(u"while holding {article} {item}")).format( article=article(evolution.held_item.name), item=item_link(evolution.held_item, include_icon=False))) if evolution.known_move_id: chunks.append(h.literal(_(u"knowing {0}")).format( h.HTML.a(evolution.known_move.name, href=url(controller='dex', action='moves', name=evolution.known_move.name.lower())))) if evolution.minimum_happiness: chunks.append(_(u"with at least {0} happiness").format( evolution.minimum_happiness)) if evolution.minimum_beauty: chunks.append(_(u"with at least {0} beauty").format( evolution.minimum_beauty)) if evolution.relative_physical_stats is not None: if evolution.relative_physical_stats < 0: op = _(u'<') elif evolution.relative_physical_stats > 0: op = _(u'>') else: op = _(u'=') chunks.append(_(u"when Attack {0} Defense").format(op)) if evolution.party_species_id: chunks.append(h.literal(_(u"with {0} in the party")).format( pokemon_link(evolution.party_species.default_pokemon, include_icon=False))) if evolution.trade_species_id: chunks.append(h.literal(_(u"in exchange for {0}")).format( pokemon_link(evolution.trade_species.default_pokemon, include_icon=False))) return h.literal(u', ').join(chunks)
def _linkify_bug_number(self, match): """Regex replace function for changing bug numbers into links.""" n = match.group(1) bug_url = self.bug_tracker.format(match.group(1)) return helpers.literal( u"""<a href="{0}">{1}</a>""".format(bug_url, match.group(0)))
def _poll(self, limit, max_age): feed = feedparser.parse(self.feed_url) if feed.bozo and isinstance(feed.bozo_exception, URLError): # Feed is DOWN. Bail here; otherwise, old entries might be lost # just because, say, Bulbanews is down yet again return None if not self.title: self.title = feed.feed.title updates = [] for entry in feed.entries[:limit]: # Grab a date -- Atom has published, RSS usually just has updated. # Both come out as time tuples, which datetime.datetime() can read try: timestamp_tuple = entry.published_parsed except AttributeError: timestamp_tuple = entry.updated_parsed timestamp = datetime.datetime(*timestamp_tuple[:6]) if max_age and timestamp < max_age: # Entries should be oldest-first, so we can bail after the first # expired entry break # Try to find something to show! Default to the summary, if there is # one, or try to generate one otherwise content = u'' if 'summary' in entry: # If there be a summary, cheerfully trust that it's actually a # summary content = entry.summary elif 'content' in entry and \ len(entry.content[0].value) <= self.SUMMARY_LENGTH: # Full content is short; use as-is! content = entry.content[0].value elif 'content' in entry: # Full content is way too much, especially for my giant blog posts. # Cut this down to some arbitrary number of characters, then feed # it to lxml.html to fix tag nesting broken_html = entry.content[0].value[:self.SUMMARY_LENGTH] fragment = lxml.html.fromstring(broken_html) # Insert an ellipsis at the end of the last node with text last_text_node = None last_tail_node = None # Need to find the last node with a tail, OR the last node with # text if it's later for node in fragment.iter(): if node.tail: last_tail_node = node last_text_node = None elif node.text: last_text_node = node last_tail_node = None if last_text_node is not None: last_text_node.text += '...' if last_tail_node is not None: last_tail_node.tail += '...' # Serialize content = lxml.html.tostring(fragment) content = helpers.literal(content) update = FrontPageRSS( source=self, time=timestamp, content=content, entry=entry, ) updates.append(update) return updates
def _poll(self, limit, max_age): # Fetch the main repo's git tags git_dir = '--git-dir=' + self.repo_paths[0] args = [ 'git', git_dir, 'tag', '-l', ] if self.tag_pattern: args.append(self.tag_pattern) git_output, _ = subprocess.Popen(args, stdout=PIPE).communicate() tags = git_output.strip().split('\n') # Tags come out in alphabetical order, which means earliest first. Reverse # it to make the slicing easier tags.reverse() # Only history from tag to tag is actually interesting, so get the most # recent $limit tags but skip the earliest interesting_tags = tags[:-1][:limit] updates = [] for tag, since_tag in zip(interesting_tags, tags[1:]): # Get the date when this tag was actually created. # 'raw' format gives unixtime followed by timezone offset args = [ 'git', git_dir, 'for-each-ref', '--format=%(taggerdate:raw)', 'refs/tags/' + tag, ] tag_timestamp, _ = subprocess.Popen(args, stdout=PIPE).communicate() tag_unixtime, tag_timezone = tag_timestamp.split(None, 1) tagged_timestamp = datetime.datetime.fromtimestamp(int(tag_unixtime)) if max_age and tagged_timestamp < max_age: break commits = [] for repo_path, repo_name in zip(self.repo_paths, self.repo_names): # Grab an easily-parsed history: fields delimited by nulls. # Hash, author's name, commit timestamp, subject. git_log_args = [ 'git', '--git-dir=' + repo_path, 'log', '--pretty=%h%x00%an%x00%aE%x00%at%x00%s', "{0}..{1}".format(since_tag, tag), ] proc = subprocess.Popen(git_log_args, stdout=PIPE) for line in proc.stdout: hash, author, email, time, subject \ = line.strip().decode('utf8').split('\x00') # Convert bug numbers in subject to URLs if self.bug_tracker: subject = helpers.literal( re.sub(u'#(\d+)', self._linkify_bug_number, subject) ) commits.append( FrontPageGitCommit( hash = hash, author = author, email = email, time = datetime.datetime.fromtimestamp(int(time)), subject = subject, repo = repo_name, ) ) update = FrontPageGit( source = self, time = tagged_timestamp, log = commits, tag = tag, ) updates.append(update) return updates
def apply_pokemon_template(template, pokemon, _=_): u"""`template` should be a string.Template object. Uses safe_substitute to inject some fields from the Pokémon into the template. This cheerfully returns a literal, so be sure to escape the original format string BEFORE passing it to Template! """ d = dict( icon=pokemon_form_image(pokemon.default_form, prefix=u'icons'), id=pokemon.species.id, name=pokemon.default_form.name, height=format_height_imperial(pokemon.height), height_ft=format_height_imperial(pokemon.height), height_m=format_height_metric(pokemon.height), weight=format_weight_imperial(pokemon.weight), weight_lb=format_weight_imperial(pokemon.weight), weight_kg=format_weight_metric(pokemon.weight), gender=_(gender_rate_label[pokemon.species.gender_rate]), genus=pokemon.species.genus, base_experience=pokemon.base_experience, capture_rate=pokemon.species.capture_rate, base_happiness=pokemon.species.base_happiness, ) # "Lazy" loading, to avoid hitting other tables if unnecessary. This is # very chumpy and doesn't distinguish between literal text and fields (e.g. # '$type' vs 'type'), but that's very unlikely to happen, and it's not a # big deal if it does if 'type' in template.template: types = pokemon.types d['type'] = u'/'.join(type_.name for type_ in types) d['type1'] = types[0].name d['type2'] = types[1].name if len(types) > 1 else u'' if 'egg_group' in template.template: egg_groups = pokemon.species.egg_groups d['egg_group'] = u'/'.join(group.name for group in egg_groups) d['egg_group1'] = egg_groups[0].name d['egg_group2'] = egg_groups[1].name if len(egg_groups) > 1 else u'' if 'ability' in template.template: abilities = pokemon.abilities d['ability'] = u'/'.join(ability.name for ability in abilities) d['ability1'] = abilities[0].name d['ability2'] = abilities[1].name if len(abilities) > 1 else u'' if pokemon.hidden_ability: d['hidden_ability'] = pokemon.hidden_ability.name else: d['hidden_ability'] = u'' if 'color' in template.template: d['color'] = pokemon.species.color.name if 'habitat' in template.template: d['habitat'] = pokemon.species.habitat.name if 'shape' in template.template: if pokemon.species.shape: d['shape'] = pokemon.species.shape.name else: d['shape'] = '' if 'hatch_counter' in template.template: d['hatch_counter'] = pokemon.species.hatch_counter if 'steps_to_hatch' in template.template: d['steps_to_hatch'] = (pokemon.species.hatch_counter + 1) * 255 if 'stat' in template.template or \ 'hp' in template.template or \ 'attack' in template.template or \ 'defense' in template.template or \ 'speed' in template.template or \ 'effort' in template.template: d['effort'] = u', '.join("{0} {1}".format(_.effort, _.stat.name) for _ in pokemon.stats if _.effort) d['stats'] = u'/'.join(str(_.base_stat) for _ in pokemon.stats) for pokemon_stat in pokemon.stats: key = pokemon_stat.stat.name.lower().replace(' ', '_') d[key] = pokemon_stat.base_stat return h.literal(template.safe_substitute(d))
def _poll(self, limit, max_age): # Fetch the main repo's git tags git_dir = '--git-dir=' + self.repo_paths[0] args = [ 'git', git_dir, 'tag', '-l', ] if self.tag_pattern: args.append(self.tag_pattern) git_output, _ = subprocess.Popen(args, stdout=PIPE).communicate() tags = git_output.strip().split('\n') # Tags come out in alphabetical order, which means earliest first. Reverse # it to make the slicing easier tags.reverse() # Only history from tag to tag is actually interesting, so get the most # recent $limit tags but skip the earliest interesting_tags = tags[:-1][:limit] updates = [] for tag, since_tag in zip(interesting_tags, tags[1:]): # Get the date when this tag was actually created. # 'raw' format gives unixtime followed by timezone offset args = [ 'git', git_dir, 'for-each-ref', '--format=%(taggerdate:raw)', 'refs/tags/' + tag, ] tag_timestamp, _ = subprocess.Popen(args, stdout=PIPE).communicate() tag_unixtime, tag_timezone = tag_timestamp.split(None, 1) tagged_timestamp = datetime.datetime.fromtimestamp( int(tag_unixtime)) if max_age and tagged_timestamp < max_age: break commits = [] for repo_path, repo_name in zip(self.repo_paths, self.repo_names): # Grab an easily-parsed history: fields delimited by nulls. # Hash, author's name, commit timestamp, subject. git_log_args = [ 'git', '--git-dir=' + repo_path, 'log', '--pretty=%h%x00%an%x00%aE%x00%at%x00%s', "{0}..{1}".format(since_tag, tag), ] proc = subprocess.Popen(git_log_args, stdout=PIPE) for line in proc.stdout: hash, author, email, time, subject \ = line.strip().decode('utf8').split('\x00') # Convert bug numbers in subject to URLs if self.bug_tracker: subject = helpers.literal( re.sub(u'#(\d+)', self._linkify_bug_number, subject)) commits.append( FrontPageGitCommit( hash=hash, author=author, email=email, time=datetime.datetime.fromtimestamp(int(time)), subject=subject, repo=repo_name, )) update = FrontPageGit( source=self, time=tagged_timestamp, log=commits, tag=tag, ) updates.append(update) return updates
def apply_pokemon_template(template, pokemon, dex_translate=_, _=_): u"""`template` should be a string.Template object. Uses safe_substitute to inject some fields from the Pokémon into the template. This cheerfully returns a literal, so be sure to escape the original format string BEFORE passing it to Template! """ d = dict( icon=pokemon_sprite(pokemon, prefix=u'icons'), id=pokemon.national_id, name=pokemon.full_name, height=format_height_imperial(pokemon.height), height_ft=format_height_imperial(pokemon.height), height_m=format_height_metric(pokemon.height), weight=format_weight_imperial(pokemon.weight), weight_lb=format_weight_imperial(pokemon.weight), weight_kg=format_weight_metric(pokemon.weight), gender=_(gender_rate_label[pokemon.gender_rate]), species=dex_translate(pokemon.species), base_experience=pokemon.base_experience, capture_rate=pokemon.capture_rate, base_happiness=pokemon.base_happiness, ) # "Lazy" loading, to avoid hitting other tables if unnecessary. This is # very chumpy and doesn't distinguish between literal text and fields (e.g. # '$type' vs 'type'), but that's very unlikely to happen, and it's not a # big deal if it does if 'type' in template.template: types = pokemon.types d['type'] = u'/'.join(dex_translate(type_.name) for type_ in types) d['type1'] = dex_translate(types[0].name) d['type2'] = dex_translate(types[1].name) if len(types) > 1 else u'' if 'egg_group' in template.template: egg_groups = pokemon.egg_groups d['egg_group'] = u'/'.join(dex_translate(group.name) for group in egg_groups) d['egg_group1'] = dex_translate(egg_groups[0].name) d['egg_group2'] = dex_translate(egg_groups[1].name) if len(egg_groups) > 1 else u'' if 'ability' in template.template: abilities = pokemon.abilities d['ability'] = u'/'.join(dex_translate(ability.name) for ability in abilities) d['ability1'] = dex_translate(abilities[0].name) d['ability2'] = dex_translate(abilities[1].name) if len(abilities) > 1 else u'' if 'color' in template.template: d['color'] = dex_translate(pokemon.color) if 'habitat' in template.template: d['habitat'] = dex_translate(pokemon.habitat) if 'shape' in template.template: if pokemon.shape: d['shape'] = dex_translate(pokemon.shape.name) else: d['shape'] = '' if 'hatch_counter' in template.template: d['hatch_counter'] = pokemon.hatch_counter if 'steps_to_hatch' in template.template: d['steps_to_hatch'] = (pokemon.hatch_counter + 1) * 255 if 'stat' in template.template or \ 'hp' in template.template or \ 'attack' in template.template or \ 'defense' in template.template or \ 'speed' in template.template or \ 'effort' in template.template: d['effort'] = u', '.join("{0} {1}".format(_.effort, _.stat.name) for _ in pokemon.stats if _.effort) d['stats'] = u'/'.join(str(_.base_stat) for _ in pokemon.stats) for pokemon_stat in pokemon.stats: key = pokemon_stat.stat.name.lower().replace(' ', '_') d[key] = pokemon_stat.base_stat return h.literal(template.safe_substitute(d))
def _linkify_bug_number(self, match): """Regex replace function for changing bug numbers into links.""" n = match.group(1) bug_url = self.bug_tracker.format(match.group(1)) return helpers.literal(u"""<a href="{0}">{1}</a>""".format( bug_url, match.group(0)))
def evolution_description(evolution, _=_): """Crafts a human-readable description from a `pokemon_evolution` row object. """ chunks = [] # Trigger if evolution.trigger.identifier == u'level-up': chunks.append(_(u'Level up')) elif evolution.trigger.identifier == u'trade': chunks.append(_(u'Trade')) elif evolution.trigger.identifier == u'use-item': chunks.append( h.literal(_(u"Use {article} {item}")).format( article=article(evolution.trigger_item.name, _=_), item=item_link(evolution.trigger_item, include_icon=False))) elif evolution.trigger.identifier == u'shed': chunks.append( _(u"Evolve {from_pokemon} ({to_pokemon} will consume " u"a Poké Ball and appear in a free party slot)").format( from_pokemon=evolution.evolved_species.parent_species.name, to_pokemon=evolution.evolved_species.name)) else: chunks.append(_(u'Do something')) # Conditions if evolution.gender_id: chunks.append(_(u"{0}s only").format(evolution.gender.identifier)) if evolution.time_of_day: chunks.append(_(u"during the {0}").format(evolution.time_of_day)) if evolution.minimum_level: chunks.append( _(u"starting at level {0}").format(evolution.minimum_level)) if evolution.location_id: chunks.append( h.literal(_(u"around {0} ({1})")).format( h.HTML.a(evolution.location.name, href=url(controller='dex', action='locations', name=evolution.location.name.lower())), evolution.location.region.name)) if evolution.held_item_id: chunks.append( h.literal(_(u"while holding {article} {item}")).format( article=article(evolution.held_item.name), item=item_link(evolution.held_item, include_icon=False))) if evolution.known_move_id: chunks.append( h.literal(_(u"knowing {0}")).format( h.HTML.a(evolution.known_move.name, href=url(controller='dex', action='moves', name=evolution.known_move.name.lower())))) if evolution.known_move_type_id: chunks.append( h.literal(_(u'knowing a {0}-type move')).format( h.HTML.a(evolution.known_move_type.name, href=url( controller='dex', action='types', name=evolution.known_move_type.name.lower())))) if evolution.minimum_happiness: chunks.append( _(u"with at least {0} happiness").format( evolution.minimum_happiness)) if evolution.minimum_beauty: chunks.append( _(u"with at least {0} beauty").format(evolution.minimum_beauty)) if evolution.minimum_affection: chunks.append( _(u'with at least {0} affection in Pokémon-Amie').format( evolution.minimum_affection)) if evolution.relative_physical_stats is not None: if evolution.relative_physical_stats < 0: op = _(u'<') elif evolution.relative_physical_stats > 0: op = _(u'>') else: op = _(u'=') chunks.append(_(u"when Attack {0} Defense").format(op)) if evolution.party_species_id: chunks.append( h.literal(_(u"with {0} in the party")).format( pokemon_link(evolution.party_species.default_pokemon, include_icon=False))) if evolution.party_type_id: chunks.append( h.literal(_(u"with a {0}-type Pokémon in the party")).format( h.HTML.a(evolution.party_type.name, href=url(controller='dex', action='types', name=evolution.party_type.name.lower())))) if evolution.trade_species_id: chunks.append( h.literal(_(u"in exchange for {0}")).format( pokemon_link(evolution.trade_species.default_pokemon, include_icon=False))) if evolution.needs_overworld_rain: chunks.append(_(u'while it is raining outside of battle')) if evolution.turn_upside_down: chunks.append(_(u'with the 3DS turned upside-down')) return h.literal(u', ').join(chunks)
def evolution_description(evolution, _=_): """Crafts a human-readable description from a `pokemon_evolution` row object. """ chunks = [] # Trigger if evolution.trigger.identifier == u"level-up": chunks.append(_(u"Level up")) elif evolution.trigger.identifier == u"trade": chunks.append(_(u"Trade")) elif evolution.trigger.identifier == u"use-item": chunks.append( h.literal(_(u"Use {article} {item}")).format( article=article(evolution.trigger_item.name, _=_), item=item_link(evolution.trigger_item, include_icon=False), ) ) elif evolution.trigger.identifier == u"shed": chunks.append( _( u"Evolve {from_pokemon} ({to_pokemon} will consume " u"a Poké Ball and appear in a free party slot)" ).format( from_pokemon=evolution.evolved_species.parent_species.name, to_pokemon=evolution.evolved_species.name ) ) else: chunks.append(_(u"Do something")) # Conditions if evolution.gender_id: chunks.append(_(u"{0}s only").format(evolution.gender.identifier)) if evolution.time_of_day: chunks.append(_(u"during the {0}").format(evolution.time_of_day)) if evolution.minimum_level: chunks.append(_(u"starting at level {0}").format(evolution.minimum_level)) if evolution.location_id: chunks.append( h.literal(_(u"around {0} ({1})")).format( h.HTML.a( evolution.location.name, href=url(controller="dex", action="locations", name=evolution.location.name.lower()), ), evolution.location.region.name, ) ) if evolution.held_item_id: chunks.append( h.literal(_(u"while holding {article} {item}")).format( article=article(evolution.held_item.name), item=item_link(evolution.held_item, include_icon=False) ) ) if evolution.known_move_id: chunks.append( h.literal(_(u"knowing {0}")).format( h.HTML.a( evolution.known_move.name, href=url(controller="dex", action="moves", name=evolution.known_move.name.lower()), ) ) ) if evolution.known_move_type_id: chunks.append( h.literal(_(u"knowing a {0}-type move")).format( h.HTML.a( evolution.known_move_type.name, href=url(controller="dex", action="types", name=evolution.known_move_type.name.lower()), ) ) ) if evolution.minimum_happiness: chunks.append(_(u"with at least {0} happiness").format(evolution.minimum_happiness)) if evolution.minimum_beauty: chunks.append(_(u"with at least {0} beauty").format(evolution.minimum_beauty)) if evolution.minimum_affection: chunks.append(_(u"with at least {0} affection in Pokémon-Amie").format(evolution.minimum_affection)) if evolution.relative_physical_stats is not None: if evolution.relative_physical_stats < 0: op = _(u"<") elif evolution.relative_physical_stats > 0: op = _(u">") else: op = _(u"=") chunks.append(_(u"when Attack {0} Defense").format(op)) if evolution.party_species_id: chunks.append( h.literal(_(u"with {0} in the party")).format( pokemon_link(evolution.party_species.default_pokemon, include_icon=False) ) ) if evolution.party_type_id: chunks.append( h.literal(_(u"with a {0}-type Pokémon in the party")).format( h.HTML.a( evolution.party_type.name, href=url(controller="dex", action="types", name=evolution.party_type.name.lower()), ) ) ) if evolution.trade_species_id: chunks.append( h.literal(_(u"in exchange for {0}")).format( pokemon_link(evolution.trade_species.default_pokemon, include_icon=False) ) ) if evolution.needs_overworld_rain: chunks.append(_(u"while it is raining outside of battle")) if evolution.turn_upside_down: chunks.append(_(u"with the 3DS turned upside-down")) return h.literal(u", ").join(chunks)
def conquest_transformation_description(tform, _=_): """Crafts a human-readable description from a `conquest_warrior_transformation' row object. """ # Dumb tricks for saving characters; this has a lot of big awkward lines def link(thing): return h.HTML.a(thing.name, href=make_thingy_url(thing, controller='dex_conquest')) lit = h.literal chunks = [] # Triggers if tform.is_automatic: chunks.append(_(u'Automatically happens in-story')) elif tform.required_link is not None: if tform.pokemon[0].identifier == 'eevee': pokemon = lit(_(u'{0} or any of its evolutions')).format( link(tform.pokemon[0])) else: pokemon = lit(_(u' or ')).join(link(p) for p in tform.pokemon) chunks.append( lit(_(u'Reach at least {link}% link with {pokemon}')).format( link=tform.required_link, pokemon=pokemon)) # Conditions if tform.completed_episode_id is not None: warrior = tform.completed_episode.warriors[0] if warrior != tform.warrior_rank.warrior: warrior = link(warrior) else: warrior = warrior.name chunks.append( lit(_(u'after completing {warrior}\'s episode, "{name}"')).format( warrior=warrior, name=tform.completed_episode.name)) elif tform.current_episode_id is not None: if tform.current_episode.warriors[0].identifier == 'player-m': warrior = _(u'the player') else: warrior = tform.current_episode.warriors[0].name chunks.append( _(u'during {warrior}\'s episode, "{name}"').format( warrior=warrior, name=tform.current_episode.name)) elif tform.distant_warrior_id is not None: chunks.append( lit( _(u'with {0} in the army but not in the same kingdom ' u'or any adjacent kingdom')).format( link(tform.distant_warrior))) elif tform.present_warriors: chunks.append( h.literal(_(u'with {0} in the same kingdom')).format( lit(_(u' and ')).join( link(warrior) for warrior in tform.present_warriors))) elif tform.female_warlord_count is not None: chunks.append( _(u'with at least {0} female warlords in the same kingdom').format( tform.female_warlord_count)) elif tform.pokemon_count is not None: chunks.append( _(u'with at least {0} Pokémon in the gallery').format( tform.pokemon_count)) elif tform.collection_type_id is not None: chunks.append( _(u'with all {0}-type Pokémon in the gallery').format( tform.type.name)) elif tform.warrior_count is not None: chunks.append( _(u'with at least {0} warriors in the gallery').format( tform.warrior_count)) return lit(u', ').join(chunks)
def conquest_transformation_description(tform, _=_): """Crafts a human-readable description from a `conquest_warrior_transformation' row object. """ # Dumb tricks for saving characters; this has a lot of big awkward lines def link(thing): return h.HTML.a(thing.name, href=make_thingy_url(thing, controller="dex_conquest")) lit = h.literal chunks = [] # Triggers if tform.is_automatic: chunks.append(_(u"Automatically happens in-story")) elif tform.required_link is not None: if tform.pokemon[0].identifier == "eevee": pokemon = lit(_(u"{0} or any of its evolutions")).format(link(tform.pokemon[0])) else: pokemon = lit(_(u" or ")).join(link(p) for p in tform.pokemon) chunks.append( lit(_(u"Reach at least {link}% link with {pokemon}")).format(link=tform.required_link, pokemon=pokemon) ) # Conditions if tform.completed_episode_id is not None: warrior = tform.completed_episode.warriors[0] if warrior != tform.warrior_rank.warrior: warrior = link(warrior) else: warrior = warrior.name chunks.append( lit(_(u'after completing {warrior}\'s episode, "{name}"')).format( warrior=warrior, name=tform.completed_episode.name ) ) elif tform.current_episode_id is not None: if tform.current_episode.warriors[0].identifier == "player-m": warrior = _(u"the player") else: warrior = tform.current_episode.warriors[0].name chunks.append( _(u'during {warrior}\'s episode, "{name}"').format(warrior=warrior, name=tform.current_episode.name) ) elif tform.distant_warrior_id is not None: chunks.append( lit(_(u"with {0} in the army but not in the same kingdom " u"or any adjacent kingdom")).format( link(tform.distant_warrior) ) ) elif tform.present_warriors: chunks.append( h.literal(_(u"with {0} in the same kingdom")).format( lit(_(u" and ")).join(link(warrior) for warrior in tform.present_warriors) ) ) elif tform.female_warlord_count is not None: chunks.append(_(u"with at least {0} female warlords in the same kingdom").format(tform.female_warlord_count)) elif tform.pokemon_count is not None: chunks.append(_(u"with at least {0} Pokémon in the gallery").format(tform.pokemon_count)) elif tform.collection_type_id is not None: chunks.append(_(u"with all {0}-type Pokémon in the gallery").format(tform.type.name)) elif tform.warrior_count is not None: chunks.append(_(u"with at least {0} warriors in the gallery").format(tform.warrior_count)) return lit(u", ").join(chunks)
def apply_pokemon_template(template, pokemon, _=_): u"""`template` should be a string.Template object. Uses safe_substitute to inject some fields from the Pokémon into the template. This cheerfully returns a literal, so be sure to escape the original format string BEFORE passing it to Template! """ d = dict( icon=pokemon_form_image(pokemon.default_form, prefix=u"icons"), id=pokemon.species.id, name=pokemon.default_form.name, height=format_height_imperial(pokemon.height), height_ft=format_height_imperial(pokemon.height), height_m=format_height_metric(pokemon.height), weight=format_weight_imperial(pokemon.weight), weight_lb=format_weight_imperial(pokemon.weight), weight_kg=format_weight_metric(pokemon.weight), gender=_(gender_rate_label[pokemon.species.gender_rate]), genus=pokemon.species.genus, base_experience=pokemon.base_experience, capture_rate=pokemon.species.capture_rate, base_happiness=pokemon.species.base_happiness, ) # "Lazy" loading, to avoid hitting other tables if unnecessary. This is # very chumpy and doesn't distinguish between literal text and fields (e.g. # '$type' vs 'type'), but that's very unlikely to happen, and it's not a # big deal if it does if "type" in template.template: types = pokemon.types d["type"] = u"/".join(type_.name for type_ in types) d["type1"] = types[0].name d["type2"] = types[1].name if len(types) > 1 else u"" if "egg_group" in template.template: egg_groups = pokemon.species.egg_groups d["egg_group"] = u"/".join(group.name for group in egg_groups) d["egg_group1"] = egg_groups[0].name d["egg_group2"] = egg_groups[1].name if len(egg_groups) > 1 else u"" if "ability" in template.template: abilities = pokemon.abilities d["ability"] = u"/".join(ability.name for ability in abilities) d["ability1"] = abilities[0].name d["ability2"] = abilities[1].name if len(abilities) > 1 else u"" if pokemon.hidden_ability: d["hidden_ability"] = pokemon.hidden_ability.name else: d["hidden_ability"] = u"" if "color" in template.template: d["color"] = pokemon.species.color.name if "habitat" in template.template: d["habitat"] = pokemon.species.habitat.name if "shape" in template.template: if pokemon.species.shape: d["shape"] = pokemon.species.shape.name else: d["shape"] = "" if "hatch_counter" in template.template: d["hatch_counter"] = pokemon.species.hatch_counter if "steps_to_hatch" in template.template: d["steps_to_hatch"] = (pokemon.species.hatch_counter + 1) * 255 if ( "stat" in template.template or "hp" in template.template or "attack" in template.template or "defense" in template.template or "speed" in template.template or "effort" in template.template ): d["effort"] = u", ".join("{0} {1}".format(_.effort, _.stat.name) for _ in pokemon.stats if _.effort) d["stats"] = u"/".join(str(_.base_stat) for _ in pokemon.stats) for pokemon_stat in pokemon.stats: key = pokemon_stat.stat.name.lower().replace(" ", "_") d[key] = pokemon_stat.base_stat return h.literal(template.safe_substitute(d))
def _poll(self, limit, max_age): feed = feedparser.parse(self.feed_url) if feed.bozo and isinstance(feed.bozo_exception, URLError): # Feed is DOWN. Bail here; otherwise, old entries might be lost # just because, say, Bulbanews is down yet again return None if not self.title: self.title = feed.feed.title updates = [] for entry in feed.entries[:limit]: # Grab a date -- Atom has published, RSS usually just has updated. # Both come out as time tuples, which datetime.datetime() can read try: timestamp_tuple = entry.published_parsed except AttributeError: timestamp_tuple = entry.updated_parsed timestamp = datetime.datetime(*timestamp_tuple[:6]) if max_age and timestamp < max_age: # Entries should be oldest-first, so we can bail after the first # expired entry break # Try to find something to show! Default to the summary, if there is # one, or try to generate one otherwise content = u'' if 'summary' in entry: # If there be a summary, cheerfully trust that it's actually a # summary content = entry.summary elif 'content' in entry and \ len(entry.content[0].value) <= self.SUMMARY_LENGTH: # Full content is short; use as-is! content = entry.content[0].value elif 'content' in entry: # Full content is way too much, especially for my giant blog posts. # Cut this down to some arbitrary number of characters, then feed # it to lxml.html to fix tag nesting broken_html = entry.content[0].value[:self.SUMMARY_LENGTH] fragment = lxml.html.fromstring(broken_html) # Insert an ellipsis at the end of the last node with text last_text_node = None last_tail_node = None # Need to find the last node with a tail, OR the last node with # text if it's later for node in fragment.iter(): if node.tail: last_tail_node = node last_text_node = None elif node.text: last_text_node = node last_tail_node = None if last_text_node is not None: last_text_node.text += '...' if last_tail_node is not None: last_tail_node.tail += '...' # Serialize content = lxml.html.tostring(fragment) content = helpers.literal(content) update = FrontPageRSS( source = self, time = timestamp, content = content, entry = entry, ) updates.append(update) return updates