Add BigBrother.
This commit is contained in:
parent
63832a3d8f
commit
fd14622a36
41
BigBrother/BigBrother.lua
Normal file
41
BigBrother/BigBrother.lua
Normal file
|
@ -0,0 +1,41 @@
|
|||
BigBrother = {}
|
||||
BigBrother.title = GetAddOnMetadata("BigBrother", "Title")
|
||||
BigBrother.version = GetAddOnMetadata("BigBrother", "Version")
|
||||
BigBrother.author = GetAddOnMetadata("BigBrother", "Author")
|
||||
BigBrother.Items = {}
|
||||
print(("Loaded %s v%s by %s."):format(BigBrother.title, BigBrother.version, BigBrother.author))
|
||||
|
||||
BigBrotherDB = {} -- default value, will be overwritten by the game if persistent data exists
|
||||
|
||||
local frame = CreateFrame("FRAME")
|
||||
frame:RegisterEvent("ENCOUNTER_START")
|
||||
frame:SetScript("OnEvent", function(self, event, encounterID, encounterName, difficultyID, groupSize)
|
||||
if not IsInRaid() then
|
||||
return
|
||||
end
|
||||
local classBuffs = {}
|
||||
for className, _ in pairs(RAID_CLASS_COLORS) do
|
||||
classBuffs[className] = {}
|
||||
end
|
||||
for m = 1, GetNumGroupMembers() do
|
||||
local unit = "raid" .. m
|
||||
local unitBuffs = {}
|
||||
for b = 1, 40 do
|
||||
_, _, _, _, _, _, _, _, _, spellId = UnitBuff(unit, b)
|
||||
if not spellId then
|
||||
break
|
||||
end
|
||||
unitBuffs[b] = spellId
|
||||
end
|
||||
local _, className = UnitClass(unit)
|
||||
classBuffs[className][UnitName(unit)] = unitBuffs
|
||||
end
|
||||
|
||||
table.insert(BigBrotherDB, {
|
||||
date = date("%F %T"),
|
||||
zone = GetRealZoneText(),
|
||||
encounterID = encounterID,
|
||||
encounterName = encounterName,
|
||||
buffs = classBuffs,
|
||||
})
|
||||
end)
|
12
BigBrother/BigBrother.toc
Normal file
12
BigBrother/BigBrother.toc
Normal file
|
@ -0,0 +1,12 @@
|
|||
## Interface: 11305
|
||||
|
||||
## Title: BigBrother
|
||||
## Notes: Log raid buffs and consumables.
|
||||
## Author: Caspervk
|
||||
## Version: 0.0.1
|
||||
|
||||
## X-License: GNU General Public License v3 or later (GPLv3+)
|
||||
|
||||
##SavedVariables: BigBrotherDB
|
||||
|
||||
BigBrother.lua
|
0
BigBrother/README.md
Normal file
0
BigBrother/README.md
Normal file
4
BigBrother/bb/__init__.py
Normal file
4
BigBrother/bb/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
__version__ = "0.0.1"
|
||||
__author__ = "Casper V. Kristensen"
|
||||
__licence__ = "GPLv3"
|
||||
__url__ = "https://git.caspervk.net/caspervk/wow-addons"
|
4
BigBrother/bb/__main__.py
Normal file
4
BigBrother/bb/__main__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
if __name__ == "__main__":
|
||||
from .cli import main
|
||||
|
||||
main()
|
69
BigBrother/bb/cli.py
Normal file
69
BigBrother/bb/cli.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import argparse
|
||||
import itertools
|
||||
import logging
|
||||
import logging.config
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from . import __url__, __author__, __version__, config, graph
|
||||
from .slpp import slpp as lua
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CLI:
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
def parse_args(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="bb",
|
||||
description=f"BigBrother v{__version__} by {__author__}.",
|
||||
epilog=f"For more information, see <{__url__}>."
|
||||
)
|
||||
parser.add_argument(
|
||||
dest="saved_variables",
|
||||
type=lambda s: Path(s),
|
||||
help="Path to WTF/Account/<ACCOUNT>/SavedVariables/BigBrother.lua",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="count",
|
||||
default=0,
|
||||
help="Increase verbosity level. Can be used multiple times."
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
def select_raid(self, saved_variables: Path) -> List[dict]:
|
||||
text = saved_variables.read_text(encoding="utf8")
|
||||
db = lua.decode(f"{{{text}}}") # double-{} since slpp requires all variables to be part of a lua table
|
||||
raids = []
|
||||
for zone, encounters in itertools.groupby(db["BigBrotherDB"], key=lambda x: (x["zone"], x["date"][:10])):
|
||||
raids.append(list(encounters))
|
||||
raids.reverse() # ensure latest raid is first
|
||||
for i, raid in enumerate(raids, start=1):
|
||||
print(f"{i:3} {raid[0]['date']} {raid[0]['zone']}")
|
||||
choice = int(input("Raid: "))
|
||||
return raids[choice-1]
|
||||
|
||||
def prompt_delete_saved_variables(self, saved_variables: Path) -> None:
|
||||
if input("Delete saved variables BigBrother.lua? [y/N] ").lower() == "y":
|
||||
saved_variables.unlink()
|
||||
logger.info("%s deleted.", saved_variables)
|
||||
|
||||
def run(self) -> None:
|
||||
args = self.parse_args()
|
||||
logging.config.dictConfig(config.get_logging_config(level=("WARNING", "INFO", "DEBUG")[min(args.verbose, 2)]))
|
||||
logger.debug("Args: %s", args)
|
||||
raid = self.select_raid(args.saved_variables)
|
||||
graph.graph(raid)
|
||||
self.prompt_delete_saved_variables(args.saved_variables)
|
||||
|
||||
|
||||
def main():
|
||||
cli = CLI()
|
||||
cli.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
36
BigBrother/bb/config/__init__.py
Normal file
36
BigBrother/bb/config/__init__.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
here = Path(__file__).parent.resolve()
|
||||
buffs = yaml.safe_load(here.joinpath("buffs.yaml").read_text("utf8"))
|
||||
class_overrides = yaml.safe_load(here.joinpath("class_overrides.yaml").read_text("utf8"))
|
||||
|
||||
|
||||
def get_logging_config(level="WARNING") -> dict:
|
||||
return {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"standard": {
|
||||
"format": "%(asctime)s [%(levelname)-7s] %(name)s:%(funcName)s - %(message)s"
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stdout",
|
||||
"formatter": "standard",
|
||||
"level": level,
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"bb": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["console"]
|
||||
}
|
||||
},
|
||||
}
|
131
BigBrother/bb/config/buffs.yaml
Normal file
131
BigBrother/bb/config/buffs.yaml
Normal file
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
# 15366 Songflower
|
||||
|
||||
Dragonslayer:
|
||||
color: "e20a00"
|
||||
ids:
|
||||
- 22888
|
||||
classes:
|
||||
- ALL
|
||||
|
||||
Zandalar:
|
||||
color: "41ca46"
|
||||
ids:
|
||||
- 24425
|
||||
classes:
|
||||
- ALL
|
||||
|
||||
DM:T:
|
||||
color: "016275"
|
||||
ids:
|
||||
- 22817 # Fengus' Ferocity
|
||||
- 22820 # Slip'kik's Savvy
|
||||
- 22818 # Mol'dar's Moxie
|
||||
classes:
|
||||
- ALL
|
||||
|
||||
Zanza:
|
||||
color: "f07cf7"
|
||||
ids:
|
||||
- 24382 # Spirit of Zanza
|
||||
- 20081 # Swiftness of Zanza
|
||||
- 20080 # Sheen of Zanza
|
||||
classes:
|
||||
- ALL
|
||||
|
||||
|
||||
Alchohol:
|
||||
color: "b99863"
|
||||
ids:
|
||||
- 22790 # Kreeg's Stout Beatdown
|
||||
- 22789 # Gordok Green Grog
|
||||
- 25804 # Rumsey Rum Black Label
|
||||
classes:
|
||||
- Druid
|
||||
- Priest
|
||||
- Tank Warrior
|
||||
|
||||
Food:
|
||||
color: "f6abaf"
|
||||
ids:
|
||||
- 18194 # Nightfin Soup
|
||||
- 24799 # Smoked Desert Dumplings
|
||||
- 18192 # Grilled Squid
|
||||
- 18125 # Blessed Sunfruit
|
||||
- 19710 # Tender Wolf Steak
|
||||
- 22730 # Runn Tum Tuber Surprise
|
||||
classes:
|
||||
- ALL
|
||||
|
||||
Mageblood:
|
||||
color: "550c65"
|
||||
ids:
|
||||
- 24363
|
||||
classes:
|
||||
- Balance Druid
|
||||
- Druid
|
||||
- Paladin
|
||||
- Priest
|
||||
|
||||
Strength:
|
||||
color: "d5bb33"
|
||||
ids:
|
||||
- 11405 # Elixir of the Giants
|
||||
- 16323 # Juju Power
|
||||
classes:
|
||||
- Feral Druid
|
||||
- Retribution Paladin
|
||||
- Rogue
|
||||
- Warrior
|
||||
- Tank Warrior
|
||||
|
||||
Mongoose:
|
||||
color: "5e66e5"
|
||||
ids:
|
||||
- 17538
|
||||
classes:
|
||||
- Feral Druid
|
||||
- Hunter
|
||||
- Retribution Paladin
|
||||
- Rogue
|
||||
- Warrior
|
||||
- Tank Warrior
|
||||
|
||||
Arcane:
|
||||
color: "9a39ad"
|
||||
ids:
|
||||
- 17539
|
||||
classes:
|
||||
- Balance Druid
|
||||
- Mage
|
||||
- Shadow Priest
|
||||
- Warlock
|
||||
|
||||
Shadow:
|
||||
color: "510164"
|
||||
ids:
|
||||
- 11474
|
||||
classes:
|
||||
- Shadow Priest
|
||||
- Warlock
|
||||
|
||||
Frost:
|
||||
color: "60d0fe"
|
||||
ids:
|
||||
- 21920
|
||||
classes:
|
||||
- Mage
|
||||
|
||||
Arthas:
|
||||
color: "2bddc7"
|
||||
ids:
|
||||
- 11374
|
||||
classes:
|
||||
- Tank Warrior
|
||||
|
||||
Defense:
|
||||
color: "0a0a0a"
|
||||
ids:
|
||||
- 11348
|
||||
classes:
|
||||
- Tank Warrior
|
10
BigBrother/bb/config/class_overrides.yaml
Normal file
10
BigBrother/bb/config/class_overrides.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Needed since Classic API doesn't support talent specialisation inspection
|
||||
Ralfe: Balance Druid
|
||||
Ezeriel: Feral Druid
|
||||
Zyalar: Feral Druid
|
||||
Borahk: Retribution Paladin
|
||||
Loodt: Shadow Priest
|
||||
Corten: Tank Warrior
|
||||
Boldus: Tank Warrior
|
||||
Exas: Tank Warrior
|
111
BigBrother/bb/graph.py
Normal file
111
BigBrother/bb/graph.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import base64
|
||||
import io
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from . import config
|
||||
|
||||
|
||||
def graph(raid: List[dict]) -> None:
|
||||
figs = []
|
||||
buff_ids = {
|
||||
buff_id: buff_name
|
||||
for buff_name, buff_data in config.buffs.items()
|
||||
for buff_id in buff_data["ids"]
|
||||
}
|
||||
classes = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
|
||||
xticks = []
|
||||
first_encounter_date = None
|
||||
last_encounter_date = None
|
||||
previous_encounter_date = None
|
||||
for encounter in raid:
|
||||
encounter_date = datetime.strptime(encounter["date"], "%Y-%m-%d %H:%M:%S")
|
||||
if first_encounter_date is None:
|
||||
first_encounter_date = encounter_date
|
||||
previous_encounter_date = encounter_date - timedelta(minutes=5)
|
||||
last_encounter_date = encounter_date
|
||||
xticks.append((encounter_date, encounter["encounterName"]))
|
||||
for class_name, players in encounter["buffs"].items():
|
||||
for player_name, buffs in sorted(players.items()):
|
||||
real_class_name = config.class_overrides.get(player_name, str.title(class_name))
|
||||
for buff_id in buffs:
|
||||
try:
|
||||
buff_name = buff_ids[buff_id]
|
||||
except KeyError:
|
||||
continue # buff not tracked
|
||||
classes[real_class_name][player_name][buff_name].append(
|
||||
(previous_encounter_date, (encounter_date - previous_encounter_date))
|
||||
)
|
||||
previous_encounter_date = encounter_date
|
||||
|
||||
for class_name, players in sorted(classes.items()):
|
||||
fig, ax = plt.subplots()
|
||||
required_class_buffs = {
|
||||
buff_name: buff_data
|
||||
for buff_name, buff_data in config.buffs.items()
|
||||
if any(x in buff_data["classes"] for x in ("ALL", class_name))
|
||||
}
|
||||
#legend_handles = []
|
||||
bar_width = 1 / (len(required_class_buffs) + 1)
|
||||
for b, (buff_name, buff) in enumerate(required_class_buffs.items()):
|
||||
bar_color = "#{color}".format(**buff)
|
||||
#legend_handles.append(Patch(label=buff_name, color=bar_color))
|
||||
for y0, (player_name, player_buffs) in zip(np.arange(len(players)), players.items()):
|
||||
ax.broken_barh(
|
||||
player_buffs[buff_name],
|
||||
(bar_width/2 + y0 + b * bar_width, bar_width),
|
||||
color=bar_color
|
||||
)
|
||||
|
||||
annotation_color = "black"
|
||||
try:
|
||||
if player_buffs[buff_name][1][0] == first_encounter_date:
|
||||
annotation_color = contrast_color(*tuple(int(buff["color"][i:i+2], 16) for i in (0, 2, 4)))
|
||||
except IndexError:
|
||||
pass
|
||||
ax.annotate(
|
||||
f" {buff_name}",
|
||||
xy=(first_encounter_date, bar_width + y0 + b * bar_width),
|
||||
ha="left",
|
||||
va="center",
|
||||
color=annotation_color
|
||||
)
|
||||
|
||||
for y0 in np.arange(len(players)):
|
||||
ax.axhline(y=y0, linewidth=1.0, color="black")
|
||||
|
||||
plt.xticks(*zip(*xticks), rotation=30, ha="right")
|
||||
plt.yticks(
|
||||
[x for x in np.arange(len(players))],
|
||||
players
|
||||
)
|
||||
ax.invert_yaxis()
|
||||
ax.grid(axis="x", linestyle="--")
|
||||
ax.set_xlim(first_encounter_date - timedelta(minutes=10), last_encounter_date + timedelta(minutes=1))
|
||||
|
||||
plt.title(class_name, fontweight="bold", fontsize=16)
|
||||
#plt.legend(handles=legend_handles, bbox_to_anchor=(1, 1))
|
||||
fig.set_size_inches(15, 2*len(players))
|
||||
plt.tight_layout()
|
||||
#plt.show()
|
||||
|
||||
buffer = io.BytesIO()
|
||||
fig.savefig(buffer, format="svg")
|
||||
figs.append(base64.b64encode(buffer.getbuffer()).decode("ascii"))
|
||||
|
||||
with open("{date:10.10}-{zone}.html".format(**raid[0]), "w") as f:
|
||||
f.write("<html>")
|
||||
f.writelines(
|
||||
f"<img src='data:image/svg+xml;base64,{fig}'/>"
|
||||
for fig in figs
|
||||
)
|
||||
f.write("</html>")
|
||||
|
||||
|
||||
def contrast_color(r, g, b):
|
||||
luma = ((0.299 * r) + (0.587 * g) + (0.114 * b)) / 255
|
||||
return "black" if luma > 0.5 else "white"
|
3
BigBrother/bb/requirements.txt
Normal file
3
BigBrother/bb/requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
pyyaml
|
||||
matplotlib
|
||||
numpy
|
281
BigBrother/bb/slpp.py
Normal file
281
BigBrother/bb/slpp.py
Normal file
|
@ -0,0 +1,281 @@
|
|||
"""
|
||||
Simple lua-python data structures parser from https://github.com/SirAnthony/slpp (b947496 @ 28-04-2020).
|
||||
|
||||
Copyright (c) 2010, 2011, 2012 SirAnthony <anthony at adsorbtion.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from numbers import Number
|
||||
|
||||
ERRORS = {
|
||||
'unexp_end_string': u'Unexpected end of string while parsing Lua string.',
|
||||
'unexp_end_table': u'Unexpected end of table while parsing Lua string.',
|
||||
'mfnumber_minus': u'Malformed number (no digits after initial minus).',
|
||||
'mfnumber_dec_point': u'Malformed number (no digits after decimal point).',
|
||||
'mfnumber_sci': u'Malformed number (bad scientific format).',
|
||||
}
|
||||
|
||||
def sequential(lst):
|
||||
length = len(lst)
|
||||
if length == 0 or lst[0] != 0:
|
||||
return False
|
||||
for i in range(length):
|
||||
if i + 1 < length:
|
||||
if lst[i] + 1 != lst[i+1]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SLPP(object):
|
||||
|
||||
def __init__(self):
|
||||
self.text = ''
|
||||
self.ch = ''
|
||||
self.at = 0
|
||||
self.len = 0
|
||||
self.depth = 0
|
||||
self.space = re.compile('\s', re.M)
|
||||
self.alnum = re.compile('\w', re.M)
|
||||
self.newline = '\n'
|
||||
self.tab = '\t'
|
||||
|
||||
def decode(self, text):
|
||||
if not text or not isinstance(text, str):
|
||||
return
|
||||
# FIXME: only short comments removed
|
||||
reg = re.compile('--.*$', re.M)
|
||||
text = reg.sub('', text, 0)
|
||||
self.text = text
|
||||
self.at, self.ch, self.depth = 0, '', 0
|
||||
self.len = len(text)
|
||||
self.next_chr()
|
||||
result = self.value()
|
||||
return result
|
||||
|
||||
def encode(self, obj):
|
||||
self.depth = 0
|
||||
return self.__encode(obj)
|
||||
|
||||
def __encode(self, obj):
|
||||
s = ''
|
||||
tab = self.tab
|
||||
newline = self.newline
|
||||
|
||||
if isinstance(obj, str):
|
||||
s += '"%s"' % obj.replace(r'"', r'\"')
|
||||
elif isinstance(obj, bytes):
|
||||
s += '"{}"'.format(''.join(r'\x{:02x}'.format(c) for c in obj))
|
||||
elif isinstance(obj, bool):
|
||||
s += str(obj).lower()
|
||||
elif obj is None:
|
||||
s += 'nil'
|
||||
elif isinstance(obj, Number):
|
||||
s += str(obj)
|
||||
elif isinstance(obj, (list, tuple, dict)):
|
||||
self.depth += 1
|
||||
if len(obj) == 0 or (not isinstance(obj, dict) and len([
|
||||
x for x in obj
|
||||
if isinstance(x, Number) or (isinstance(x, str) and len(x) < 10)
|
||||
]) == len(obj)):
|
||||
newline = tab = ''
|
||||
dp = tab * self.depth
|
||||
s += "%s{%s" % (tab * (self.depth - 2), newline)
|
||||
if isinstance(obj, dict):
|
||||
key = '[%s]' if all(isinstance(k, int) for k in obj.keys()) else '%s'
|
||||
contents = [dp + (key + ' = %s') % (k, self.__encode(v)) for k, v in obj.items()]
|
||||
s += (',%s' % newline).join(contents)
|
||||
else:
|
||||
s += (',%s' % newline).join(
|
||||
[dp + self.__encode(el) for el in obj])
|
||||
self.depth -= 1
|
||||
s += "%s%s}" % (newline, tab * self.depth)
|
||||
return s
|
||||
|
||||
def white(self):
|
||||
while self.ch:
|
||||
if self.space.match(self.ch):
|
||||
self.next_chr()
|
||||
else:
|
||||
break
|
||||
|
||||
def next_chr(self):
|
||||
if self.at >= self.len:
|
||||
self.ch = None
|
||||
return None
|
||||
self.ch = self.text[self.at]
|
||||
self.at += 1
|
||||
return True
|
||||
|
||||
def value(self):
|
||||
self.white()
|
||||
if not self.ch:
|
||||
return
|
||||
if self.ch == '{':
|
||||
return self.object()
|
||||
if self.ch == "[":
|
||||
self.next_chr()
|
||||
if self.ch in ['"', "'", '[']:
|
||||
return self.string(self.ch)
|
||||
if self.ch.isdigit() or self.ch == '-':
|
||||
return self.number()
|
||||
return self.word()
|
||||
|
||||
def string(self, end=None):
|
||||
s = ''
|
||||
start = self.ch
|
||||
if end == '[':
|
||||
end = ']'
|
||||
if start in ['"', "'", '[']:
|
||||
while self.next_chr():
|
||||
if self.ch == end:
|
||||
self.next_chr()
|
||||
if start != "[" or self.ch == ']':
|
||||
return s
|
||||
if self.ch == '\\' and start == end:
|
||||
self.next_chr()
|
||||
if self.ch != end:
|
||||
s += '\\'
|
||||
s += self.ch
|
||||
raise ParseError(ERRORS['unexp_end_string'])
|
||||
|
||||
def object(self):
|
||||
o = {}
|
||||
k = None
|
||||
idx = 0
|
||||
numeric_keys = False
|
||||
self.depth += 1
|
||||
self.next_chr()
|
||||
self.white()
|
||||
if self.ch and self.ch == '}':
|
||||
self.depth -= 1
|
||||
self.next_chr()
|
||||
return o # Exit here
|
||||
else:
|
||||
while self.ch:
|
||||
self.white()
|
||||
if self.ch == '{':
|
||||
o[idx] = self.object()
|
||||
idx += 1
|
||||
continue
|
||||
elif self.ch == '}':
|
||||
self.depth -= 1
|
||||
self.next_chr()
|
||||
if k is not None:
|
||||
o[idx] = k
|
||||
if len([key for key in o if isinstance(key, (str, float, bool, tuple))]) == 0:
|
||||
so = sorted([key for key in o])
|
||||
if sequential(so):
|
||||
ar = []
|
||||
for key in o:
|
||||
ar.insert(key, o[key])
|
||||
o = ar
|
||||
return o # or here
|
||||
else:
|
||||
if self.ch == ',':
|
||||
self.next_chr()
|
||||
continue
|
||||
else:
|
||||
k = self.value()
|
||||
if self.ch == ']':
|
||||
self.next_chr()
|
||||
self.white()
|
||||
ch = self.ch
|
||||
if ch in ('=', ','):
|
||||
self.next_chr()
|
||||
self.white()
|
||||
if ch == '=':
|
||||
o[k] = self.value()
|
||||
else:
|
||||
o[idx] = k
|
||||
idx += 1
|
||||
k = None
|
||||
raise ParseError(ERRORS['unexp_end_table']) # Bad exit here
|
||||
|
||||
words = {'true': True, 'false': False, 'nil': None}
|
||||
def word(self):
|
||||
s = ''
|
||||
if self.ch != '\n':
|
||||
s = self.ch
|
||||
self.next_chr()
|
||||
while self.ch is not None and self.alnum.match(self.ch) and s not in self.words:
|
||||
s += self.ch
|
||||
self.next_chr()
|
||||
return self.words.get(s, s)
|
||||
|
||||
def number(self):
|
||||
def next_digit(err):
|
||||
n = self.ch
|
||||
self.next_chr()
|
||||
if not self.ch or not self.ch.isdigit():
|
||||
raise ParseError(err)
|
||||
return n
|
||||
n = ''
|
||||
try:
|
||||
if self.ch == '-':
|
||||
n += next_digit(ERRORS['mfnumber_minus'])
|
||||
n += self.digit()
|
||||
if n == '0' and self.ch in ['x', 'X']:
|
||||
n += self.ch
|
||||
self.next_chr()
|
||||
n += self.hex()
|
||||
else:
|
||||
if self.ch and self.ch == '.':
|
||||
n += next_digit(ERRORS['mfnumber_dec_point'])
|
||||
n += self.digit()
|
||||
if self.ch and self.ch in ['e', 'E']:
|
||||
n += self.ch
|
||||
self.next_chr()
|
||||
if not self.ch or self.ch not in ('+', '-'):
|
||||
raise ParseError(ERRORS['mfnumber_sci'])
|
||||
n += next_digit(ERRORS['mfnumber_sci'])
|
||||
n += self.digit()
|
||||
except ParseError:
|
||||
t, e = sys.exc_info()[:2]
|
||||
print(e)
|
||||
return 0
|
||||
try:
|
||||
return int(n, 0)
|
||||
except:
|
||||
pass
|
||||
return float(n)
|
||||
|
||||
def digit(self):
|
||||
n = ''
|
||||
while self.ch and self.ch.isdigit():
|
||||
n += self.ch
|
||||
self.next_chr()
|
||||
return n
|
||||
|
||||
def hex(self):
|
||||
n = ''
|
||||
while self.ch and (self.ch in 'ABCDEFabcdef' or self.ch.isdigit()):
|
||||
n += self.ch
|
||||
self.next_chr()
|
||||
return n
|
||||
|
||||
|
||||
slpp = SLPP()
|
||||
|
||||
__all__ = ['slpp']
|
Loading…
Reference in a new issue