1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
import os
from . import MinetestCheckError, ContentType
from .config import parse_conf
def get_base_dir(path):
if not os.path.isdir(path):
raise IOError("Expected dir")
root, subdirs, files = next(os.walk(path))
if len(subdirs) == 1 and len(files) == 0:
return get_base_dir(path + "/" + subdirs[0])
else:
return path
def detect_type(path):
if os.path.isfile(path + "/game.conf"):
return ContentType.GAME
elif os.path.isfile(path + "/init.lua"):
return ContentType.MOD
elif os.path.isfile(path + "/modpack.txt") or \
os.path.isfile(path + "/modpack.conf"):
return ContentType.MODPACK
elif os.path.isdir(path + "/mods"):
return ContentType.GAME
elif os.path.isfile(path + "/texture_pack.conf"):
return ContentType.TXP
else:
return ContentType.UNKNOWN
class PackageTreeNode:
def __init__(self, baseDir, relative, author=None, repo=None, name=None):
self.baseDir = baseDir
self.relative = relative
self.author = author
self.name = name
self.repo = repo
self.meta = None
self.children = []
# Detect type
self.type = detect_type(baseDir)
self.read_meta()
if self.type == ContentType.GAME:
if not os.path.isdir(baseDir + "/mods"):
raise MinetestCheckError(("game at {} does not have a mods/ folder").format(self.relative))
self.add_children_from_mod_dir(baseDir + "/mods")
elif self.type == ContentType.MODPACK:
self.add_children_from_mod_dir(baseDir)
def getMetaFilePath(self):
filename = None
if self.type == ContentType.GAME:
filename = "game.conf"
elif self.type == ContentType.MOD:
filename = "mod.conf"
elif self.type == ContentType.MODPACK:
filename = "modpack.conf"
elif self.type == ContentType.TXP:
filename = "texture_pack.conf"
else:
return None
return self.baseDir + "/" + filename
def read_meta(self):
result = {}
# .conf file
try:
with open(self.getMetaFilePath() or "", "r") as myfile:
conf = parse_conf(myfile.read())
for key, value in conf.items():
result[key] = value
except IOError:
pass
# description.txt
if not "description" in result:
try:
with open(self.baseDir + "/description.txt", "r") as myfile:
result["description"] = myfile.read()
except IOError:
pass
# depends.txt
import re
pattern = re.compile("^([a-z0-9_]+)\??$")
if not "depends" in result and not "optional_depends" in result:
try:
with open(self.baseDir + "/depends.txt", "r") as myfile:
contents = myfile.read()
soft = []
hard = []
for line in contents.split("\n"):
line = line.strip()
if pattern.match(line):
if line[len(line) - 1] == "?":
soft.append( line[:-1])
else:
hard.append(line)
result["depends"] = hard
result["optional_depends"] = soft
except IOError:
pass
else:
if "depends" in result:
result["depends"] = [x.strip() for x in result["depends"].split(",")]
if "optional_depends" in result:
result["optional_depends"] = [x.strip() for x in result["optional_depends"].split(",")]
# Fix games using "name" as "title"
if self.type == ContentType.GAME:
result["title"] = result["name"]
del result["name"]
# Calculate Title
if "name" in result and not "title" in result:
result["title"] = result["name"].replace("_", " ").title()
# Calculate short description
if "description" in result:
desc = result["description"]
idx = desc.find(".") + 1
cutIdx = min(len(desc), 200 if idx < 5 else idx)
result["short_description"] = desc[:cutIdx]
if "name" in result:
self.name = result["name"]
del result["name"]
self.meta = result
def add_children_from_mod_dir(self, dir):
for entry in next(os.walk(dir))[1]:
path = os.path.join(dir, entry)
if not entry.startswith('.') and os.path.isdir(path):
child = PackageTreeNode(path, self.relative + entry + "/", name=entry)
if not child.type.isModLike():
raise MinetestCheckError(("Expecting mod or modpack, found {} at {} inside {}") \
.format(child.type.value, child.relative, self.type.value))
self.children.append(child)
def getModNames(self):
return self.fold("name", type=ContentType.MOD)
# attr: Attribute name
# key: Key in attribute
# retval: Accumulator
# type: Filter to type
def fold(self, attr, key=None, retval=None, type=None):
if retval is None:
retval = set()
# Iterate through children
for child in self.children:
child.fold(attr, key, retval, type)
# Filter on type
if type and type != self.type:
return retval
# Get attribute
at = getattr(self, attr)
if not at:
return retval
# Get value
value = at if key is None else at.get(key)
if isinstance(value, list):
retval |= set(value)
elif value:
retval.add(value)
return retval
def get(self, key):
return self.meta.get(key)
def validate(self):
for child in self.children:
child.validate()
|