import t, c from stdint import * import memhub import string import stdlib import viperlib import hashlib import w32.win32file import w32.win32base import w32.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 '0' + n if uppercase != 0: return 'A' + (n - 10) return '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(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(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: w32.win32file.WIN32_FIND_DATAA* = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__()) if find_data is None: stdlib.free(pattern) return -1 string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__()) handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data) if handle == w32.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 & w32.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 w32.win32file.RemoveDirectoryA(full_path) else: # Delete the file if w32.win32file.DeleteFileA(full_path) != 0: deleted += 1 stdlib.free(full_path) if w32.win32file.FindNextFileA(handle, find_data) == 0: break w32.win32file.FindClose(handle) stdlib.free(pattern) stdlib.free(find_data) return deleted