I'm considering the value and feasibility of making an extension to the help code Evennia has, for viewing the code associated with a command.
This is conceptual. I'm learning Python code because it is valuable both to me as a member to the community and to my day job, seeing if this is worth pursuing.
The Theory
Help files are typically static, but the code for a command may change over time. By pulling some or all of the values in the code into a help file's output, you might make that help file more evergreen and therefore more accurate and useful.
The Problem
Best way to do this? Do we signpost entire code blocks and offer a secondary @help command, or do we selectively insert details into the helpfiles?
Will this alteration around a command's code slow it down in a meaningful way?
Reference Code
I'm going to use the Donate command from the Arx github as my learning example. The raw code below is a reference to the public Git, and only posted here for learning purposes. It helps me to point at the changes that might be made in a specific example, instead of theoretical changes.
Edit: tried to spoiler the code so it would take less space and failed. Anyone know the syntax to put code in a spoiler?
class CmdDonate(ArxCommand):
"""
Donates money to some group
Usage:
+donate <group name>=<amount>
+donate/hype <player>,<group>=<amount>
+donate/score [<group>]
Donates money to some group of npcs in exchange for prestige.
+donate/score lists donation amounts. Costs 1 AP.
"""
key = "+donate"
locks = "cmd:all()"
help_category = "Social"
action_point_cost = 1
@property
def donations(self):
"""Queryset of donations by caller"""
return self.caller.player.Dominion.assets.donations.all().order_by('amount')
def func(self):
"""Execute command."""
caller = self.caller
try:
if "score" in self.switches:
return self.display_score()
if not self.lhs:
self.list_donations(caller)
return
group = self.get_donation_target()
if not group:
return
try:
val = int(self.rhs)
if val > caller.db.currency:
raise CommandError("Not enough money.")
if val <= 0:
raise ValueError
if not caller.player.pay_action_points(self.action_point_cost):
raise CommandError("Not enough AP.")
caller.pay_money(val)
group.donate(val, self.caller)
except (TypeError, ValueError):
raise CommandError("Must give a positive number.")
except CommandError as err:
caller.msg(err)
def list_donations(self, caller):
"""Lists donations to the caller"""
msg = "{wDonations:{n\n"
table = PrettyTable(["{wGroup{n", "{wTotal{n"])
for donation in self.donations:
table.add_row([str(donation.receiver), donation.amount])
msg += str(table)
caller.msg(msg)
def get_donation_target(self):
"""Get donation object"""
org, npc = self.get_org_or_npc_from_args()
if not org and not npc:
return
if "hype" in self.switches:
player = self.caller.player.search(self.lhslist[0])
if not player:
return
donations = player.Dominion.assets.donations
else:
donations = self.caller.player.Dominion.assets.donations
if org:
return donations.get_or_create(organization=org)[0]
return donations.get_or_create(npc_group=npc)[0]
def get_org_or_npc_from_args(self):
"""Get a tuple of org, npc used for getting the donation object"""
org, npc = None, None
if "hype" in self.switches:
if len(self.lhslist) < 2:
raise CommandError("Usage: <player>,<group>=<amount>")
name = self.lhslist[1]
else:
name = self.lhs
try:
org = Organization.objects.get(name__iexact=name)
if org.secret and not self.caller.check_permstring("builders"):
if not org.active_members.filter(player__player=self.caller.player):
org = None
raise Organization.DoesNotExist
except Organization.DoesNotExist:
try:
npc = InfluenceCategory.objects.get(name__iexact=name)
except InfluenceCategory.DoesNotExist:
raise CommandError("Could not find an organization or npc group by the name %s." % name)
return org, npc
def display_score(self):
"""Displays score for donations"""
if self.args:
return self.display_score_for_group()
return self.display_top_donor_for_each_group()
def display_score_for_group(self):
"""Displays a list of the top 10 donors for a given group"""
org, npc = self.get_org_or_npc_from_args()
if org and org.secret:
raise CommandError("Cannot display donations for secret orgs.")
group = org or npc
if not group:
return
msg = "Top donors for %s\n" % group
table = PrettyTable(["Donor", "Amount"])
for donation in group.donations.filter(amount__gt=0).distinct().order_by('-amount'):
table.add_row([str(donation.giver), str(donation.amount)])
msg += str(table)
self.msg(msg)
def display_top_donor_for_each_group(self):
"""Displays the highest donor for each group"""
orgs = Organization.objects.filter(donations__isnull=False)
if not self.caller.check_permstring("builders"):
orgs = orgs.exclude(secret=True)
orgs = list(orgs.distinct())
npcs = list(InfluenceCategory.objects.filter(donations__isnull=False).distinct())
groups = orgs + npcs
table = PrettyTable(["Group", "Top Donor", "Donor's Total Donations"])
top_donations = []
for group in groups:
donation = group.donations.filter(amount__gt=0).order_by('-amount').distinct().first()
if donation:
top_donations.append(donation)
top_donations.sort(key=lambda x: x.amount, reverse=True)
for donation in top_donations:
table.add_row([str(donation.receiver), str(donation.giver), str(donation.amount)])
self.msg(str(table))
Method One - Full Code Block
Acknowledging this might be possible, but not recommending it.
This would just be a full verbatim output of a class file in game, such as the CmdDonate class above.
Bulky and if they want that much detail, why not just go to the github?
Method Two - Selective Inserts
Help files are mostly static and remain functionally similar, but numerical values and examples of rolled stats are pulled from named fields in the associated class.
Advantage: Basic tweaks automatically show up, users won't be confused when a value is altered and the helpfile gets missed. Helpfiles are Evergreen, unless there is a large change.
Drawback: Changes the structure of the class. Helpfile goes at the bottom as it has to explicitly pull in values already defined. Will possibly impact command efficiency.
Using the Example: The AP cost for Donate would be automatically taken from action_point_cost and integrated into the help text.
Markup Style (Optional): To ensure users know which values are static and which are pulled from the code, code values might be given a different default color than the standard @help output. This could be a Green or White highlight, for example.