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