mirror of
https://github.com/Ed94/WATL_Exercise.git
synced 2025-08-04 22:32:43 -07:00
some more progress
This commit is contained in:
@@ -5,34 +5,6 @@ Version: 0 (AS INTENDED)
|
|||||||
Vendor OS & Compiler: Windows 11, MSVC
|
Vendor OS & Compiler: Windows 11, MSVC
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma region Debug
|
|
||||||
|
|
||||||
#if !defined(BUILD_DEBUG)
|
|
||||||
#define debug_trap()
|
|
||||||
#define assert(cond)
|
|
||||||
#define assert_msg(cond, msg, ...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(BUILD_DEBUG)
|
|
||||||
#define debug_trap() __debugbreak()
|
|
||||||
|
|
||||||
#define assert(cond) assert_msg(cond, nullptr)
|
|
||||||
#define assert_msg(cond, msg, ...) \
|
|
||||||
do \
|
|
||||||
{ \
|
|
||||||
if (! (cond)) \
|
|
||||||
{ \
|
|
||||||
assert_handler(lit(stringify(cond)), lit(__FILE__), lit(__func__), cast(S64, __LINE__), msg, ##__VA_ARGS__); \
|
|
||||||
debug_trap(); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
while(0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void assert_handler(Str8 condition, Str8 path_file, Str8 function, S64 line, message, ...);
|
|
||||||
|
|
||||||
#pragma endregion Debug
|
|
||||||
|
|
||||||
#pragma region C Things
|
#pragma region C Things
|
||||||
|
|
||||||
#define cast(type, data) ((type)(data))
|
#define cast(type, data) ((type)(data))
|
||||||
@@ -40,8 +12,8 @@ void assert_handler(Str8 condition, Str8 path_file, Str8 function, S64 line, mes
|
|||||||
|
|
||||||
#define nullptr cast(void*, 0)
|
#define nullptr cast(void*, 0)
|
||||||
|
|
||||||
#define glue_impl(A, B) A ## B
|
// #define glue_impl(A, B) A ## B
|
||||||
#define glue(A, B) glue_impl(A, B)
|
// #define glue(A, B) glue_impl(A, B)
|
||||||
|
|
||||||
// Enforces size querying uses SSIZE type.
|
// Enforces size querying uses SSIZE type.
|
||||||
#define size_of(data) cast(SSIZE, sizeof(data))
|
#define size_of(data) cast(SSIZE, sizeof(data))
|
||||||
@@ -81,30 +53,12 @@ typedef S8 B8;
|
|||||||
typedef S16 B16;
|
typedef S16 B16;
|
||||||
typedef S32 B32;
|
typedef S32 B32;
|
||||||
|
|
||||||
#pragma region C Things
|
#pragma endregion C Things
|
||||||
|
|
||||||
#pragma region Memory Operations
|
#pragma region Memory Operations
|
||||||
|
|
||||||
inline
|
void* memory_copy(void* restrict dest, void const* restrict src, USIZE length);
|
||||||
void* memory_copy(void* restrict dest, void const* restrict src, USIZE length)
|
B32 memory_zero(void* dest, USIZE length);
|
||||||
{
|
|
||||||
if (dest == nullptr || src == nullptr || length == 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
// https://learn.microsoft.com/en-us/cpp/intrinsics/movsb?view=msvc-170
|
|
||||||
__movsb((unsigned char*)dest, (const unsigned char*)src, length);
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline
|
|
||||||
B32 memory_zero(void* dest, USIZE length)
|
|
||||||
{
|
|
||||||
if (dest == nullptr || length <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
__stosb((unsigned char*)dest, 0, length);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct Slice_Byte Slice_Byte;
|
typedef struct Slice_Byte Slice_Byte;
|
||||||
struct Slice_Byte {
|
struct Slice_Byte {
|
||||||
@@ -117,14 +71,7 @@ struct Slice_Byte {
|
|||||||
assert(slice.len > 0); \
|
assert(slice.len > 0); \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
inline
|
void slice__copy(Slice_Byte dest, SSIZE dest_typewidth, Slice_Byte src, SSIZE src_typewidth);
|
||||||
void slice__copy(Slice_Byte dest, SSIZE dest_typewidth, Slice_Byte src, SSIZE src_typewidth) {
|
|
||||||
assert(dest.len >= src.len);
|
|
||||||
slice_assert(dest);
|
|
||||||
slice_assert(src);
|
|
||||||
memory_copy(dest.ptr, src.ptr, src.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define slice_copy(dest, src) slice__copy( \
|
#define slice_copy(dest, src) slice__copy( \
|
||||||
(Slice_Byte){(dest).ptr, (dest).len * size_of(*(dest).ptr)}, size_of(*(dest).ptr) \
|
(Slice_Byte){(dest).ptr, (dest).len * size_of(*(dest).ptr)}, size_of(*(dest).ptr) \
|
||||||
, (Slice_Byte){(src ).ptr, (src ).len * size_of(*(src ).ptr)}, size_of(*(src ).ptr) \
|
, (Slice_Byte){(src ).ptr, (src ).len * size_of(*(src ).ptr)}, size_of(*(src ).ptr) \
|
||||||
@@ -135,15 +82,15 @@ void slice__copy(Slice_Byte dest, SSIZE dest_typewidth, Slice_Byte src, SSIZE sr
|
|||||||
#pragma region Allocator Interface
|
#pragma region Allocator Interface
|
||||||
|
|
||||||
typedef U32 AllocatorOp; enum {
|
typedef U32 AllocatorOp; enum {
|
||||||
Alloc,
|
AllocatorOp_Alloc = 0,
|
||||||
Alloc_NoZero, // If Alloc exist, so must No_Zero
|
AllocatorOp_Alloc_NoZero, // If Alloc exist, so must No_Zero
|
||||||
Free,
|
AllocatorOp_Free,
|
||||||
Reset,
|
AllocatorOp_Reset,
|
||||||
Resize,
|
AllocatorOp_Resize,
|
||||||
Resize_NoZero,
|
AllocatorOp_Resize_NoZero,
|
||||||
Rewind,
|
AllocatorOp_Rewind,
|
||||||
SavePoint,
|
AllocatorOp_SavePoint,
|
||||||
Query, // Must always be implemented
|
AllocatorOp_Query, // Must always be implemented
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -185,8 +132,8 @@ typedef void (AllocatorProc) (AllocatorOpData In, AllocatorOpData* Out);
|
|||||||
|
|
||||||
typedef struct AllocatorInfo AllocatorInfo;
|
typedef struct AllocatorInfo AllocatorInfo;
|
||||||
struct AllocatorInfo {
|
struct AllocatorInfo {
|
||||||
AllocatorProc proc;
|
AllocatorProc* proc;
|
||||||
void* data;
|
void* data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This the point right after the last allocation usually.
|
// This the point right after the last allocation usually.
|
||||||
@@ -203,14 +150,14 @@ Slice_Byte mem_alloc (AllocatorInfo ainfo, SSIZE size);
|
|||||||
Slice_Byte mem_alloc_nz (AllocatorInfo ainfo, SSIZE size);
|
Slice_Byte mem_alloc_nz (AllocatorInfo ainfo, SSIZE size);
|
||||||
Slice_Byte mem_alloc_align (AllocatorInfo ainfo, SSIZE size);
|
Slice_Byte mem_alloc_align (AllocatorInfo ainfo, SSIZE size);
|
||||||
Slice_Byte mem_alloc_align_nz (AllocatorInfo ainfo, SSIZE size);
|
Slice_Byte mem_alloc_align_nz (AllocatorInfo ainfo, SSIZE size);
|
||||||
void mem_free (AllocatorInfo ainfo, SliceByte mem);
|
void mem_free (AllocatorInfo ainfo, Slice_Byte mem);
|
||||||
void mem_reset (AllocatorInfo ainfo);
|
void mem_reset (AllocatorInfo ainfo);
|
||||||
Slice_Byte mem_grow (AllocatorInfo ainfo, SliceByte mem, SSIZE size);
|
Slice_Byte mem_grow (AllocatorInfo ainfo, Slice_Byte mem, SSIZE size);
|
||||||
Slice_Byte mem_shrink (AllocatorInfo ainfo, SliceByte mem, SSIZE size);
|
Slice_Byte mem_shrink (AllocatorInfo ainfo, Slice_Byte mem, SSIZE size);
|
||||||
Slice_Byte mem_resize (AllocatorInfo ainfo, SliceByte mem, SSIZE desired_size);
|
Slice_Byte mem_resize (AllocatorInfo ainfo, Slice_Byte mem, SSIZE desired_size);
|
||||||
Slice_Byte mem_resize_nz (AllocatorInfo ainfo, SliceByte mem, SSIZE desired_size);
|
Slice_Byte mem_resize_nz (AllocatorInfo ainfo, Slice_Byte mem, SSIZE desired_size);
|
||||||
Slice_Byte mem_resize_align (AllocatorInfo ainfo, SliceByte mem, SSIZE desired_size, SSIZE alignment);
|
Slice_Byte mem_resize_align (AllocatorInfo ainfo, Slice_Byte mem, SSIZE desired_size, SSIZE alignment);
|
||||||
Slice_Byte mem_resize_align_nz(AllocatorInfo ainfo, SliceByte mem, SSIZE desired_size, SSIZE alignment);
|
Slice_Byte mem_resize_align_nz(AllocatorInfo ainfo, Slice_Byte mem, SSIZE desired_size, SSIZE alignment);
|
||||||
void mem_rewind (AllocatorInfo ainfo, AllocatorSP save_point);
|
void mem_rewind (AllocatorInfo ainfo, AllocatorSP save_point);
|
||||||
AllocatorSP mem_save_point (AllocatorInfo ainfo);
|
AllocatorSP mem_save_point (AllocatorInfo ainfo);
|
||||||
|
|
||||||
@@ -226,6 +173,8 @@ struct Str8 {
|
|||||||
};
|
};
|
||||||
#define lit(string_literal) (Str8){ string_literal, size_of(string_literal) - 1 }
|
#define lit(string_literal) (Str8){ string_literal, size_of(string_literal) - 1 }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct Str8Cache_Slot Str8Cache_Slot;
|
typedef struct Str8Cache_Slot Str8Cache_Slot;
|
||||||
struct Str8Cache_Slot {
|
struct Str8Cache_Slot {
|
||||||
Str8Cache_Slot* next;
|
Str8Cache_Slot* next;
|
||||||
@@ -250,21 +199,48 @@ struct Str8Cache {
|
|||||||
void str8cache_init(Str8Cache* cache, AllocatorInfo ainfo_str, AllocatorInfo ainfo_slots);
|
void str8cache_init(Str8Cache* cache, AllocatorInfo ainfo_str, AllocatorInfo ainfo_slots);
|
||||||
Str8Cache str8cache_make( AllocatorInfo ainfo_str, AllocatorInfo ainfo_slots);
|
Str8Cache str8cache_make( AllocatorInfo ainfo_str, AllocatorInfo ainfo_slots);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#pragma endregion Strings
|
#pragma endregion Strings
|
||||||
|
|
||||||
|
#pragma region Debug
|
||||||
|
|
||||||
|
#if !defined(BUILD_DEBUG)
|
||||||
|
#define debug_trap()
|
||||||
|
#define assert(cond)
|
||||||
|
#define assert_msg(cond, msg, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(BUILD_DEBUG)
|
||||||
|
#define debug_trap() __debugbreak()
|
||||||
|
|
||||||
|
#define assert(cond) assert_msg(cond, nullptr)
|
||||||
|
#define assert_msg(cond, msg, ...) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
if (! (cond)) \
|
||||||
|
{ \
|
||||||
|
assert_handler(lit(stringify(cond)), lit(__FILE__), lit(__func__), cast(S64, __LINE__), msg, ##__VA_ARGS__); \
|
||||||
|
debug_trap(); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void assert_handler(Str8 condition, Str8 path_file, Str8 function, S64 line, Str8 message, ...);
|
||||||
|
|
||||||
|
#pragma endregion Debug
|
||||||
|
|
||||||
#pragma region File System
|
#pragma region File System
|
||||||
|
|
||||||
typedef struct FileOpInfo;
|
typedef struct FileOpInfo FileOpInfo;
|
||||||
struct FileOpInfo {
|
struct FileOpInfo {
|
||||||
Slice_Byte content;
|
Slice_Byte content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct Opts_read_file_contents Opts_read_file_contents;
|
||||||
struct Opts_read_file_contents {
|
struct Opts_read_file_contents {
|
||||||
AllocatorInfo backing;
|
AllocatorInfo backing;
|
||||||
B32 zero_backing;
|
B32 zero_backing;
|
||||||
}
|
};
|
||||||
|
|
||||||
void file_read_contents_api(FileOpInfo* result, Str8 path, Opts_read_file_contents opts);
|
void file_read_contents_api(FileOpInfo* result, Str8 path, Opts_read_file_contents opts);
|
||||||
void file_write_str8 (Str8 path, Str8 content);
|
void file_write_str8 (Str8 path, Str8 content);
|
||||||
@@ -337,6 +313,43 @@ void watl_lex_api(WATL_LexInfo* info, Str8 source, Opts_watl_lex* opts);
|
|||||||
|
|
||||||
#pragma region Implementation
|
#pragma region Implementation
|
||||||
|
|
||||||
|
#pragma region Memory Operations
|
||||||
|
|
||||||
|
inline
|
||||||
|
void* memory_copy(void* restrict dest, void const* restrict src, USIZE length)
|
||||||
|
{
|
||||||
|
if (dest == nullptr || src == nullptr || length == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// https://learn.microsoft.com/en-us/cpp/intrinsics/movsb?view=msvc-170
|
||||||
|
memcpy((unsigned char*)dest, (const unsigned char*)src, length);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
B32 memory_zero(void* dest, USIZE length)
|
||||||
|
{
|
||||||
|
if (dest == nullptr || length <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset((unsigned char*)dest, 0, length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
void slice__copy(Slice_Byte dest, SSIZE dest_typewidth, Slice_Byte src, SSIZE src_typewidth) {
|
||||||
|
assert(dest.len >= src.len);
|
||||||
|
slice_assert(dest);
|
||||||
|
slice_assert(src);
|
||||||
|
memory_copy(dest.ptr, src.ptr, src.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion Memory Operations
|
||||||
|
|
||||||
|
#pragma region Allocator Interface
|
||||||
|
|
||||||
|
#pragma endregion Allocator Interface
|
||||||
|
|
||||||
#pragma region File System
|
#pragma region File System
|
||||||
|
|
||||||
#include <apiset.h>
|
#include <apiset.h>
|
||||||
@@ -452,4 +465,12 @@ file_read_contents_api(FileOpInfo* result, Str8* path, Opts__read_file_contents*
|
|||||||
|
|
||||||
#pragma endregion File System
|
#pragma endregion File System
|
||||||
|
|
||||||
|
#pragma region Debug
|
||||||
|
|
||||||
|
void assert_handler(Str8 condition, Str8 path_file, Str8 function, Str8 line, Str8 message, ...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion Debug
|
||||||
|
|
||||||
#pragma endregion Implementation
|
#pragma endregion Implementation
|
@@ -1,4 +1,8 @@
|
|||||||
$devshell = join-path $PSScriptRoot 'devshell.ps1'
|
$misc = Join-Path $PSScriptRoot 'helpers/misc.psm1'
|
||||||
|
import-module $misc
|
||||||
|
|
||||||
|
$devshell = join-path $PSScriptRoot 'helpers/devshell.ps1'
|
||||||
|
$path_root = Get-ScriptRepoRoot
|
||||||
|
|
||||||
if ($IsWindows) {
|
if ($IsWindows) {
|
||||||
& $devshell -arch amd64
|
& $devshell -arch amd64
|
||||||
@@ -60,7 +64,7 @@ $archiver = 'lib'
|
|||||||
$compiler = 'cl'
|
$compiler = 'cl'
|
||||||
$linker = 'link'
|
$linker = 'link'
|
||||||
|
|
||||||
$path_build = join-path $PSScriptRoot 'build'
|
$path_build = join-path $path_root 'build'
|
||||||
if ( -not(test-path -Path $path_build) ) {
|
if ( -not(test-path -Path $path_build) ) {
|
||||||
new-item -ItemType Directory -Path $path_build
|
new-item -ItemType Directory -Path $path_build
|
||||||
}
|
}
|
||||||
@@ -101,14 +105,14 @@ $compiler_args += ( $flag_path_debug + $path_build + '\' )
|
|||||||
$compiler_args += $flag_link_win_rt_static_debug
|
$compiler_args += $flag_link_win_rt_static_debug
|
||||||
|
|
||||||
# Include setup
|
# Include setup
|
||||||
$compiler_args += ($flag_include + $PSScriptRoot)
|
$compiler_args += ($flag_include + $path_root)
|
||||||
|
|
||||||
# Specify unit to compile
|
# Specify unit to compile
|
||||||
$unit = join-path $PSScriptRoot 'watl.v0.msvc.c'
|
$unit = join-path $path_root 'C\watl.v0.msvc.c'
|
||||||
$compiler_args += $flag_compile, $unit
|
$compiler_args += $flag_compile, $unit
|
||||||
|
|
||||||
# Diagnoistc print for the args
|
# Diagnoistc print for the args
|
||||||
# $compiler_args | ForEach-Object { Write-Host $_ }
|
$compiler_args | ForEach-Object { Write-Host $_ }
|
||||||
write-host
|
write-host
|
||||||
|
|
||||||
# $compiler_args += ( $flag_define + 'DEMO_STR_SLICE' )
|
# $compiler_args += ( $flag_define + 'DEMO_STR_SLICE' )
|
||||||
@@ -137,7 +141,7 @@ if ($true) {
|
|||||||
$linker_args += $object
|
$linker_args += $object
|
||||||
|
|
||||||
# Diagnoistc print for the args
|
# Diagnoistc print for the args
|
||||||
# $linker_args | ForEach-Object { Write-Host $_ }
|
$linker_args | ForEach-Object { Write-Host $_ }
|
||||||
write-host
|
write-host
|
||||||
|
|
||||||
& $linker $linker_args
|
& $linker $linker_args
|
73
scripts/helpers/incremental_checks.ps1
Normal file
73
scripts/helpers/incremental_checks.ps1
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# This is meant to be used with build.ps1, and is not a standalone script.
|
||||||
|
|
||||||
|
function check-FileForChanges
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$path_file
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path $path_file -PathType Leaf)) {
|
||||||
|
Write-Error "The provided path is not a valid file: $path_file"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
$file_name = Split-Path $path_file -Leaf
|
||||||
|
$path_csv = Join-Path $path_build ($file_name + "_file_hash.csv")
|
||||||
|
|
||||||
|
$csv_file_hash = $null
|
||||||
|
if (Test-Path $path_csv) {
|
||||||
|
$csv_file_hash = Import-Csv $path_csv | Select-Object -ExpandProperty value
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_hash_info = Get-FileHash -Path $path_file -Algorithm MD5
|
||||||
|
$current_file_hash = $current_hash_info.Hash
|
||||||
|
|
||||||
|
# Save the current hash to the CSV
|
||||||
|
[PSCustomObject]@{
|
||||||
|
name = $path_file
|
||||||
|
value = $current_file_hash
|
||||||
|
} | Export-Csv $path_csv -NoTypeInformation
|
||||||
|
|
||||||
|
if ($csv_file_hash -and $csv_file_hash -eq $current_file_hash) {
|
||||||
|
return $false
|
||||||
|
} else {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check to see if the module has changed files since the last build
|
||||||
|
function check-ModuleForChanges
|
||||||
|
{
|
||||||
|
param( [string]$path_module, [array]$excludes )
|
||||||
|
|
||||||
|
$module_name = split-path $path_module -leaf
|
||||||
|
$path_csv = Join-Path $path_build ($module_name + "_module_hashes.csv")
|
||||||
|
|
||||||
|
$csv_file_hashes = $null
|
||||||
|
if ( test-path $path_csv ) {
|
||||||
|
$csv_file_hashes = @{}
|
||||||
|
import-csv $path_csv | foreach-object {
|
||||||
|
$csv_file_hashes[ $_.name ] = $_.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_hashes = @{}
|
||||||
|
get-childitem -path $path_module -recurse -file -Exclude $excludes | foreach-object {
|
||||||
|
$id = $_.fullname
|
||||||
|
$hash_info = get-filehash -path $id -Algorithm MD5
|
||||||
|
$file_hashes[ $id ] = $hash_info.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_hashes.GetEnumerator() | foreach-object { [PSCustomObject]$_ } |
|
||||||
|
export-csv $path_csv -NoTypeInformation
|
||||||
|
|
||||||
|
if ( -not $csv_file_hashes ) { return $true }
|
||||||
|
if ( $csv_file_hashes.Count -ne $file_hashes.Count ) { return $true }
|
||||||
|
|
||||||
|
foreach ( $key in $csv_file_hashes.Keys ) {
|
||||||
|
if ( $csv_file_hashes[ $key ] -ne $file_hashes[ $key ] ) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
20
scripts/helpers/misc.psm1
Normal file
20
scripts/helpers/misc.psm1
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
function Get-ScriptRepoRoot {
|
||||||
|
$currentPath = $PSScriptRoot
|
||||||
|
while ($currentPath -ne $null -and $currentPath -ne "")
|
||||||
|
{
|
||||||
|
if (Test-Path (Join-Path $currentPath ".git")) {
|
||||||
|
return $currentPath
|
||||||
|
}
|
||||||
|
# Also check for .git file which indicates a submodule
|
||||||
|
$gitFile = Join-Path $currentPath ".git"
|
||||||
|
if (Test-Path $gitFile -PathType Leaf)
|
||||||
|
{
|
||||||
|
$gitContent = Get-Content $gitFile
|
||||||
|
if ($gitContent -match "gitdir: (.+)") {
|
||||||
|
return $currentPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$currentPath = Split-Path $currentPath -Parent
|
||||||
|
}
|
||||||
|
throw "Unable to find repository root"
|
||||||
|
}
|
Reference in New Issue
Block a user