char* path_prepend( char* tail, const char* base ) { if( !tail ) return path_clean( string_clone( base ), path_is_absolute( base ) ); tail = string_prepend( tail, "/" ); tail = string_prepend( tail, base ); tail = path_clean( tail, path_is_absolute( tail ) ); return tail; }
void test_prepend() { Value hello, there; set_string(&hello, "hello"); set_string(&there, "there"); test_equals(&hello, "hello"); test_equals(&there, "there"); string_prepend(&there, " "); test_equals(&there, " there"); string_prepend(&there, &hello); string_prepend(&there, "hello there"); test_equals(&hello, "hello"); }
static void test1() { String* a = string_create(""); String* b = string_create("Hello"); String* c = string_create("World"); printf("a = \"%s\"\n", a->text); printf("b = \"%s\"\n", b->text); printf("c = \"%s\"\n", c->text); assert(strcmp(a->text, "") == 0); assert(strcmp(b->text, "Hello") == 0); assert(strcmp(c->text, "World") == 0); string_append(a, b->text); string_append(b, c->text); string_append(c, a->text); printf("a = a+b = \"%s\"\n", a->text); printf("b = b+c = \"%s\"\n", b->text); printf("c = c+a = \"%s\"\n", c->text); assert(strcmp(a->text, "Hello") == 0); assert(strcmp(b->text, "HelloWorld") == 0); assert(strcmp(c->text, "WorldHello") == 0); string_prepend(a, b->text); string_prepend(b, c->text); string_prepend(c, a->text); printf("a = b+a = \"%s\"\n", a->text); printf("b = c+b = \"%s\"\n", b->text); printf("c = a+c = \"%s\"\n", c->text); assert(strcmp(a->text, "HelloWorldHello") == 0); assert(strcmp(b->text, "WorldHelloHelloWorld") == 0); assert(strcmp(c->text, "HelloWorldHelloWorldHello") == 0); string_destroy(a); string_destroy(b); string_destroy(c); }
static void windows_cache_locale(void) { if (s_datetime_locale != NULL) return; s_datetime_locale = new MCDateTimeLocale; // OK-2007-05-23: Fix for bug 5035. Adjusted to ensure that first element of weekday names is always Sunday. s_datetime_locale -> weekday_names[0] = windows_query_locale(LOCALE_SDAYNAME7); s_datetime_locale -> abbrev_weekday_names[0] = windows_query_locale(LOCALE_SABBREVDAYNAME7); for (uint4 t_index = 0; t_index < 6; ++t_index) { s_datetime_locale -> weekday_names[t_index + 1] = windows_query_locale(LOCALE_SDAYNAME1 + t_index); s_datetime_locale -> abbrev_weekday_names[t_index + 1] = windows_query_locale(LOCALE_SABBREVDAYNAME1 + t_index); } for(uint4 t_index = 0; t_index < 12; ++t_index) { s_datetime_locale -> month_names[t_index] = windows_query_locale(LOCALE_SMONTHNAME1 + t_index); s_datetime_locale -> abbrev_month_names[t_index] = windows_query_locale(LOCALE_SABBREVMONTHNAME1 + t_index); } s_datetime_locale -> date_formats[0] = string_prepend(windows_query_date_format(LOCALE_SSHORTDATE, false), '^'); s_datetime_locale -> date_formats[1] = windows_query_date_format(LOCALE_SLONGDATE, true); s_datetime_locale -> date_formats[2] = windows_query_date_format(LOCALE_SLONGDATE, false); // AL-2013-02-08: [[ Bug 9942 ]] Allow appropriate versions of Windows to retrieve the short time format. if (MCmajorosversion >= 0x0601) s_datetime_locale -> time_formats[0] = string_prepend(windows_query_time_format(LOCALE_SSHORTTIME), '!'); else s_datetime_locale -> time_formats[0] = string_prepend(windows_query_time_format(LOCALE_STIMEFORMAT), '!'); s_datetime_locale -> time_formats[1] = string_prepend(windows_query_time_format(LOCALE_STIMEFORMAT), '!'); s_datetime_locale -> time24_formats[0] = string_prepend(windows_query_time_format(LOCALE_STIMEFORMAT), '!'); s_datetime_locale -> time24_formats[1] = string_prepend(windows_query_time_format(LOCALE_STIMEFORMAT), '!'); // AL-2013-02-08: [[ Bug 9945 ]] Retrieve locale-specific AM & PM designators. s_datetime_locale -> time_morning_suffix = windows_query_locale(LOCALE_S1159); s_datetime_locale -> time_evening_suffix = windows_query_locale(LOCALE_S2359); }
int process_spawn( process_t* proc ) { static char unescaped[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:/\\"; int i, num_args; int size; #if FOUNDATION_PLATFORM_WINDOWS wchar_t* wcmdline; wchar_t* wwd; char* cmdline = 0; #endif if( !proc ) return PROCESS_INVALID_ARGS; proc->code = PROCESS_INVALID_ARGS; if( !string_length( proc->path ) ) return proc->code; //Always escape path on Windows platforms #if FOUNDATION_PLATFORM_POSIX if( string_find_first_not_of( proc->path, unescaped, 0 ) != STRING_NPOS ) #endif { if( proc->path[0] != '"' ) proc->path = string_prepend( proc->path, "\"" ); if( proc->path[ string_length( proc->path ) - 1 ] != '"' ) proc->path = string_append( proc->path, "\"" ); } size = array_size( proc->args ); for( i = 0, num_args = 0; i < size; ++i ) { char* arg = proc->args[i]; if( !string_length( arg ) ) continue; ++num_args; if( string_find_first_not_of( arg, unescaped, 0 ) != STRING_NPOS ) { if( arg[0] != '"' ) { //Check if we need to escape " characters unsigned int pos = string_find( arg, '"', 0 ); while( pos != STRING_NPOS ) { if( arg[ pos - 1 ] != '\\' ) { char* escarg = string_substr( arg, 0, pos ); char* left = string_substr( arg, pos, STRING_NPOS ); escarg = string_append( escarg, "\\" ); escarg = string_append( escarg, left ); string_deallocate( left ); string_deallocate( arg ); arg = escarg; } pos = string_find( arg, '"', pos + 2 ); } arg = string_prepend( arg, "\"" ); arg = string_append( arg, "\"" ); proc->args[i] = arg; } } } #if FOUNDATION_PLATFORM_WINDOWS # ifndef SEE_MASK_NOASYNC # define SEE_MASK_NOASYNC 0x00000100 # endif if( !( proc->flags & PROCESS_WINDOWS_USE_SHELLEXECUTE ) ) //Don't prepend exe path to parameters if using ShellExecute cmdline = string_clone( proc->path ); //Build command line string for( i = 0; i < size; ++i ) { char* arg = proc->args[i]; if( !string_length( arg ) ) continue; if( cmdline ) cmdline = string_append( cmdline, " " ); cmdline = string_append( cmdline, arg ); } if( !string_length( proc->wd ) ) proc->wd = string_clone( environment_current_working_directory() ); wcmdline = wstring_allocate_from_string( cmdline, 0 ); wwd = wstring_allocate_from_string( proc->wd, 0 ); if( proc->flags & PROCESS_WINDOWS_USE_SHELLEXECUTE ) { SHELLEXECUTEINFOW sei; wchar_t* wverb; wchar_t* wpath; wverb = ( proc->verb && string_length( proc->verb ) ) ? wstring_allocate_from_string( proc->verb, 0 ) : 0; wpath = wstring_allocate_from_string( proc->path, 0 ); ZeroMemory( &sei, sizeof( sei ) ); sei.cbSize = sizeof(SHELLEXECUTEINFOW); sei.hwnd = 0; sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS; sei.lpVerb = wverb; sei.lpFile = wpath; sei.lpParameters = wcmdline; sei.lpDirectory = wwd; sei.nShow = SW_SHOWNORMAL; if( !( proc->flags & PROCESS_CONSOLE ) ) sei.fMask |= SEE_MASK_NO_CONSOLE; if( proc->flags & PROCESS_STDSTREAMS ) log_warnf( 0, WARNING_UNSUPPORTED, "Unable to redirect standard in/out through pipes when using ShellExecute for process spawning" ); log_debugf( 0, "Spawn process (ShellExecute): %s %s", proc->path, cmdline ); if( !ShellExecuteExW( &sei ) ) { log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to spawn process (ShellExecute) for executable '%s': %s", proc->path, system_error_message( GetLastError() ) ); } else { proc->hp = sei.hProcess; proc->ht = 0; proc->code = 0; } wstring_deallocate( wverb ); wstring_deallocate( wpath ); } else { STARTUPINFOW si; PROCESS_INFORMATION pi; BOOL inherit_handles = FALSE; memset( &si, 0, sizeof( si ) ); memset( &pi, 0, sizeof( pi ) ); si.cb = sizeof( si ); if( proc->flags & PROCESS_STDSTREAMS ) { proc->pipeout = pipe_allocate(); proc->pipein = pipe_allocate(); si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = pipe_write_handle( proc->pipeout ); si.hStdError = pipe_write_handle( proc->pipeout ); si.hStdInput = pipe_read_handle( proc->pipein ); //Don't inherit wrong ends of pipes SetHandleInformation( pipe_read_handle( proc->pipeout ), HANDLE_FLAG_INHERIT, 0 ); SetHandleInformation( pipe_write_handle( proc->pipein ), HANDLE_FLAG_INHERIT, 0 ); inherit_handles = TRUE; } log_debugf( 0, "Spawn process (CreateProcess): %s %s", proc->path, cmdline ); if( !CreateProcessW( 0/*wpath*/, wcmdline, 0, 0, inherit_handles, ( proc->flags & PROCESS_CONSOLE ) ? CREATE_NEW_CONSOLE : 0, 0, wwd, &si, &pi ) ) { log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to spawn process (CreateProcess) for executable '%s': %s", proc->path, system_error_message( GetLastError() ) ); stream_deallocate( proc->pipeout ); stream_deallocate( proc->pipein ); proc->pipeout = 0; proc->pipein = 0; } else { proc->hp = pi.hProcess; proc->ht = pi.hThread; proc->code = 0; } if( proc->pipeout ) pipe_close_write( proc->pipeout ); if( proc->pipein ) pipe_close_read( proc->pipein ); } wstring_deallocate( wcmdline ); wstring_deallocate( wwd ); string_deallocate( cmdline ); if( proc->code < 0 ) return proc->code; //Error #endif #if FOUNDATION_PLATFORM_MACOSX if( proc->flags & PROCESS_OSX_USE_OPENAPPLICATION ) { proc->pid = 0; LSApplicationParameters params; ProcessSerialNumber psn; FSRef* fsref = memory_allocate( 0, sizeof( FSRef ), 0, MEMORY_TEMPORARY | MEMORY_ZERO_INITIALIZED ); memset( ¶ms, 0, sizeof( LSApplicationParameters ) ); memset( &psn, 0, sizeof( ProcessSerialNumber ) ); char* pathstripped = string_strip( string_clone( proc->path ), "\"" ); OSStatus status = 0; status = FSPathMakeRef( (uint8_t*)pathstripped, fsref, 0 ); if( status < 0 ) { pathstripped = string_append( pathstripped, ".app" ); status = FSPathMakeRef( (uint8_t*)pathstripped, fsref, 0 ); } CFStringRef* args = 0; for( i = 0, size = array_size( proc->args ); i < size; ++i ) //App gets executable path automatically, don't include array_push( args, CFStringCreateWithCString( 0, proc->args[i], kCFStringEncodingUTF8 ) ); CFArrayRef argvref = CFArrayCreate( 0, (const void**)args, (CFIndex)array_size( args ), 0 ); params.flags = kLSLaunchDefaults; params.application = fsref; params.argv = argvref; log_debugf( 0, "Spawn process (LSOpenApplication): %s", pathstripped ); status = LSOpenApplication( ¶ms, &psn ); if( status != 0 ) { proc->code = status; log_warnf( 0, WARNING_BAD_DATA, "Unable to spawn process for executable '%s': %s", proc->path, system_error_message( status ) ); } CFRelease( argvref ); for( i = 0, size = array_size( args ); i < size; ++i ) CFRelease( args[i] ); memory_deallocate( fsref ); string_deallocate( pathstripped ); if( status == 0 ) { pid_t pid = 0; GetProcessPID( &psn, &pid ); proc->pid = pid; //Always "detached" with LSOpenApplication, not a child process at all //Setup a kqueue to watch when process terminates so we can emulate a wait proc->kq = kqueue(); if( proc->kq < 0 ) { log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to create kqueue for process watch: %s (%d)", proc->kq, system_error_message( proc->kq ) ); proc->kq = 0; } else { struct kevent changes; EV_SET( &changes, (pid_t)pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, 0 ); int ret = kevent( proc->kq, &changes, 1, &changes, 1, 0 ); if( ret != 1 ) { log_warnf( 0, WARNING_SYSTEM_CALL_FAIL, "Unable to setup kqueue for process watch, failed to add event to kqueue (%d)", ret ); close( proc->kq ); proc->kq = 0; } } } goto exit; } #endif #if FOUNDATION_PLATFORM_POSIX //Insert executable arg at start and null ptr at end int argc = array_size( proc->args ) + 1; array_grow( proc->args, 2 ); for( int arg = argc - 1; arg > 0; --arg ) proc->args[arg] = proc->args[arg-1]; proc->args[0] = string_clone( proc->path ); proc->args[argc] = 0; if( proc->flags & PROCESS_STDSTREAMS ) { proc->pipeout = pipe_allocate(); proc->pipein = pipe_allocate(); } proc->pid = 0; pid_t pid = fork(); if( pid == 0 ) { //Child if( string_length( proc->wd ) ) { log_debugf( 0, "Spawned child process, setting working directory to %s", proc->wd ); environment_set_current_working_directory( proc->wd ); } log_debugf( 0, "Child process executing: %s", proc->path ); if( proc->flags & PROCESS_STDSTREAMS ) { pipe_close_read( proc->pipeout ); dup2( pipe_write_fd( proc->pipeout ), STDOUT_FILENO ); pipe_close_write( proc->pipein ); dup2( pipe_read_fd( proc->pipein ), STDIN_FILENO ); } int code = execv( proc->path, proc->args ); if( code < 0 ) //Will always be true since this point will never be reached if execve() is successful log_warnf( 0, WARNING_BAD_DATA, "Child process failed execve() : %s : %s", proc->path, system_error_message( errno ) ); //Error process_exit( -1 ); } if( pid > 0 ) { log_debugf( 0, "Child process forked, pid %d", pid ); proc->pid = pid; if( proc->pipeout ) pipe_close_write( proc->pipeout ); if( proc->pipein ) pipe_close_read( proc->pipein ); if( proc->flags & PROCESS_DETACHED ) { int cstatus = 0; pid_t err = waitpid( pid, &cstatus, WNOHANG ); if( err == 0 ) { //TODO: Ugly wait to make sure process spawned correctly thread_sleep( 500 ); err = waitpid( pid, &cstatus, WNOHANG ); } if( err > 0 ) { //Process exited, check code proc->code = (int)((char)WEXITSTATUS( cstatus )); log_debugf( 0, "Child process returned: %d", proc->code ); return proc->code; } } } else { //Error proc->code = errno; log_warnf( 0, WARNING_BAD_DATA, "Unable to spawn process: %s : %s", proc->path, system_error_message( proc->code ) ); if( proc->pipeout ) stream_deallocate( proc->pipeout ); if( proc->pipein ) stream_deallocate( proc->pipein ); proc->pipeout = 0; proc->pipein = 0; return proc->code; } #endif #if !FOUNDATION_PLATFORM_WINDOWS && !FOUNDATION_PLATFORM_POSIX FOUNDATION_ASSERT_FAIL( "Process spawning not supported on platform" ); #endif #if FOUNDATION_PLATFORM_MACOSX exit: #endif if( proc->flags & PROCESS_DETACHED ) return PROCESS_STILL_ACTIVE; return process_wait( proc ); }
DECLARE_TEST( string, prepend ) { const char* nullstr = 0; const char* emptystr = ""; const char* shortstr = "short"; const char* longstr = "long long long long long long long long long"; char* val; val = 0; val = string_prepend( val, nullstr ); EXPECT_STREQ( val, "" ); string_deallocate( val ); val = 0; val = string_prepend( val, emptystr ); EXPECT_STREQ( val, "" ); string_deallocate( val ); val = string_clone( emptystr ); val = string_prepend( val, nullstr ); EXPECT_STREQ( val, "" ); string_deallocate( val ); val = string_clone( emptystr ); val = string_prepend( val, emptystr ); EXPECT_STREQ( val, "" ); string_deallocate( val ); val = 0; val = string_prepend( val, shortstr ); EXPECT_STREQ( val, "short" ); string_deallocate( val ); val = string_clone( shortstr ); val = string_prepend( val, nullstr ); EXPECT_STREQ( val, "short" ); string_deallocate( val ); val = string_clone( emptystr ); val = string_prepend( val, shortstr ); EXPECT_STREQ( val, "short" ); string_deallocate( val ); val = string_clone( shortstr ); val = string_prepend( val, emptystr ); EXPECT_STREQ( val, "short" ); string_deallocate( val ); val = string_clone( shortstr ); val = string_prepend( val, shortstr ); EXPECT_STREQ( val, "shortshort" ); string_deallocate( val ); val = 0; val = string_prepend( val, longstr ); EXPECT_STREQ( val, "long long long long long long long long long" ); string_deallocate( val ); val = string_clone( longstr ); val = string_prepend( val, nullstr ); EXPECT_STREQ( val, "long long long long long long long long long" ); string_deallocate( val ); val = string_clone( emptystr ); val = string_prepend( val, longstr ); EXPECT_STREQ( val, "long long long long long long long long long" ); string_deallocate( val ); val = string_clone( longstr ); val = string_prepend( val, emptystr ); EXPECT_STREQ( val, "long long long long long long long long long" ); string_deallocate( val ); val = string_clone( shortstr ); val = string_prepend( val, longstr ); EXPECT_STREQ( val, "long long long long long long long long longshort" ); string_deallocate( val ); val = string_clone( longstr ); val = string_prepend( val, shortstr ); EXPECT_STREQ( val, "shortlong long long long long long long long long" ); string_deallocate( val ); val = string_clone( longstr ); val = string_prepend( val, longstr ); EXPECT_STREQ( val, "long long long long long long long long longlong long long long long long long long long" ); string_deallocate( val ); return 0; }
char* path_make_absolute( const char* path ) { unsigned int up, last, length, protocollen; char* abspath = string_clone( path ); if( !path_is_absolute( abspath ) ) { abspath = string_prepend( abspath, "/" ); abspath = string_prepend( abspath, environment_current_working_directory() ); abspath = path_clean( abspath, true ); } else { abspath = path_clean( abspath, true ); } protocollen = string_find_string( abspath, "://", 0 ); if( protocollen != STRING_NPOS ) protocollen += 3; //Also skip the "://" separator else protocollen = 0; //Deal with .. references while( ( up = string_find_string( abspath, "/../", 0 ) ) != STRING_NPOS ) { char* subpath; if( ( protocollen && ( up == ( protocollen - 1 ) ) ) || ( !protocollen && ( up == 0 ) ) ) { //This moves mem so "prot://../path" ends up as "prot://path" memmove( abspath + protocollen, abspath + 3 + protocollen, string_length( abspath ) + 1 - ( 3 + protocollen ) ); continue; } last = string_rfind( abspath, '/', up - 1 ); if( last == STRING_NPOS ) { //Must be a path like C:/../something since other absolute paths last = up; } subpath = string_substr( abspath, 0, last ); subpath = string_append( subpath, abspath + up + 3 ); // +3 will include the / of the later part of the path string_deallocate( abspath ); abspath = subpath; } length = string_length( abspath ); if( length >= 3 ) { while( ( length >= 3 ) && ( abspath[length-3] == '/' ) && ( abspath[length-2] == '.' ) && ( abspath[length-1] == L'.' ) ) { //Step up if( length == 3 ) { abspath[1] = 0; length = 1; } else { length = string_rfind( abspath, '/', length - 4 ); abspath[length] = 0; } } } return abspath; }
int main(int argc, char *argv[]) { string *s = NULL; string *s2 = NULL; string *s3 = NULL; s = string_append(s, "Hello There"); test(s, "Hello There"); s = string_append(s, "123"); test(s, "Hello There123"); s = string_insert(s, 1, "<new>"); test(s, "H<new>ello There123"); s = string_insert(s, 23, "<new>"); test(s, "H<new>ello There123<new>"); s = string_remove(s, 11, 5); test(s, "H<new>ello 123<new>"); s = string_replace(s, "<new>", "<nothing>"); test(s, "H<nothing>ello 123<nothing>"); s = string_remove(s, 0, 30); test(s, ""); s = string_append(s, "H<nothing>ello 123<nothing>"); test(s, "H<nothing>ello 123<nothing>"); s = string_trunc(s); test(s, ""); string_free(&s); s = string_append_int(s, 1); test(s, "1"); s = string_append_int(s, 10); test(s, "110"); s = string_append_int(s, 100); test(s, "110100"); s = string_append_int(s, 1000000000); test(s, "1101001000000000"); s = string_append_int(s, -2); test(s, "1101001000000000-2"); s = string_append_int(s, -345); test(s, "1101001000000000-2-345"); s = string_append_int(s, -3.45); test(s, "1101001000000000-2-345-3"); s = string_trunc(s); test(s,""); s = string_prepend_int(s, 1); test(s, "1"); s = string_prepend_int(s, 10); test(s, "101"); s = string_prepend_int(s, 100); test(s, "100101"); s = string_prepend_int(s, 1000000000); test(s, "1000000000100101"); s = string_prepend_int(s, -2); test(s, "-21000000000100101"); s = string_prepend_int(s, -345); test(s, "-345-21000000000100101"); s = string_prepend_int(s, -3.45); test(s, "-3-345-21000000000100101"); s = string_trunc(s); test(s,""); string_free(&s); s = string_new("\"Quoted String\""); test(s, "\"Quoted String\""); s = string_replace(s, "\"", ""); test(s, "Quoted String"); string_free(&s); s = string_prepend(s, "Nothing"); test(s, "Nothing"); s = string_prepend(s, "12345678901234567890"); test(s, "12345678901234567890Nothing"); s = string_prepend(s, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); test(s, "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890Nothing"); string_free(&s); s = string_append(s, "Nothing"); test(s, "Nothing"); s = string_append(s, "12345678901234567890"); test(s, "Nothing12345678901234567890"); s = string_append(s, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); test(s, "Nothing12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); string_free(&s); s = string_new("printf_append"); test(s, "printf_append"); s = string_printf_append(s, "%8s %3d %10.6f", "string", 20, 3.4); test(s, "printf_append string 20 3.400000"); string_free(&s); s = string_new(""); s = string_printf(s, "%8s %3d %10.6f", "string", 20, 3.4); test(s, " string 20 3.400000"); s2 = string_copy(s); test(s2, " string 20 3.400000"); testi(string_equal_char(s, string_string(s2)), 1); testi(string_equal(s, s2), 1); testi(string_equal_char(s, " string 20 3.40"), 0); s3 = string_new(" string 20 3.40"); testi(string_equal(s, s3), 0); string_free(&s2); string_free(&s3); s = string_remove(s, 100000, 20); test(s, " string 20 3.400000"); s = string_remove(s, 15, 20); test(s, " string 20 "); s = string_remove(s, 13, -1); test(s, " string 20 "); s = string_remove(s, 1, 1); test(s, " string 20 "); s = string_remove(s, 7, 2); test(s, " string20 "); s = string_remove(s, 9, 1); test(s, " string20"); s = string_remove(s, -1, -1); test(s, ""); s = string_insert(s, -1, " string20"); test(s, " string20"); s = string_insert(s, 9, " "); test(s, " string20 "); s = string_insert(s, 7, " "); test(s, " string 20 "); s = string_insert(s, 1, " "); test(s, " string 20 "); s = string_insert(s, 13, " "); test(s, " string 20 "); s = string_insert(s, 15, "3.400000"); test(s, " string 20 3.400000"); s = string_insert(s, 10000, " morestuff"); test(s, " string 20 3.400000 morestuff"); s = string_insert(s, -1, " morestuff"); test(s, " morestuff string 20 3.400000 morestuff"); s2 = string_substr(s, 1, 4); test(s2, "more"); string_free(&s2); s2 = string_substr(s, 5, 7); test(s2, "stuff "); string_free(&s2); string_free(&s); return retval; }