2 from . import MinetestCheckError, ContentType
3 from .config import parse_conf
5 basenamePattern = re.compile("^([a-z0-9_]+)$")
7 def get_base_dir(path):
8 if not os.path.isdir(path):
9 raise IOError("Expected dir")
11 root, subdirs, files = next(os.walk(path))
12 if len(subdirs) == 1 and len(files) == 0:
13 return get_base_dir(path + "/" + subdirs[0])
18 def detect_type(path):
19 if os.path.isfile(path + "/game.conf"):
20 return ContentType.GAME
21 elif os.path.isfile(path + "/init.lua"):
22 return ContentType.MOD
23 elif os.path.isfile(path + "/modpack.txt") or \
24 os.path.isfile(path + "/modpack.conf"):
25 return ContentType.MODPACK
26 elif os.path.isdir(path + "/mods"):
27 return ContentType.GAME
28 elif os.path.isfile(path + "/texture_pack.conf"):
29 return ContentType.TXP
31 return ContentType.UNKNOWN
34 def get_csv_line(line):
35 return [x.strip() for x in line.split(",") if x.strip() != ""]
38 class PackageTreeNode:
39 def __init__(self, baseDir, relative, author=None, repo=None, name=None):
40 self.baseDir = baseDir
41 self.relative = relative
49 self.type = detect_type(baseDir)
52 if self.type == ContentType.GAME:
53 if not os.path.isdir(baseDir + "/mods"):
54 raise MinetestCheckError(("Game at {} does not have a mods/ folder").format(self.relative))
55 self.add_children_from_mod_dir(baseDir + "/mods")
56 elif self.type == ContentType.MOD:
57 if self.name and not basenamePattern.match(self.name):
58 raise MinetestCheckError(("Invalid base name for mod {} at {}, names must only contain a-z0-9_.") \
59 .format(self.name, self.relative))
60 elif self.type == ContentType.MODPACK:
61 self.add_children_from_mod_dir(baseDir)
64 def getMetaFilePath(self):
66 if self.type == ContentType.GAME:
67 filename = "game.conf"
68 elif self.type == ContentType.MOD:
70 elif self.type == ContentType.MODPACK:
71 filename = "modpack.conf"
72 elif self.type == ContentType.TXP:
73 filename = "texture_pack.conf"
77 return self.baseDir + "/" + filename
85 with open(self.getMetaFilePath() or "", "r") as myfile:
86 conf = parse_conf(myfile.read())
87 for key, value in conf.items():
93 if not "description" in result:
95 with open(self.baseDir + "/description.txt", "r") as myfile:
96 result["description"] = myfile.read()
101 if "depends" in result or "optional_depends" in result:
102 if "depends" in result:
103 result["depends"] = get_csv_line(result["depends"])
105 if "optional_depends" in result:
106 result["optional_depends"] = get_csv_line(result["optional_depends"])
110 pattern = re.compile("^([a-z0-9_]+)\??$")
112 with open(self.baseDir + "/depends.txt", "r") as myfile:
113 contents = myfile.read()
116 for line in contents.split("\n"):
118 if pattern.match(line):
119 if line[len(line) - 1] == "?":
120 soft.append( line[:-1])
124 result["depends"] = hard
125 result["optional_depends"] = soft
131 # Fix games using "name" as "title"
132 if self.type == ContentType.GAME:
133 result["title"] = result["name"]
137 if "name" in result and not "title" in result:
138 result["title"] = result["name"].replace("_", " ").title()
140 # Calculate short description
141 if "description" in result:
142 desc = result["description"]
143 idx = desc.find(".") + 1
144 cutIdx = min(len(desc), 200 if idx < 5 else idx)
145 result["short_description"] = desc[:cutIdx]
148 self.name = result["name"]
153 def add_children_from_mod_dir(self, dir):
154 for entry in next(os.walk(dir))[1]:
155 path = os.path.join(dir, entry)
156 if not entry.startswith('.') and os.path.isdir(path):
157 child = PackageTreeNode(path, self.relative + entry + "/", name=entry)
158 if not child.type.isModLike():
159 raise MinetestCheckError(("Expecting mod or modpack, found {} at {} inside {}") \
160 .format(child.type.value, child.relative, self.type.value))
162 if child.name is None:
163 raise MinetestCheckError(("Missing base name for mod at {}").format(self.relative))
165 self.children.append(child)
167 def getModNames(self):
168 return self.fold("name", type=ContentType.MOD)
170 # attr: Attribute name
171 # key: Key in attribute
172 # retval: Accumulator
173 # type: Filter to type
174 def fold(self, attr, key=None, retval=None, type=None):
178 # Iterate through children
179 for child in self.children:
180 child.fold(attr, key, retval, type)
183 if type and type != self.type:
187 at = getattr(self, attr)
192 value = at if key is None else at.get(key)
193 if isinstance(value, list):
201 return self.meta.get(key)
204 for child in self.children: