mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
utils: add new tool xash-clang-format, a wrapper around clang-format that adds our formatting extensions
This commit is contained in:
91
utils/xash-clang-format/config.c
Normal file
91
utils/xash-clang-format/config.c
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
config.c - .clang-format extension parser
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "crtlib.h"
|
||||
#include "xcf.h"
|
||||
|
||||
#define XCF_KEY "XashCollapseParens"
|
||||
|
||||
/*
|
||||
============
|
||||
ConfigDefault
|
||||
============
|
||||
*/
|
||||
static void ConfigDefault( xcf_config_t *c )
|
||||
{
|
||||
c->collapse_parens = false;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
SkipWhitespace
|
||||
============
|
||||
*/
|
||||
static const char *SkipWhitespace( const char *p, const char *end )
|
||||
{
|
||||
for( ; p < end && ( *p == ' ' || *p == '\t' ); p++ )
|
||||
;
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
ParseConfigText
|
||||
|
||||
Parse extension keys out of a YAML text body.
|
||||
We only look at lines that begin (after optional whitespace) with `#`.
|
||||
============
|
||||
*/
|
||||
qboolean ParseConfigText( const char *text, xcf_config_t *cfg )
|
||||
{
|
||||
ConfigDefault( cfg );
|
||||
if( !text )
|
||||
return true;
|
||||
|
||||
for( const char *p = text; *p; )
|
||||
{
|
||||
const char *end = Q_strchrnul( p, '\n' );
|
||||
const char *q;
|
||||
const size_t klen = sizeof( XCF_KEY ) - 1;
|
||||
|
||||
q = SkipWhitespace( p, end );
|
||||
|
||||
if( q < end && *q == '#' )
|
||||
{
|
||||
q = SkipWhitespace( q + 1, end );
|
||||
|
||||
if( end - q >= klen && memcmp( q, XCF_KEY, klen ) == 0 )
|
||||
{
|
||||
q = SkipWhitespace( q + klen, end );
|
||||
|
||||
if( q < end && *q == ':' )
|
||||
{
|
||||
q = SkipWhitespace( q + 1, end );
|
||||
|
||||
if( end - q >= 4 && memcmp( q, "true", 4 ) == 0 )
|
||||
cfg->collapse_parens = true;
|
||||
else if( end - q >= 5 && memcmp( q, "false", 5 ) == 0 )
|
||||
cfg->collapse_parens = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !*end )
|
||||
break;
|
||||
p = end + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
402
utils/xash-clang-format/main.c
Normal file
402
utils/xash-clang-format/main.c
Normal file
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
main.c - xash-clang-format wrapper entry point
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "crtlib.h"
|
||||
#include "xcf.h"
|
||||
|
||||
#ifndef XCF_CLANG_FORMAT_DEFAULT
|
||||
#define XCF_CLANG_FORMAT_DEFAULT "clang-format"
|
||||
#endif
|
||||
|
||||
// TODO: move to custom config keys
|
||||
#define XCF_MIN_CLANG_FORMAT_MAJOR 17
|
||||
|
||||
static const char *g_clang_format = NULL;
|
||||
|
||||
/*
|
||||
============
|
||||
ResolveClangFormat
|
||||
============
|
||||
*/
|
||||
static const char *ResolveClangFormat( void )
|
||||
{
|
||||
const char *e = getenv( "XASH_CLANG_FORMAT_BIN" );
|
||||
|
||||
if( !COM_StringEmptyOrNULL( e ))
|
||||
return e;
|
||||
|
||||
return XCF_CLANG_FORMAT_DEFAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
IsFileArg
|
||||
|
||||
Attempt to distinguish files from other args.
|
||||
But to keep parsing args simple, it will parse value as --key value as
|
||||
filename. :(
|
||||
============
|
||||
*/
|
||||
static qboolean IsFileArg( const char *a )
|
||||
{
|
||||
if( COM_StringEmptyOrNULL( a ))
|
||||
return false;
|
||||
if( a[0] == '-' )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
ReadTheFile
|
||||
|
||||
============
|
||||
*/
|
||||
static qboolean ReadTheFile( const char *path, xcf_buf_t *out )
|
||||
{
|
||||
FILE *f = fopen( path, "rb" );
|
||||
|
||||
if( !f )
|
||||
return false;
|
||||
|
||||
char buf[8192];
|
||||
size_t n;
|
||||
|
||||
while(( n = fread( buf, 1, sizeof( buf ), f )) > 0 )
|
||||
{
|
||||
if( !BufPutMem( out, buf, n ))
|
||||
{
|
||||
fclose( f );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fclose( f );
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
WriteTheFile
|
||||
|
||||
============
|
||||
*/
|
||||
static qboolean WriteTheFile( const char *path, const char *data, size_t len )
|
||||
{
|
||||
FILE *f = fopen( path, "wb" );
|
||||
|
||||
if( !f )
|
||||
return false;
|
||||
if( fwrite( data, 1, len, f ) != len )
|
||||
{
|
||||
fclose( f );
|
||||
return false;
|
||||
}
|
||||
if( fclose( f ) != 0 )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
FindConfig
|
||||
============
|
||||
*/
|
||||
static char *FindConfig( const char *startdir )
|
||||
{
|
||||
char dir[MAX_SYSPATH];
|
||||
|
||||
if( startdir )
|
||||
{
|
||||
Q_strncpy( dir, startdir, sizeof( dir ));
|
||||
}
|
||||
else if( !getcwd( dir, sizeof( dir )))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
char path[MAX_SYSPATH];
|
||||
struct stat st;
|
||||
char *slash;
|
||||
|
||||
Q_snprintf( path, sizeof( path ), "%s/.clang-format", dir );
|
||||
|
||||
if( stat( path, &st ) == 0 && S_ISREG( st.st_mode ))
|
||||
{
|
||||
size_t len = Q_strlen( path ) + 1;
|
||||
char *r = malloc( len );
|
||||
|
||||
if( r )
|
||||
Q_strncpy( r, path, len );
|
||||
return r;
|
||||
}
|
||||
|
||||
slash = Q_strrchr( dir, '/' );
|
||||
if( !slash || slash == dir )
|
||||
break;
|
||||
*slash = 0;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
LoadConfig
|
||||
============
|
||||
*/
|
||||
static qboolean LoadConfig( const char *path, xcf_config_t *cfg )
|
||||
{
|
||||
xcf_buf_t buf = { 0 };
|
||||
qboolean ok = false;
|
||||
|
||||
if( path && ReadTheFile( path, &buf ) && BufPutChar( &buf, 0 ))
|
||||
ok = ParseConfigText( buf.data, cfg );
|
||||
else
|
||||
ParseConfigText( "", cfg );
|
||||
|
||||
BufFree( &buf );
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
Emit
|
||||
|
||||
Transform pass on buffer and output to file
|
||||
============
|
||||
*/
|
||||
static qboolean Emit( const xcf_config_t *cfg, const xcf_buf_t *raw, FILE *dst, const char *write_path )
|
||||
{
|
||||
xcf_buf_t out = { 0 };
|
||||
const char *data;
|
||||
size_t len;
|
||||
if( cfg->collapse_parens && Transform( raw->data, raw->len, &out ))
|
||||
{
|
||||
data = out.data;
|
||||
len = out.len;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = raw->data;
|
||||
len = raw->len;
|
||||
}
|
||||
|
||||
qboolean ok = true;
|
||||
if( write_path )
|
||||
{
|
||||
if( !WriteTheFile( write_path, data, len ))
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: cannot write %s: %s\n", write_path, strerror( errno ));
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
else if( len )
|
||||
{
|
||||
if( fwrite( data, 1, len, dst ) != len )
|
||||
ok = false;
|
||||
}
|
||||
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
PipeMode
|
||||
|
||||
Process stdout (when clang-format called without -i)
|
||||
============
|
||||
*/
|
||||
static int PipeMode( int argc, char **argv, const xcf_config_t *cfg )
|
||||
{
|
||||
char **fwd = malloc( sizeof( char * ) * ( argc + 1 ));
|
||||
if( !fwd )
|
||||
return 1;
|
||||
|
||||
fwd[0] = (char *)g_clang_format;
|
||||
for( int i = 1; i < argc; i++ )
|
||||
fwd[i] = argv[i];
|
||||
fwd[argc] = NULL;
|
||||
|
||||
xcf_buf_t raw = { 0 };
|
||||
int rc = RunCapture( fwd, &raw, false );
|
||||
free( fwd );
|
||||
|
||||
if( rc != 0 )
|
||||
{
|
||||
// pass through whatever clang-format produced
|
||||
if( raw.len )
|
||||
fwrite( raw.data, 1, raw.len, stdout );
|
||||
BufFree( &raw );
|
||||
return rc < 0 ? 1 : rc;
|
||||
}
|
||||
|
||||
rc = Emit( cfg, &raw, stdout, NULL ) ? 0 : 1;
|
||||
BufFree( &raw );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
PostProcessFile
|
||||
|
||||
Read `path`, run the paren-collapse pass, write result back.
|
||||
============
|
||||
*/
|
||||
static qboolean PostProcessFile( const char *path )
|
||||
{
|
||||
xcf_buf_t in = { 0 };
|
||||
xcf_buf_t out = { 0 };
|
||||
qboolean ok = false;
|
||||
|
||||
if( ReadTheFile( path, &in ) && Transform( in.data, in.len, &out ))
|
||||
ok = WriteTheFile( path, out.data, out.len );
|
||||
|
||||
BufFree( &in );
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
InPlaceMode
|
||||
|
||||
Post-process mode, when clang-format is called with -i
|
||||
============
|
||||
*/
|
||||
static int InPlaceMode( int argc, char **argv, const xcf_config_t *cfg )
|
||||
{
|
||||
char **fwd = malloc( sizeof( char * ) * ( argc + 1 ));
|
||||
|
||||
if( !fwd )
|
||||
return 1;
|
||||
|
||||
fwd[0] = (char *)g_clang_format;
|
||||
for( int i = 1; i < argc; i++ )
|
||||
fwd[i] = argv[i];
|
||||
fwd[argc] = NULL;
|
||||
|
||||
xcf_buf_t sink = { 0 };
|
||||
int rc = RunCapture( fwd, &sink, false );
|
||||
|
||||
BufFree( &sink );
|
||||
free( fwd );
|
||||
|
||||
if( rc != 0 )
|
||||
return rc < 0 ? 1 : rc;
|
||||
|
||||
if( !cfg->collapse_parens )
|
||||
return 0;
|
||||
|
||||
for( int i = 1; i < argc; i++ )
|
||||
{
|
||||
if( !IsFileArg( argv[i] ))
|
||||
continue;
|
||||
if( !PostProcessFile( argv[i] ))
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: post-process failed for %s\n", argv[i] );
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void Usage( FILE *dst )
|
||||
{
|
||||
fprintf( dst,
|
||||
"xash-clang-format: clang-format wrapper with xash3d-fwgs extensions\n"
|
||||
"\n"
|
||||
"Usage: xash-clang-format [clang-format options] [<file> ...]\n"
|
||||
" xash-clang-format -i <file> ...\n"
|
||||
"\n"
|
||||
"Most clang-format flags are forwarded as-is but there are limitations:\n"
|
||||
"* --key value must be passed as --key=value\n"
|
||||
"\n"
|
||||
"Use XASH_CLANG_FORMAT_BIN envvar to override the clang-format binary;\n"
|
||||
" setting it also skips the version probe.\n"
|
||||
"\n"
|
||||
"Wrapper's config is shared with clang-format config, but extended keys\n"
|
||||
" start with comment hash.\n"
|
||||
"New keys:\n"
|
||||
" XashCollapseParens, accepts 'true' or 'false'\n" );
|
||||
}
|
||||
|
||||
int main( int argc, char **argv )
|
||||
{
|
||||
g_clang_format = ResolveClangFormat();
|
||||
|
||||
qboolean has_inplace = false;
|
||||
qboolean has_version = false;
|
||||
qboolean has_help = false;
|
||||
|
||||
for( int i = 1; i < argc; i++ )
|
||||
{
|
||||
if( Q_strcmp( argv[i], "-i" ) == 0 )
|
||||
has_inplace = true;
|
||||
else if( Q_strcmp( argv[i], "--version" ) == 0 || Q_strcmp( argv[i], "-version" ) == 0 )
|
||||
has_version = true;
|
||||
else if( Q_strcmp( argv[i], "--help" ) == 0 || Q_strcmp( argv[i], "-h" ) == 0 || Q_strcmp( argv[i], "-help" ) == 0 )
|
||||
has_help = true;
|
||||
}
|
||||
|
||||
if( has_help )
|
||||
{
|
||||
Usage( stdout );
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( has_version )
|
||||
{
|
||||
// transparent passthrough so tools that scrape `clang-format --version`
|
||||
// (e.g. git clang-format auto-detection) keep working
|
||||
execlp( g_clang_format, g_clang_format, "--version", (char *)NULL );
|
||||
fprintf( stderr, "xash-clang-format: cannot exec %s: %s\n", g_clang_format, strerror( errno ));
|
||||
return 127;
|
||||
}
|
||||
|
||||
// if caller sets XASH_CLANG_FORMAT_BIN — they own version compatibility
|
||||
// so skip the probe (lets ./waf format do the check once for parallel invocation)
|
||||
if( COM_StringEmptyOrNULL( getenv( "XASH_CLANG_FORMAT_BIN" )))
|
||||
{
|
||||
int major = 0;
|
||||
|
||||
if( !ClangFormatVersion( g_clang_format, &major ))
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: cannot probe clang-format version (binary: %s)\n", g_clang_format );
|
||||
return 2;
|
||||
}
|
||||
if( major < XCF_MIN_CLANG_FORMAT_MAJOR )
|
||||
{
|
||||
fprintf( stderr, "xash-clang-format: clang-format %d found, need >= %d\n",
|
||||
major, XCF_MIN_CLANG_FORMAT_MAJOR );
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
xcf_config_t cfg;
|
||||
char *cfgpath = FindConfig( NULL );
|
||||
LoadConfig( cfgpath, &cfg );
|
||||
free( cfgpath );
|
||||
|
||||
return has_inplace ? InPlaceMode( argc, argv, &cfg ) : PipeMode( argc, argv, &cfg );
|
||||
}
|
||||
99
utils/xash-clang-format/proc.c
Normal file
99
utils/xash-clang-format/proc.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
proc.c - subprocess utilities
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include "xcf.h"
|
||||
|
||||
/*
|
||||
============
|
||||
RunCapture
|
||||
|
||||
Spawn argv as a child, capture stdout into `out`.
|
||||
============
|
||||
*/
|
||||
int RunCapture( char *const argv[], xcf_buf_t *out, qboolean silent_stderr )
|
||||
{
|
||||
int p[2];
|
||||
pid_t pid;
|
||||
char buf[8192];
|
||||
int status;
|
||||
|
||||
if( pipe( p ) < 0 )
|
||||
return -1;
|
||||
|
||||
pid = fork();
|
||||
if( pid < 0 )
|
||||
{
|
||||
close( p[0] );
|
||||
close( p[1] );
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( pid == 0 )
|
||||
{
|
||||
close( p[0] );
|
||||
dup2( p[1], STDOUT_FILENO );
|
||||
close( p[1] );
|
||||
if( silent_stderr )
|
||||
{
|
||||
int devnull = open( "/dev/null", O_WRONLY );
|
||||
|
||||
if( devnull >= 0 )
|
||||
{
|
||||
dup2( devnull, STDERR_FILENO );
|
||||
close( devnull );
|
||||
}
|
||||
}
|
||||
execvp( argv[0], argv );
|
||||
_exit( 127 );
|
||||
}
|
||||
|
||||
close( p[1] );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
ssize_t r = read( p[0], buf, sizeof( buf ));
|
||||
|
||||
if( r < 0 )
|
||||
{
|
||||
if( errno == EINTR )
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if( r == 0 )
|
||||
break;
|
||||
if( !BufPutMem( out, buf, r ))
|
||||
{
|
||||
close( p[0] );
|
||||
kill( pid, SIGKILL );
|
||||
waitpid( pid, NULL, 0 );
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
close( p[0] );
|
||||
|
||||
if( waitpid( pid, &status, 0 ) < 0 )
|
||||
return -1;
|
||||
|
||||
// heh wife excited
|
||||
if( WIFEXITED( status ))
|
||||
return WEXITSTATUS( status );
|
||||
|
||||
return -1;
|
||||
}
|
||||
77
utils/xash-clang-format/tests/test_config.c
Normal file
77
utils/xash-clang-format/tests/test_config.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <stdio.h>
|
||||
#include "xcf.h"
|
||||
|
||||
static int run_case( const char *label, const char *yaml, qboolean expect )
|
||||
{
|
||||
xcf_config_t cfg;
|
||||
|
||||
ParseConfigText( yaml, &cfg );
|
||||
|
||||
if( cfg.collapse_parens != expect )
|
||||
{
|
||||
fprintf( stderr, "[%s] collapse_parens=%d, expected %d\n yaml:\n%s\n",
|
||||
label, cfg.collapse_parens, expect, yaml );
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main( void )
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if( !run_case( "empty", "", false ))
|
||||
return ++n;
|
||||
|
||||
xcf_config_t cfg2;
|
||||
|
||||
ParseConfigText( NULL, &cfg2 );
|
||||
if( cfg2.collapse_parens )
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "explicit true",
|
||||
"# XashCollapseParens: true\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "explicit false",
|
||||
"# XashCollapseParens: false\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "leading ws",
|
||||
" # XashCollapseParens: true\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "tabs",
|
||||
"#\tXashCollapseParens\t:\ttrue\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "unrelated key",
|
||||
"# SomeOtherKey: true\nIndentWidth: 4\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "in larger config",
|
||||
"---\n"
|
||||
"Language: Cpp\n"
|
||||
"# XashCollapseParens: true\n"
|
||||
"IndentWidth: 4\n",
|
||||
true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "junk value",
|
||||
"# XashCollapseParens: maybe\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "no colon",
|
||||
"# XashCollapseParens true\n", false ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "last wins",
|
||||
"# XashCollapseParens: false\n# XashCollapseParens: true\n", true ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "no trailing nl",
|
||||
"# XashCollapseParens: true", true ))
|
||||
return ++n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
171
utils/xash-clang-format/tests/test_transform.c
Normal file
171
utils/xash-clang-format/tests/test_transform.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "xcf.h"
|
||||
|
||||
static int run_case( const char *label, const char *in, const char *expect )
|
||||
{
|
||||
xcf_buf_t out = { 0 };
|
||||
int ok;
|
||||
|
||||
if( !Transform( in, strlen( in ), &out ))
|
||||
{
|
||||
fprintf( stderr, "[%s] Transform returned error\n", label );
|
||||
BufFree( &out );
|
||||
return 0;
|
||||
}
|
||||
|
||||
ok = ( out.len == strlen( expect ) && memcmp( out.data, expect, out.len ) == 0 );
|
||||
|
||||
if( !ok )
|
||||
{
|
||||
fprintf( stderr, "[%s] mismatch\n in: %s\n out: %.*s\n expect: %s\n",
|
||||
label, in, (int)out.len, out.data, expect );
|
||||
}
|
||||
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main( void )
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if( !run_case( "outer parens",
|
||||
"if( ( a + b ) )\n",
|
||||
"if(( a + b ))\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "triple nesting",
|
||||
"x = ( ( ( a ) ) );\n",
|
||||
"x = ((( a )));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "mixed [ )",
|
||||
"arr[i] )\n",
|
||||
"arr[i] )\n" ))
|
||||
return ++n;
|
||||
if( !run_case( "mixed ) ]",
|
||||
"f( a ) ];\n",
|
||||
"f( a ) ];\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "newline boundary",
|
||||
"(\n(\n",
|
||||
"(\n(\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "string literal",
|
||||
"x = \"( ( hello ) )\";\n",
|
||||
"x = \"( ( hello ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "escaped quote in string",
|
||||
"x = \"a \\\" ( ( b ) ) \";\n",
|
||||
"x = \"a \\\" ( ( b ) ) \";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "line comment",
|
||||
"a; // ( ( comment ) )\nb;\n",
|
||||
"a; // ( ( comment ) )\nb;\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "block comment",
|
||||
"/* ( ( leave\nthis ) ) alone */ x;\n",
|
||||
"/* ( ( leave\nthis ) ) alone */ x;\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "char literal '('",
|
||||
"if( c == '(' )\n",
|
||||
"if( c == '(' )\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "raw string",
|
||||
"const char *s = R\"x(( (\nstays )x\";\n",
|
||||
"const char *s = R\"x(( (\nstays )x\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "raw string empty delim",
|
||||
"R\"(( (\n))\"",
|
||||
"R\"(( (\n))\"" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "ident Rabbit not raw",
|
||||
"int Rabbit = ( ( 1 ) );\n",
|
||||
"int Rabbit = (( 1 ));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u8R raw",
|
||||
"const auto s = u8R\"(( ( )\";\n",
|
||||
"const auto s = u8R\"(( ( )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "include angle",
|
||||
"#include <foo.h>\nf( ( x ) );\n",
|
||||
"#include <foo.h>\nf(( x ));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "tabs between parens",
|
||||
"f(\t(\tx\t)\t);\n",
|
||||
"f((\tx\t));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "empty", "", "" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "idempotent",
|
||||
"f((( x )));\n",
|
||||
"f((( x )));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "already touching",
|
||||
"((a))",
|
||||
"((a))" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "single paren",
|
||||
"f( a, b )",
|
||||
"f( a, b )" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "multiple pairs",
|
||||
"f( ( a ) ) + g( ( b ) );\n",
|
||||
"f(( a )) + g(( b ));\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "L wide string",
|
||||
"wchar_t *s = L\"( ( x ) )\";\n",
|
||||
"wchar_t *s = L\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u utf16 string",
|
||||
"auto s = u\"( ( x ) )\";\n",
|
||||
"auto s = u\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "U utf32 string",
|
||||
"auto s = U\"( ( x ) )\";\n",
|
||||
"auto s = U\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u8 utf8 string",
|
||||
"auto s = u8\"( ( x ) )\";\n",
|
||||
"auto s = u8\"( ( x ) )\";\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "L wide char",
|
||||
"if( c == L'(' )\n",
|
||||
"if( c == L'(' )\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "u8 char",
|
||||
"if( c == u8'(' )\n",
|
||||
"if( c == u8'(' )\n" ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "ident L not prefix",
|
||||
"int callL = ( ( 1 ) );\n",
|
||||
"int callL = (( 1 ));\n" ))
|
||||
return ++n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
58
utils/xash-clang-format/tests/test_version.c
Normal file
58
utils/xash-clang-format/tests/test_version.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <stdio.h>
|
||||
#include "xcf.h"
|
||||
|
||||
static int run_case( const char *label, const char *s, qboolean expect_ok, int expect_major )
|
||||
{
|
||||
int major = -1;
|
||||
qboolean ok = ParseVersion( s, &major );
|
||||
|
||||
if( ok != expect_ok )
|
||||
{
|
||||
fprintf( stderr, "[%s] ok=%d expected %d (input: %s)\n", label, ok, expect_ok, s ? s : "(null)" );
|
||||
return 0;
|
||||
}
|
||||
if( ok && major != expect_major )
|
||||
{
|
||||
fprintf( stderr, "[%s] major=%d expected %d (input: %s)\n", label, major, expect_major, s );
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main( void )
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if( !run_case( "vanilla 17",
|
||||
"clang-format version 17.0.6 (Fedora 17.0.6-1.fc40)\n", true, 17 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "ubuntu 18",
|
||||
"Ubuntu clang-format version 18.1.3 (1ubuntu1)\n", true, 18 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "large major",
|
||||
"clang-format version 100.0.0\n", true, 100 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "garbage",
|
||||
"this has no version here\n", false, 0 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "null", NULL, false, 0 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "distro tag with digits",
|
||||
"Ubuntu99 clang-format version 17.0.6\n", true, 17 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "bare digits",
|
||||
"foo 42 bar\n", false, 0 ))
|
||||
return ++n;
|
||||
|
||||
if( !run_case( "no version word",
|
||||
"fake clang-format 19.1.0\n", false, 0 ))
|
||||
return ++n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
325
utils/xash-clang-format/transform.c
Normal file
325
utils/xash-clang-format/transform.c
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
transform.c - source post-processing pass
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "xcf.h"
|
||||
|
||||
#define XCF_RAW_DELIM_MAX 16 // C++11: 16 chars max in raw-string delimiter
|
||||
|
||||
/*
|
||||
============
|
||||
BufFree
|
||||
|
||||
============
|
||||
*/
|
||||
void BufFree( xcf_buf_t *b )
|
||||
{
|
||||
free( b->data );
|
||||
b->data = NULL;
|
||||
b->len = b->cap = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
BufGrow
|
||||
|
||||
============
|
||||
*/
|
||||
static qboolean BufGrow( xcf_buf_t *b, size_t need )
|
||||
{
|
||||
size_t cap;
|
||||
char *p;
|
||||
|
||||
if( b->len + need <= b->cap )
|
||||
return true;
|
||||
|
||||
cap = b->cap ? b->cap : 256;
|
||||
while( cap < b->len + need )
|
||||
cap *= 2;
|
||||
|
||||
p = realloc( b->data, cap );
|
||||
if( !p )
|
||||
return false;
|
||||
|
||||
b->data = p;
|
||||
b->cap = cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
BufPutChar
|
||||
|
||||
============
|
||||
*/
|
||||
qboolean BufPutChar( xcf_buf_t *b, char c )
|
||||
{
|
||||
if( !BufGrow( b, 1 ))
|
||||
return false;
|
||||
b->data[b->len++] = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
BufPutMem
|
||||
|
||||
============
|
||||
*/
|
||||
qboolean BufPutMem( xcf_buf_t *b, const char *src, size_t n )
|
||||
{
|
||||
if( !n )
|
||||
return true;
|
||||
if( !BufGrow( b, n ))
|
||||
return false;
|
||||
memcpy( b->data + b->len, src, n );
|
||||
b->len += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
TryCollapse
|
||||
|
||||
Collapse spaces between the same type parentheses
|
||||
============
|
||||
*/
|
||||
static void TryCollapse( xcf_buf_t *out, char paren )
|
||||
{
|
||||
size_t i = out->len;
|
||||
|
||||
while( i > 0 && ( out->data[i - 1] == ' ' || out->data[i - 1] == '\t' ))
|
||||
i--;
|
||||
|
||||
if( i == 0 || i == out->len )
|
||||
return; // nothing to collapse, or no whitespace between
|
||||
|
||||
if( out->data[i - 1] != paren )
|
||||
return; // different bracket types or different char entirely
|
||||
|
||||
out->len = i;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
DetectRaw
|
||||
|
||||
Detect C++11 raw string because why not
|
||||
============
|
||||
*/
|
||||
static qboolean DetectRaw( const char *s, size_t len, size_t i, size_t *prefix_end, size_t *paren )
|
||||
{
|
||||
size_t j = i, k;
|
||||
|
||||
// must not be in the middle of an identifier
|
||||
if( i > 0 && ( isalnum( s[i - 1] ) || s[i - 1] == '_' ))
|
||||
return false;
|
||||
|
||||
if( j + 2 < len && s[j] == 'u' && s[j + 1] == '8' && s[j + 2] == 'R' )
|
||||
j += 3;
|
||||
else if( j + 1 < len && ( s[j] == 'u' || s[j] == 'U' || s[j] == 'L' ) && s[j + 1] == 'R' )
|
||||
j += 2;
|
||||
else if( j < len && s[j] == 'R' )
|
||||
j += 1;
|
||||
else
|
||||
return false;
|
||||
|
||||
if( j >= len || s[j] != '"' )
|
||||
return false;
|
||||
j++; // past the opening quote
|
||||
*prefix_end = j;
|
||||
|
||||
k = j;
|
||||
while( k < len && s[k] != '(' && k - j < XCF_RAW_DELIM_MAX )
|
||||
k++;
|
||||
|
||||
if( k >= len || s[k] != '(' )
|
||||
return false;
|
||||
|
||||
*paren = k;
|
||||
return true;
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
S_NORMAL,
|
||||
S_LINE_CMT,
|
||||
S_BLOCK_CMT,
|
||||
S_STRING,
|
||||
S_CHAR,
|
||||
S_RAW,
|
||||
};
|
||||
|
||||
/*
|
||||
============
|
||||
Transform
|
||||
|
||||
Parse source, find parens and try collapse them
|
||||
============
|
||||
*/
|
||||
qboolean Transform( const char *in, size_t in_len, xcf_buf_t *out )
|
||||
{
|
||||
size_t raw_dlen = 0;
|
||||
char raw_delim[XCF_RAW_DELIM_MAX + 1];
|
||||
int state = S_NORMAL;
|
||||
|
||||
for( size_t i = 0; i < in_len; i++ )
|
||||
{
|
||||
char c1 = in[i];
|
||||
char c2 = ( i + 1 < in_len ) ? in[i + 1] : 0;
|
||||
|
||||
switch( state )
|
||||
{
|
||||
case S_NORMAL:
|
||||
{
|
||||
size_t pe, pp;
|
||||
|
||||
if( c1 == '/' && c2 == '/' )
|
||||
{
|
||||
if( !BufPutMem( out, in + i, 2 ))
|
||||
return false;
|
||||
i++;
|
||||
state = S_LINE_CMT;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '/' && c2 == '*' )
|
||||
{
|
||||
if( !BufPutMem( out, in + i, 2 ))
|
||||
return false;
|
||||
i++;
|
||||
state = S_BLOCK_CMT;
|
||||
break;
|
||||
}
|
||||
|
||||
if( DetectRaw( in, in_len, i, &pe, &pp ))
|
||||
{
|
||||
size_t dl = pp - pe;
|
||||
|
||||
if( dl > XCF_RAW_DELIM_MAX )
|
||||
dl = XCF_RAW_DELIM_MAX;
|
||||
|
||||
if( !BufPutMem( out, in + i, pp - i + 1 ))
|
||||
return false;
|
||||
memcpy( raw_delim, in + pe, dl );
|
||||
raw_delim[dl] = 0;
|
||||
raw_dlen = dl;
|
||||
i = pp; // sit on the '('
|
||||
state = S_RAW;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '"' )
|
||||
{
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
state = S_STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '\'' )
|
||||
{
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
state = S_CHAR;
|
||||
break;
|
||||
}
|
||||
|
||||
if( c1 == '(' || c1 == ')' )
|
||||
TryCollapse( out, c1 );
|
||||
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
case S_LINE_CMT:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '\n' )
|
||||
state = S_NORMAL;
|
||||
break;
|
||||
|
||||
case S_BLOCK_CMT:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '*' && c2 == '/' )
|
||||
{
|
||||
if( !BufPutChar( out, c2 ))
|
||||
return false;
|
||||
i++;
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_STRING:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '\\' && c2 )
|
||||
{
|
||||
if( !BufPutChar( out, c2 ))
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
else if( c1 == '"' )
|
||||
{
|
||||
state = S_NORMAL;
|
||||
}
|
||||
else if( c1 == '\n' )
|
||||
{
|
||||
// unterminated; recover at next line
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_CHAR:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == '\\' && c2 )
|
||||
{
|
||||
if( !BufPutChar( out, c2 ))
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
else if( c1 == '\'' )
|
||||
{
|
||||
state = S_NORMAL;
|
||||
}
|
||||
else if( c1 == '\n' )
|
||||
{
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_RAW:
|
||||
if( !BufPutChar( out, c1 ))
|
||||
return false;
|
||||
if( c1 == ')'
|
||||
&& i + 1 + raw_dlen + 1 <= in_len
|
||||
&& memcmp( in + i + 1, raw_delim, raw_dlen ) == 0
|
||||
&& in[i + 1 + raw_dlen] == '"' )
|
||||
{
|
||||
if( !BufPutMem( out, in + i + 1, raw_dlen + 1 ))
|
||||
return false;
|
||||
i += raw_dlen + 1;
|
||||
state = S_NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
67
utils/xash-clang-format/version.c
Normal file
67
utils/xash-clang-format/version.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
version.c - clang-format version probe
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#include "crtlib.h"
|
||||
#include "xcf.h"
|
||||
|
||||
/*
|
||||
============
|
||||
ParseVersion
|
||||
|
||||
Parse the major version number out of a `clang-format --version`
|
||||
============
|
||||
*/
|
||||
qboolean ParseVersion( const char *s, int *major )
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if( COM_StringEmptyOrNULL( s ) || !major )
|
||||
return false;
|
||||
|
||||
// anchor on "version "
|
||||
p = Q_strstr( s, "version " );
|
||||
if( !p )
|
||||
return false;
|
||||
p += sizeof( "version " ) - 1;
|
||||
|
||||
while( *p == ' ' || *p == '\t' )
|
||||
p++;
|
||||
if( *p < '0' || *p > '9' )
|
||||
return false;
|
||||
|
||||
*major = Q_atoi( p );
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
ClangFormatVersion
|
||||
|
||||
============
|
||||
*/
|
||||
qboolean ClangFormatVersion( const char *exe, int *major )
|
||||
{
|
||||
char *argv[] = { (char *)exe, (char *)"--version", NULL };
|
||||
xcf_buf_t out = { 0 };
|
||||
qboolean ok = false;
|
||||
|
||||
if( !exe || !major )
|
||||
return false;
|
||||
|
||||
if( RunCapture( argv, &out, true ) == 0 && BufPutChar( &out, 0 ))
|
||||
ok = ParseVersion( out.data, major );
|
||||
|
||||
BufFree( &out );
|
||||
return ok;
|
||||
}
|
||||
42
utils/xash-clang-format/wscript
Normal file
42
utils/xash-clang-format/wscript
Normal file
@@ -0,0 +1,42 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
grp = opt.get_option_group('Utilities options')
|
||||
|
||||
grp.add_option('--disable-utils-xcf', action='store_true',
|
||||
dest='DISABLE_UTILS_XCF', default = False,
|
||||
help='disable xash-clang-format wrapper utility [default: %(default)s]')
|
||||
|
||||
def configure(conf):
|
||||
# wrapper relies on POSIX, disable on Windows for now
|
||||
conf.env.DISABLE_UTILS_XCF = conf.options.DISABLE_UTILS_XCF or conf.env.DEST_OS == 'win32'
|
||||
|
||||
def build(bld):
|
||||
if bld.env.DISABLE_UTILS_XCF:
|
||||
return
|
||||
|
||||
bld.stlib(source = bld.path.ant_glob(['*.c', '*.h'], excl='main.c'),
|
||||
target = 'xcflib',
|
||||
features = 'format',
|
||||
includes = '.',
|
||||
use = 'public werror',
|
||||
install_path = None)
|
||||
|
||||
bld.program(source = 'main.c',
|
||||
target = 'xash-clang-format',
|
||||
features = 'format',
|
||||
use = 'xcflib werror',
|
||||
install_path = bld.env.BINDIR,
|
||||
subsystem = bld.env.CONSOLE_SUBSYSTEM)
|
||||
|
||||
if bld.env.TESTS:
|
||||
tests = ['transform', 'config', 'version']
|
||||
|
||||
for name in tests:
|
||||
bld.program(features = 'test format',
|
||||
source = 'tests/test_%s.c' % name,
|
||||
target = 'test_xcflib_%s' % name,
|
||||
includes = '.',
|
||||
use = 'xcflib werror',
|
||||
install_path = None)
|
||||
57
utils/xash-clang-format/xcf.h
Normal file
57
utils/xash-clang-format/xcf.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
xcf.h - clang-format wrapper internals
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef XCF_H
|
||||
#define XCF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "xash3d_types.h"
|
||||
|
||||
//
|
||||
// transform.c
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
char *data;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} xcf_buf_t;
|
||||
|
||||
void BufFree( xcf_buf_t *b );
|
||||
qboolean BufPutChar( xcf_buf_t *b, char c );
|
||||
qboolean BufPutMem( xcf_buf_t *b, const char *src, size_t n );
|
||||
qboolean Transform( const char *in, size_t in_len, xcf_buf_t *out );
|
||||
|
||||
//
|
||||
// config.c
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
qboolean collapse_parens; // default: false
|
||||
} xcf_config_t;
|
||||
|
||||
qboolean ParseConfigText( const char *text, xcf_config_t *cfg );
|
||||
|
||||
//
|
||||
// version.c
|
||||
//
|
||||
qboolean ParseVersion( const char *s, int *major );
|
||||
qboolean ClangFormatVersion( const char *exe, int *major );
|
||||
|
||||
//
|
||||
// proc.c
|
||||
//
|
||||
int RunCapture( char *const argv[], xcf_buf_t *out, qboolean silent_stderr );
|
||||
|
||||
#endif // XCF_H
|
||||
Reference in New Issue
Block a user