190 lines
7.3 KiB
Plaintext
190 lines
7.3 KiB
Plaintext
from stdint import *
|
|
import memhub
|
|
import string
|
|
import stdlib
|
|
import viperlib
|
|
import hashlib
|
|
import win32.win32file
|
|
import win32.win32base
|
|
import win32.fileio as fileio
|
|
|
|
|
|
# Utils - project utility functions (SHA1 hashing, directory cleanup)
|
|
|
|
# Global mbuddy pointer
|
|
_mbuddy: memhub.MemBuddy*
|
|
|
|
|
|
# nibble_to_hex_char - convert a nibble to a hex character
|
|
def nibble_to_hex_char(n: int, uppercase: int) -> char:
|
|
"""Convert a 0-15 nibble to a hex character (uppercase=1 uses 'A'-'F')."""
|
|
if n < 10: return char('0' + n)
|
|
if uppercase != 0: return char('A' + (n - 10))
|
|
return char('a' + (n - 10))
|
|
|
|
|
|
# bytes_to_hex - convert a byte sequence to a hex string
|
|
def bytes_to_hex(pool: memhub.MemBuddy*, digest: bytes, nbytes: sizet, uppercase: int, use_malloc: int) -> str:
|
|
"""Convert the first nbytes of digest to a null-terminated hex string."""
|
|
if digest is None or nbytes <= 0: return None
|
|
hex_len: sizet = nbytes * 2 + 1
|
|
hex_buf: str = None
|
|
if use_malloc != 0: hex_buf = stdlib.malloc(hex_len)
|
|
else: hex_buf = pool.alloc(hex_len)
|
|
if hex_buf is None: return None
|
|
for i in range(int(nbytes)):
|
|
hi: int = (digest[i] >> 4) & 0xF
|
|
lo: int = digest[i] & 0xF
|
|
hex_buf[i * 2] = nibble_to_hex_char(hi, uppercase)
|
|
hex_buf[i * 2 + 1] = nibble_to_hex_char(lo, uppercase)
|
|
hex_buf[nbytes * 2] = '\0'
|
|
return hex_buf
|
|
|
|
|
|
# compute_sha1 - compute a string SHA1, return the first 16 hex chars
|
|
def compute_sha1(pool: memhub.MemBuddy*, content: str) -> str:
|
|
"""Compute the SHA1 of a string, returning its first 16 hex chars."""
|
|
if content is None or pool is None: return None
|
|
|
|
# ctx is stack-allocated (alloca); no heap allocation needed
|
|
ctx: hashlib.sha1* = hashlib.sha1()
|
|
if ctx is None: return None
|
|
|
|
# Compute the digest
|
|
ctx.update(content)
|
|
digest: bytes = pool.alloc(hashlib.SHA1_DIGEST_LEN)
|
|
if digest is None: return None
|
|
ctx.final(digest)
|
|
|
|
# Convert to a hex string (first 8 bytes = 16 hex chars)
|
|
return bytes_to_hex(pool, digest, 8, 0, 0)
|
|
|
|
|
|
# is_vp_at - check whether a path segment ends with .vp
|
|
def is_vp_at(p: str, start: sizet, plen: sizet) -> int:
|
|
"""Return 1 if p[start, start+plen) ends with the .vp suffix."""
|
|
if p is None or plen <= 3: return 0
|
|
base: sizet = start + plen - 3
|
|
if p[base] == '.' and p[base + 1] == 'v' and p[base + 2] == 'p': return 1
|
|
return 0
|
|
|
|
|
|
# JoinPath - join two path segments as a/b
|
|
def JoinPath(a: str, b: str) -> str:
|
|
"""Join two path segments as "a/b"; caller frees the malloc'd result."""
|
|
if a is None or b is None: return None
|
|
a_len: sizet = string.strlen(a)
|
|
b_len: sizet = string.strlen(b)
|
|
total_len: sizet = a_len + b_len + 2
|
|
buf: str = stdlib.malloc(total_len)
|
|
if buf is None: return None
|
|
viperlib.snprintf(buf, total_len, "%s/%s", a, b)
|
|
return buf
|
|
|
|
|
|
# read_file_to_buf - read a whole file into a null-terminated buffer
|
|
def read_file_to_buf(path: str, buf_size: sizet) -> bytes:
|
|
"""Read a whole file into a null-terminated malloc buffer, or None."""
|
|
if path is None or buf_size <= 0: return None
|
|
sf: fileio.File* = fileio.File(path, fileio.MODE.R)
|
|
if sf.closed: return None
|
|
buf: bytes = stdlib.malloc(buf_size)
|
|
if buf is None:
|
|
sf.close()
|
|
return None
|
|
br: int64 = sf.read_all(buf, buf_size)
|
|
sf.close()
|
|
if br <= 0:
|
|
stdlib.free(buf)
|
|
return None
|
|
if br < buf_size: buf[br] = 0
|
|
return buf
|
|
|
|
|
|
# PathToModuleName - convert a file path to a module name
|
|
def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
|
|
"""Convert a file path to a dotted module name; caller frees the result."""
|
|
if path is None: return None
|
|
plen: sizet = string.strlen(path)
|
|
# Skip the includes/ or includes\ prefix
|
|
prefix_len: sizet = 9
|
|
path_start: sizet = 0
|
|
if strip_includes_prefix != 0 and plen > prefix_len and string.strncmp(path, "includes", 8) == 0:
|
|
psep_ch: char = path[8]
|
|
if psep_ch == '/' or psep_ch == '\\':
|
|
path_start = prefix_len
|
|
plen = plen - prefix_len
|
|
# Strip the .vp suffix
|
|
if is_vp_at(path, path_start, plen) != 0: plen -= 3
|
|
if plen == 0: return None
|
|
# Check for a trailing /__init__ or \__init__ (package __init__.vp)
|
|
if plen > 9:
|
|
tail_off: sizet = path_start + plen - 9
|
|
sep_char: char = path[tail_off]
|
|
if (sep_char == '/' or sep_char == '\\') and path[tail_off + 1] == '_' and path[tail_off + 2] == '_' and path[tail_off + 3] == 'i' and path[tail_off + 4] == 'n' and path[tail_off + 5] == 'i' and path[tail_off + 6] == 't' and path[tail_off + 7] == '_' and path[tail_off + 8] == '_':
|
|
# Drop the separator + __init__ suffix
|
|
plen = tail_off - path_start
|
|
if plen == 0: return None
|
|
# Copy and replace / with .
|
|
result: str = stdlib.malloc(plen + 1)
|
|
if result is None: return None
|
|
for ri in range(int(plen)):
|
|
ch: char = path[path_start + ri]
|
|
if ch == '/' or ch == '\\': result[ri] = '.'
|
|
else: result[ri] = ch
|
|
result[plen] = '\0'
|
|
return result
|
|
|
|
|
|
# CleanDir - recursively delete all files/subdirs under dir_path (for --clean)
|
|
def CleanDir(dir_path: str) -> int:
|
|
"""Recursively delete files/subdirs under dir_path, returning the count."""
|
|
if dir_path is None: return -1
|
|
dir_len: sizet = string.strlen(dir_path)
|
|
pattern: bytes = stdlib.malloc(dir_len + 8)
|
|
if pattern is None: return -1
|
|
viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path)
|
|
|
|
find_data: win32.win32file.WIN32_FIND_DATAA* = stdlib.malloc(win32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
|
if find_data is None:
|
|
stdlib.free(pattern)
|
|
return -1
|
|
string.memset(find_data, 0, win32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
|
|
|
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, find_data)
|
|
if handle == win32.win32base.INVALID_HANDLE_VALUE:
|
|
stdlib.free(pattern)
|
|
stdlib.free(find_data)
|
|
return 0
|
|
|
|
deleted: int = 0
|
|
while True:
|
|
fname: str = find_data.cFileName
|
|
if fname is not None:
|
|
# Skip . and ..
|
|
if fname[0] == '.':
|
|
if fname[1] == '\0': fname = None
|
|
elif fname[1] == '.' and fname[2] == '\0': fname = None
|
|
if fname is not None:
|
|
full_path: bytes = JoinPath(dir_path, fname)
|
|
if full_path is not None:
|
|
# Check whether this is a directory
|
|
is_dir: uint32 = find_data.dwFileAttributes & win32.win32file.FILE_ATTRIBUTE_DIRECTORY
|
|
if is_dir != 0:
|
|
# Recurse into the subdirectory
|
|
sub_deleted: int = CleanDir(full_path)
|
|
if sub_deleted > 0: deleted += sub_deleted
|
|
# Remove the now-empty subdirectory
|
|
win32.win32file.RemoveDirectoryA(full_path)
|
|
else:
|
|
# Delete the file
|
|
if win32.win32file.DeleteFileA(full_path) != 0: deleted += 1
|
|
stdlib.free(full_path)
|
|
|
|
if win32.win32file.FindNextFileA(handle, find_data) == 0: break
|
|
|
|
win32.win32file.FindClose(handle)
|
|
stdlib.free(pattern)
|
|
stdlib.free(find_data)
|
|
return deleted
|