fstdtools 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fstdtools/__init__.py ADDED
File without changes
fstdtools/__main__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .cli import cli
2
+ cli()
fstdtools/cli.py ADDED
@@ -0,0 +1,130 @@
1
+ import click
2
+ import fstd
3
+ from pathlib import Path
4
+ from .convert import convert as converter
5
+ from importlib.metadata import version, PackageNotFoundError
6
+
7
+
8
+ def get_version():
9
+ try:
10
+ return version("fstdtools")
11
+ except PackageNotFoundError:
12
+ return "0.0.0-unknown"
13
+
14
+
15
+ def print_version(ctx, param, value):
16
+ if not value or ctx.resilient_parsing:
17
+ return
18
+ ver = fstd.get_version()
19
+ click.echo(click.style(f"fstdtools v{get_version()} | fstd core v{ver}", fg="green"))
20
+ ctx.exit()
21
+
22
+
23
+ def overwrite_confirm(ctx, file_path):
24
+ if Path(file_path).exists():
25
+ if not click.confirm(click.style(f"File {file_path} already exists. Overwrite?", fg="yellow"), default=False):
26
+ click.echo("Operation cancelled", err=True)
27
+ ctx.exit(code=1)
28
+ return False
29
+
30
+
31
+ # ===================== Global options =====================
32
+
33
+ @click.group(name="fstdtools", help="CLI tools for fstd dictionary to pack/unpack/list/info/convert.", context_settings={"help_option_names": ["-h", "--help"]})
34
+ @click.option("-V", "--version", is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="print version info and exit")
35
+ @click.option("--verbose", "-v", count=True, help="log level, -v simple log, -vv debug log")
36
+ @click.option("--log-level", "-l", type=click.IntRange(min=0, max=6), default=4, show_default=True, envvar="FSTDTOOLS_LOG_LEVEL", help=" log_level: 0-6, 0 is trace, 1 is debug, 2 is info, 3 is warn, 4 is error, 5 is critical, 6 is off")
37
+ @click.pass_context
38
+ def cli(ctx, verbose, log_level):
39
+ """global init, all subcommands share this context"""
40
+ ctx.ensure_object(dict)
41
+ ctx.obj["verbose"] = verbose
42
+ ctx.obj["log_level"] = log_level
43
+ fstd.set_log_level(log_level)
44
+
45
+
46
+ # ===================== Subcommands write =====================
47
+
48
+ @cli.command(name="write", help="from txt/fstdx/mdx to fstdx, from directory/mdd to fstdd")
49
+ @click.argument("source_file", type=click.Path(exists=True, file_okay=True, dir_okay=True, readable=True))
50
+ @click.argument("output_file", type=click.Path(file_okay=True, dir_okay=False, writable=True), required=False)
51
+ @click.option("-c", "--compress-level", type=click.IntRange(min=0, max=22), default=5, help="compression level 0(fast) ~ 22(max compress)")
52
+ @click.option("--compress-dict-size", type=click.IntRange(min=1, max=130), default=100, help="compression dict size, only for fstdx, 1~130, default 100")
53
+ @click.option("-b", "--block-size", type=click.IntRange(min=4, max=512), default=4, help="block size, default 4, unit KB")
54
+ @click.option("-t", "--thread", type=int, default=0, help="concurrency thread count, default 0, auto detect cpu count")
55
+ @click.option("--substyle/--no-substyle", default=False, help="enable substyle, only for mdx/mdd to fstdx/fstdd, default False")
56
+ @click.option("-y", "--yes", is_flag=True, help="overwrite output file, no confirm")
57
+ @click.pass_context
58
+ def convert_(
59
+ ctx, source_file, output_file, compress_level, compress_dict_size, block_size, thread, substyle, yes
60
+ ):
61
+ verbose = ctx.obj["verbose"]
62
+ src = Path(source_file)
63
+ out = Path(output_file) if output_file else None
64
+
65
+ if not src.exists():
66
+ click.echo(click.style(f"Source file {src} does not exist", fg="red"), err=True)
67
+ ctx.exit(code=1)
68
+
69
+ if out and out.exists() and not yes:
70
+ if not click.confirm(click.style(f"File {out} already exists, overwrite?", fg="yellow")):
71
+ click.echo("Operation cancelled", err=True)
72
+ ctx.exit(code=1)
73
+
74
+ def show_verbose():
75
+ if verbose >= 1:
76
+ click.echo(click.style(f"Source file: {src}", fg="cyan"))
77
+ click.echo(click.style(f"Output file: {output_file if output_file else 'default'}", fg="cyan"))
78
+ click.echo(click.style(f"Compression level: {compress_level}", fg="cyan"))
79
+ click.echo(click.style(f"Compression dict size: {compress_dict_size}", fg="cyan"))
80
+ click.echo(click.style(f"Block size: {block_size}", fg="cyan"))
81
+ click.echo(click.style(f"Concurrency threads: {thread if thread > 0 else 'auto detect cpu count'}", fg="cyan"))
82
+
83
+ if src.is_dir():
84
+ if out and not out.suffix == ".fstdd":
85
+ click.echo(click.style("For mdd source, output file must have .fstdd extension", fg="red"), err=True)
86
+ ctx.exit(code=1)
87
+ if not output_file:
88
+ output_file = str(src.with_suffix(".fstdd"))
89
+ overwrite_confirm(ctx, output_file)
90
+ writer = fstd.FstddWriter()
91
+ show_verbose()
92
+ writer.compile_fstdd(source_file, output_file, "{}", block_size, compress_level, thread, verbose >= 1)
93
+
94
+ elif src.suffix == ".mdx":
95
+ if out and not out.suffix == ".fstdx":
96
+ click.echo(click.style("For mdx source, output file must have .fstdx extension", fg="red"), err=True)
97
+ ctx.exit(code=1)
98
+ if not output_file:
99
+ output_file = src.with_suffix(".fstdx")
100
+ overwrite_confirm(ctx, output_file)
101
+ show_verbose()
102
+ converter(source_file, output_file, compress_level, compress_dict_size, block_size, thread, substyle, None)
103
+
104
+ elif src.suffix == ".mdd":
105
+ if out and not out.suffix == ".fstdd":
106
+ click.echo(click.style("For mdd source, output file must have .fstdd extension", fg="red"), err=True)
107
+ ctx.exit(code=1)
108
+ if not output_file:
109
+ output_file = str(src.with_suffix(".fstdd"))
110
+ overwrite_confirm(ctx, output_file)
111
+ show_verbose()
112
+ converter(source_file, output_file, compress_level, compress_dict_size, block_size, thread, substyle, None)
113
+
114
+ else:
115
+ # see src as txt or fstdx, then convert to fstdx
116
+ if out and not out.suffix == ".fstdx":
117
+ click.echo(click.style("For txt or fstdx source, output file must have .fstdx extension", fg="red"), err=True)
118
+ ctx.exit(code=1)
119
+ if not output_file:
120
+ output_file = src.with_suffix(".fstdx")
121
+ overwrite_confirm(ctx, output_file)
122
+ writer = fstd.FstdxWriter()
123
+ show_verbose()
124
+ writer.compile_fstdx(source_file, output_file, "{}", block_size, compress_level, compress_dict_size, thread, False, verbose >= 1)
125
+
126
+ click.echo(click.style(f"{output_file} written successfully", fg="bright_green"))
127
+
128
+
129
+ if __name__ == "__main__":
130
+ cli()
fstdtools/convert.py ADDED
@@ -0,0 +1,77 @@
1
+ import os.path
2
+ import fstd
3
+ import json
4
+ from concurrent.futures import ThreadPoolExecutor
5
+
6
+ from tqdm import tqdm
7
+
8
+ from .mdict.readmdict import MDX, MDD
9
+
10
+
11
+ def get_meta(source, substyle=False, passcode=None):
12
+ meta = {}
13
+ if source.endswith('.mdx'):
14
+ encoding = ''
15
+ md = MDX(source, encoding, substyle, passcode)
16
+ if source.endswith('.mdd'):
17
+ md = MDD(source, passcode)
18
+
19
+ for key, value in md.header.items():
20
+ # key has been decode from UTF-16 and encode again with UTF-8
21
+ key = key.decode('UTF-8').lower().title()
22
+ value = value.decode('UTF-8')
23
+ meta[key] = value
24
+ if value == 'Yes':
25
+ meta[key] = True
26
+ elif value == 'No':
27
+ meta[key] = False
28
+
29
+ return meta
30
+
31
+
32
+ def convert(source, target, compress_level, compress_dict_size, block_size, thread=0, substyle=False, passcode=None):
33
+ meta_info = get_meta(source, substyle, passcode)
34
+ meta_json_str = json.dumps(meta_info, ensure_ascii=False)
35
+ if source.endswith('.mdx'):
36
+ encoding = ''
37
+ mdx = MDX(source, encoding, substyle, passcode)
38
+ item_count = 0
39
+ keys = []
40
+ values = []
41
+
42
+ print("Extracting raw data from mdx:")
43
+ bar = tqdm(total=len(mdx), unit='rec')
44
+ for key, value in mdx.items():
45
+ if not value.strip():
46
+ bar.write('Skip entry: %s' % key)
47
+ continue
48
+ item_count += 1
49
+ keys.append(key)
50
+ values.append(value)
51
+ bar.update(1)
52
+ bar.close()
53
+ writer = fstd.FstdxWriter()
54
+ writer.compile_fstdx(target, keys, values, meta_json_str, block_size, compress_level, compress_dict_size, thread, False, True)
55
+ elif source.endswith('.mdd'):
56
+ mdd = MDD(source, passcode)
57
+ writer = fstd.FstddWriter()
58
+ executor = ThreadPoolExecutor(max_workers=1)
59
+ future = executor.submit(
60
+ writer.compile_fstdd,
61
+ len(mdd),
62
+ target,
63
+ meta_json_str,
64
+ block_size,
65
+ compress_level,
66
+ thread,
67
+ False
68
+ )
69
+ for key, value in mdd.items():
70
+ fname = key.decode('UTF-8').replace('\\', os.path.sep)
71
+ if not writer.push_file_stream(fname, value):
72
+ break
73
+ ret = future.result()
74
+ if ret != 0:
75
+ print("Compile fstdd failed with error code: %d" % ret)
76
+ else:
77
+ raise ValueError("Unsupported source file type: %s" % source)
File without changes
fstdtools/mdict/lzo.py ADDED
@@ -0,0 +1,246 @@
1
+ import math
2
+
3
+
4
+ class FlexBuffer():
5
+
6
+ def __init__(self):
7
+
8
+ self.blockSize = None
9
+ self.c = None
10
+ self.l = None
11
+ self.buf = None
12
+
13
+ def require(self, n):
14
+
15
+ r = self.c - self.l + n
16
+ if r > 0:
17
+ self.l = self.l + self.blockSize * math.ceil(r / self.blockSize)
18
+ #tmp = bytearray(self.l)
19
+ #for i in len(self.buf):
20
+ # tmp[i] = self.buf[i]
21
+ #self.buf = tmp
22
+ self.buf = self.buf + bytearray(self.l - len(self.buf))
23
+ self.c = self.c + n
24
+ return self.buf
25
+
26
+ def alloc(self, initSize, blockSize):
27
+
28
+ if blockSize:
29
+ sz = blockSize
30
+ else:
31
+ sz = 4096
32
+ self.blockSize = self.roundUp(sz)
33
+ self.c = 0
34
+ self.l = self.roundUp(initSize) | 0
35
+ self.l += self.blockSize - (self.l % self.blockSize)
36
+ self.buf = bytearray(self.l)
37
+ return self.buf
38
+
39
+ def roundUp(self, n):
40
+
41
+ r = n % 4
42
+ if r == 0:
43
+ return n
44
+ else:
45
+ return n + 4 - r
46
+
47
+ def reset(self):
48
+
49
+ self.c = 0
50
+ self.l = len(self.buf)
51
+
52
+ def pack(self, size):
53
+
54
+ return self.buf[0:size]
55
+
56
+ def _decompress(inBuf, outBuf):
57
+
58
+ c_top_loop = 1
59
+ c_first_literal_run = 2
60
+ c_match = 3
61
+ c_copy_match = 4
62
+ c_match_done = 5
63
+ c_match_next = 6
64
+
65
+ out = outBuf.buf
66
+ op = 0
67
+ ip = 0
68
+ t = inBuf[ip]
69
+ state = c_top_loop
70
+ m_pos = 0
71
+ ip_end = len(inBuf)
72
+
73
+ if t > 17:
74
+ ip = ip + 1
75
+ t = t - 17
76
+ if t < 4:
77
+ state = c_match_next
78
+ else:
79
+ out = outBuf.require(t)
80
+ while True:
81
+ out[op] = inBuf[ip]
82
+ op = op + 1
83
+ ip = ip + 1
84
+ t = t - 1
85
+ if not t > 0: break
86
+ state = c_first_literal_run
87
+
88
+ while True:
89
+ if_block = False
90
+
91
+ ##
92
+ if state == c_top_loop:
93
+ t = inBuf[ip]
94
+ ip = ip + 1
95
+ if t >= 16:
96
+ state = c_match
97
+ continue
98
+ if t == 0:
99
+ while inBuf[ip] == 0:
100
+ t = t + 255
101
+ ip = ip + 1
102
+ t = t + 15 + inBuf[ip]
103
+ ip = ip + 1
104
+
105
+ t = t + 3
106
+ out = outBuf.require(t)
107
+ while True:
108
+ out[op] = inBuf[ip]
109
+ op = op + 1
110
+ ip = ip + 1
111
+ t = t - 1
112
+ if not t > 0: break
113
+ # emulate c switch
114
+ state = c_first_literal_run
115
+
116
+ ##
117
+ if state == c_first_literal_run:
118
+ t = inBuf[ip]
119
+ ip = ip + 1
120
+ if t >= 16:
121
+ state = c_match
122
+ continue
123
+ m_pos = op - 0x801 - (t >> 2) - (inBuf[ip] << 2)
124
+ ip = ip + 1
125
+ out = outBuf.require(3)
126
+ out[op] = out[m_pos]
127
+ op = op + 1
128
+ m_pos = m_pos + 1
129
+ out[op] = out[m_pos]
130
+ op = op + 1
131
+ m_pos = m_pos + 1
132
+ out[op] = out[m_pos]
133
+ op = op + 1
134
+
135
+ state = c_match_done
136
+ continue
137
+
138
+ ##
139
+ if state == c_match:
140
+ if t >= 64:
141
+ m_pos = op - 1 - ((t >> 2) & 7) - (inBuf[ip] << 3)
142
+ ip = ip + 1
143
+ t = (t >> 5) - 1
144
+ state = c_copy_match
145
+ continue
146
+ elif t >= 32:
147
+ t = t & 31
148
+ if t == 0:
149
+ while inBuf[ip] == 0:
150
+ t = t + 255
151
+ ip = ip + 1
152
+ t = t + 31 + inBuf[ip]
153
+ ip = ip + 1
154
+ m_pos = op - 1 - ((inBuf[ip] + (inBuf[ip + 1] << 8)) >> 2)
155
+ ip = ip + 2
156
+ elif t >= 16:
157
+ m_pos = op - ((t & 8) << 11)
158
+ t = t & 7
159
+ if t == 0:
160
+ while inBuf[ip] == 0:
161
+ t = t + 255
162
+ ip = ip + 1
163
+ t = t + 7 + inBuf[ip]
164
+ ip = ip + 1
165
+ m_pos = m_pos - ((inBuf[ip] + (inBuf[ip + 1] << 8)) >> 2)
166
+ ip = ip + 2
167
+ if m_pos == op:
168
+ break
169
+ m_pos = m_pos - 0x4000
170
+ else:
171
+ m_pos = op - 1 - (t >> 2) - (inBuf[ip] << 2);
172
+ ip = ip + 1
173
+ out = outBuf.require(2)
174
+ out[op] = out[m_pos]
175
+ op = op + 1
176
+ m_pos = m_pos + 1
177
+ out[op] = out[m_pos]
178
+ op = op + 1
179
+ state = c_match_done
180
+ continue
181
+
182
+ if t >= 6 and (op - m_pos) >= 4:
183
+ if_block = True
184
+ t += 2
185
+ out = outBuf.require(t)
186
+ while True:
187
+ out[op] = out[m_pos]
188
+ op += 1
189
+ m_pos += 1
190
+ t -= 1
191
+ if not t > 0: break
192
+ #emulate c switch
193
+ state = c_copy_match
194
+
195
+ ##
196
+ if state == c_copy_match:
197
+ if not if_block:
198
+ t += 2
199
+ out = outBuf.require(t)
200
+ while True:
201
+ out[op] = out[m_pos]
202
+ op += 1
203
+ m_pos += 1
204
+ t -= 1
205
+ if not t > 0: break
206
+ #emulating c switch
207
+ state = c_match_done
208
+
209
+ ##
210
+ if state == c_match_done:
211
+ t = inBuf[ip - 2] & 3
212
+ if t == 0:
213
+ state = c_top_loop
214
+ continue
215
+ #emulate c switch
216
+ state = c_match_next
217
+
218
+ ##
219
+ if state == c_match_next:
220
+ out = outBuf.require(1)
221
+ out[op] = inBuf[ip]
222
+ op += 1
223
+ ip += 1
224
+ if t > 1:
225
+ out = outBuf.require(1)
226
+ out[op] = inBuf[ip]
227
+ op += 1
228
+ ip += 1
229
+ if t > 2:
230
+ out = outBuf.require(1)
231
+ out[op] = inBuf[ip]
232
+ op += 1
233
+ ip += 1
234
+ t = inBuf[ip]
235
+ ip += 1
236
+ state = c_match
237
+ continue
238
+
239
+ return bytes(outBuf.pack(op))
240
+
241
+ def decompress(input, initSize = 16000, blockSize = 8192):
242
+ output = FlexBuffer()
243
+ output.alloc(initSize, blockSize)
244
+ return _decompress(bytearray(input), output)
245
+
246
+