Files

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