forked from linuxmint/cinnamon-spices-actions
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validate-spice
executable file
·177 lines (146 loc) · 6.61 KB
/
validate-spice
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
#!/usr/bin/python3
import argparse
import configparser
import json
import os
import sys
import subprocess
from PIL import Image
SPICE_EXT = ".nemo_action.in"
GROUP = "Nemo Action"
class CheckError(Exception):
pass
# function with checks for an xlet
def validate_xlet(uuid):
valid = False
os.chdir(uuid)
try:
# Check mandatory files
for file in ["info.json", f"files/{uuid}/icon.png"]:
if not os.path.exists(file):
raise CheckError(f"Missing file: {file}")
if f"{uuid}{SPICE_EXT}" not in os.listdir('.'):
raise CheckError(f"Missing main {SPICE_EXT} file")
# Check forbidden .nemo_action.in format
config = configparser.ConfigParser()
config.optionxform = str
if config.read(f"{uuid}{SPICE_EXT}"):
if len(config.sections()) > 1:
raise CheckError(f"Too many sections in {SPICE_EXT} file!")
if GROUP in config.sections():
bad_chars = ['[', ']']
for key in config[GROUP].keys():
if any(bad_char in key for bad_char in bad_chars):
raise CheckError(f"Bad key: '{key}' in {SPICE_EXT} file!")
base_keys = ["_Name", "_Comment", "Exec"]
for key in base_keys:
if key not in config[GROUP].keys():
raise CheckError(f"Missing key: '{key}' not in {SPICE_EXT} file!")
else:
raise CheckError(f"Missing '{GROUP}' section in {SPICE_EXT} file!")
# Check forbidden files
for file in ["icon.png", "screenshot.png", f"{uuid}.nemo_action"]:
if os.path.exists(file):
raise CheckError(f"Forbidden file: {file}")
# Check mandatory directories
for directory in ["files", f"files/{uuid}"]:
if not os.path.isdir(directory):
raise CheckError(f"Missing directory: {directory}")
# Check that there are no files in files other than the uuid directory
if len(os.listdir("files")) != 1:
raise CheckError("The files directory should ONLY contain the $uuid directory!")
# check info.json
with open("info.json", encoding='utf-8') as file:
try:
info = json.load(file)
except Exception as e:
raise CheckError(f"Could not parse info.json: {e}") from e
# check mandatory fields
for field in ['author']:
if field not in info.keys():
raise CheckError(f"Missing field '{field}' in info.json")
# check metadata.json
with open(f"files/{uuid}/metadata.json", encoding='utf-8') as file:
try:
metadata = json.load(file)
except Exception as e:
raise CheckError(f"Could not parse metadata.json: {e}") from e
# check forbidden fields
for field in ['icon', 'dangerous', 'last-edited', 'max-instances']:
if field in metadata.keys():
raise CheckError(f"Forbidden field '{field}' in {file.name}")
# check mandatory fields
for field in ['uuid', 'name', 'description']:
if field not in metadata.keys():
raise CheckError(f"Missing field '{field}' in {file.name}")
# check uuid value
if metadata['uuid'] != uuid:
raise CheckError(f"Wrong uuid in {file.name}")
# check for unicode characters in metadata
for field in metadata.keys():
strvalue = str(metadata[field])
if len(strvalue.encode()) != len(strvalue):
raise CheckError(f"Forbidden unicode characters in field '{field}' in {file.name}")
# check if icon is square
im = Image.open(f"files/{uuid}/icon.png")
(width, height) = im.size
if width != height:
raise CheckError("icon.png has to be square.")
# check po and pot files
podir = f"files/{uuid}/po"
if os.path.isdir(podir):
for file in os.listdir(podir):
if file.endswith(".po"):
pocheck = subprocess.run(["msgfmt", "-o", "-", "-c",
os.path.join(podir, file)],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True, check=True)
if pocheck.stderr:
raise CheckError(f"The following errors were found in translation file '{file}':\n{pocheck.stderr}"
f"HINT: Most errors in translation file `{file}` can usually be prevented and solved by using Poedit.\n")
elif file.endswith(".pot"):
potcheck = subprocess.run(["xgettext", "-o", "-",
os.path.join(podir, file)],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True, check=True)
if potcheck.stderr:
raise CheckError(f"The following errors were found in translation template '{file}':\n{potcheck.stderr}")
# Finally...
valid = True
except CheckError as error:
print(f"[{uuid}] Error during validation: {error}")
except Exception as error:
print(f"[{uuid}] Unknown error. {error}")
os.chdir("..")
return valid
def quit(valid):
if valid:
print("No errors found. Everything looks good.")
sys.exit(0)
else:
print("\nErrors were found! Once you fix the issue, please re-run "
"'validate-spice <uuid>' to check for further errors.")
sys.exit(1)
def main():
parser = argparse.ArgumentParser()
parser.description = 'Arguments for validate-spice'
parser.add_argument('-a', '--all', action='store_true',
help='Validate all Spices')
parser.add_argument('uuid', type=str, metavar='UUID', nargs='?',
help='the UUID of the Spice')
args = parser.parse_args()
if args.all:
is_valid = True
for file_path in os.listdir("."):
if os.path.isdir(file_path) and not file_path.startswith("."):
is_valid = validate_xlet(file_path)
elif args.uuid:
is_valid = validate_xlet(args.uuid)
else:
parser.print_help()
sys.exit(2)
quit(is_valid)
if __name__ == "__main__":
main()