The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "inifile.h"

ini_file_t *inifile_open( const char *filename ) {
	ini_file_t *ini;
	Newxz( ini, 1, ini_file_t );
	my_strncpy( ini->filename, filename, PATH_MAX );
	inifile_load( ini );
	return ini;
}

void inifile_cleanup( ini_file_t *ini ) {
	ini_section_t *s1, *s2;
	ini_item_t *i1, *i2;
	if( ini == NULL )
		return;
	for( s1 = ini->first_section; s1 != NULL; ) {
		s2 = s1->next;
		for( i1 = s1->first_item; i1 != NULL; ) {
			i2 = i1->next;
			Safefree( i1->ident );
			Safefree( i1->value );
			Safefree( i1->p_comment );
			Safefree( i1 );
			i1 = i2;
		}
		Safefree( s1->s_comment );
		Safefree( s1->p_comment );
		Safefree( s1->name );
		Safefree( s1 );
		s1 = s2;
	}
	Safefree( ini->p_comment );
	Safefree( ini->newline );
	ini->first_section = ini->last_section = NULL;
	ini->p_comment = ini->newline = NULL;
}

void inifile_close( ini_file_t *ini ) {
	if( ini == NULL )
		return;
	if( ini->changed )
		inifile_save( ini );
	inifile_cleanup( ini );
	Safefree( ini );
}

int inifile_load( ini_file_t *ini ) {
	FILE *file;
	ini_section_t *sec = NULL;
	ini_item_t *item = NULL;
	char *stmp = NULL, *srmk = NULL;
	int c, step = 0, nl = 1, co = 0;
	size_t ltmp = 0, ptmp = 0, lrmk = 0, prmk = 0;
	inifile_cleanup( ini );
	file = fopen( ini->filename, "r" );
	if( file == NULL )
		return 1;
	while( (c = fgetc( file )) != EOF ) {
		switch( c ) {
		case '\r':
			if( ini->newline == NULL && nl == 1 ) {
				ini->newline_length = 2;
				Newx( ini->newline, 3, char );
				Copy( "\n\r", ini->newline, 3, char );
			}
			nl = 2;
			break;
		case '\n':
			if( ini->newline == NULL ) {
				if( nl == 2 ) {
					ini->newline_length = 2;
					Newx( ini->newline, 3, char );
					Copy( "\r\n", ini->newline, 3, char );
				}
				else {
					ini->newline_length = 1;
					Newx( ini->newline, 2, char );
					Copy( "\n", ini->newline, 2, char );
				}
			}
			if( co ) {
				co = 0;
				stmp[ptmp] = '\0';
				/*
#ifdef CSV_DEBUG
				_debug( "comment line read [%s]\n", stmp );
#endif
				*/
				if( prmk + ptmp > lrmk ) {
					lrmk = prmk + ptmp;
					Renew( srmk, lrmk + ini->newline_length, char );
				}
				Copy( stmp, &srmk[prmk], ptmp, char );
				prmk += ptmp;
				Copy( ini->newline, &srmk[prmk], ini->newline_length, char );
				prmk += ini->newline_length;
				ptmp = 0;
			}
			else if( step == 3 ) {
				for( nl = 0; nl < ptmp && isSPACE( stmp[nl] ); nl ++ );
				stmp[ptmp] = '\0';
				/*
#ifdef CSV_DEBUG
				_debug( "value read [%s]\n", &stmp[nl] );
#endif
				*/
				item->value_length = ptmp - nl;
				Newx( item->value, ptmp - nl + 1, char );
				Copy( &stmp[nl], item->value, ptmp - nl + 1, char );
				ptmp = 0;
				step = 2;
			}
			nl = 1;
			continue;
		case '[':
			if( nl ) {
				/*
#ifdef CSV_DEBUG
				_debug( "section begin\n" );
#endif
				*/
				Newxz( sec, 1, ini_section_t );
				item = NULL;
				if( prmk > 0 ) {
					srmk[prmk] = '\0';
					/*
#ifdef CSV_DEBUG
					_debug( "comment %u\n%s\n", prmk, srmk );
#endif
					*/
					sec->s_comment_length = prmk;
					Newx( sec->s_comment, prmk + 1, char );
					Copy( srmk, sec->s_comment, prmk + 1, char );
					prmk = 0;
				}
				step = 1;
				break;
			}
			goto _default;
		case ']':
			if( step == 1 ) {
				stmp[ptmp] = '\0';
				/*
#ifdef CSV_DEBUG
				_debug( "section read [%s]\n", stmp );
#endif
				*/
				sec->name_length = ptmp;
				Newx( sec->name, ptmp + 1, char );
				Copy( stmp, sec->name, ptmp + 1, char );
				if( ini->first_section == NULL )
					ini->first_section = sec;
				else {
					ini->last_section->next = sec;
					sec->prev = ini->last_section;
				}
				ini->last_section = sec;
				ptmp = 0;
				step = 2;
				break;
			}
			goto _default;
		case '=':
			if( ! co && step == 2 ) {
				if( prmk > 0 ) {
					srmk[prmk] = '\0';
					/*
#ifdef CSV_DEBUG
					_debug( "comment %u\n%s\n", prmk, srmk );
#endif
					*/
					if( item != NULL ) {
						item->p_comment_length = prmk;
						Newx( item->p_comment, prmk + 1, char );
						Copy( srmk, item->p_comment, prmk + 1, char );
					}
					else {
						sec->p_comment_length = prmk;
						Newx( sec->p_comment, prmk + 1, char );
						Copy( srmk, sec->p_comment, prmk + 1, char );
					}
					prmk = 0;
				}
				for( ; ptmp > 0 && isSPACE( stmp[ptmp - 1] ); ptmp -- );
				stmp[ptmp] = '\0';
				/*
#ifdef CSV_DEBUG
				_debug( "ident read [%s]\n", stmp );
#endif
				*/
				Newxz( item, 1, ini_item_t );
				item->ident_length = ptmp;
				Newx( item->ident, ptmp + 1, char );
				Copy( stmp, item->ident, ptmp + 1, char );
				if( sec->first_item == NULL )
					sec->first_item = item;
				else {
					sec->last_item->next = item;
					item->prev = sec->last_item;
				}
				sec->last_item = item;
				ptmp = 0;
				step = 3;
				break;
			}
			goto _default;
		case ';':
		case '#':
			if( nl )
				co = 1;
		default:
_default:
			if( ptmp >= ltmp ) {
				ltmp += 32;
				Renew( stmp, ltmp + 1, char );
			}
			stmp[ptmp ++] = (char) c;
			nl = 0;
			break;
		}
	}
	if( prmk > 0 ) {
		srmk[prmk] = '\0';
		/*
#ifdef CSV_DEBUG
		_debug( "comment %u\n%s\n", prmk, srmk );
#endif
		*/
		ini->p_comment_length = prmk;
		Newx( ini->p_comment, prmk + 1, char );
		Copy( srmk, ini->p_comment, prmk + 1, char );
	}
	fclose( file );
	Safefree( stmp );
	Safefree( srmk );
	return 0;
}

int inifile_save( ini_file_t *ini ) {
	ini_section_t *s1;
	ini_item_t *i1;
	char tmp[PATH_MAX], *p1;
	FILE *fh;
	p1 = my_strcpy( tmp, ini->filename );
	//p1 = my_strcpy( p1, ".txt" );
	fh = fopen( tmp, "w" );
	if( fh == NULL )
		return 1;
	//fseek( file, 0, SEEK_SET );
	if( ini->newline == NULL ) {
		ini->newline_length = sizeof( EOL );
		Newx( ini->newline, sizeof( EOL ) + 1, char );
		Copy( EOL, ini->newline, sizeof( EOL ) + 1, char );
	}
	for( s1 = ini->first_section; s1 != NULL; s1 = s1->next ) {
		if( s1->s_comment != NULL )
			fputs( s1->s_comment, fh );
		if( s1->prev != NULL )
			fputs( ini->newline, fh );
		fputc( '[', fh );
		fputs( s1->name, fh );
		fputc( ']', fh );
		fputs( ini->newline, fh );
		if( s1->p_comment != NULL )
			fputs( s1->p_comment, fh );
		for( i1 = s1->first_item; i1 != NULL; i1 = i1->next ) {
			fputs( i1->ident, fh );
			fputc( '=', fh );
			if( i1->value != NULL )
				fputs( i1->value, fh );
			fputs( ini->newline, fh );
			if( i1->p_comment != NULL )
				fputs( i1->p_comment, fh );
		}
	}
	if( ini->p_comment != NULL )
		fputs( ini->p_comment, fh );
	fclose( fh );
	ini->changed = 0;
	return 0;
}

const char *inifile_read_string(
	ini_file_t *ini, const char *s_section, const char *s_ident,
	const char *s_default
) {
	ini_section_t *s1;
	ini_item_t *i1;
	for( s1 = ini->first_section; s1 != NULL; s1 = s1->next ) {
		if( my_stricmp( s1->name, s_section ) != 0 )
			continue;
		for( i1 = s1->first_item; i1 != NULL; i1 = i1->next ) {
			if( my_stricmp( i1->ident, s_ident ) != 0 )
				continue;
			return i1->value;
		}
	}
	return s_default;
}

void inifile_write_string(
	ini_file_t *ini, const char *s_section, const char *s_ident,
	const char *s_value
) {
	ini_section_t *s1;
	ini_item_t *i1;
	size_t len = s_value != NULL ? strlen( s_value ) : 0;
	ini->changed = 1;
	for( s1 = ini->first_section; s1 != NULL; s1 = s1->next ) {
		if( my_stricmp( s1->name, s_section ) != 0 )
			continue;
		for( i1 = s1->first_item; i1 != NULL; i1 = i1->next ) {
			if( my_stricmp( i1->ident, s_ident ) != 0 )
				continue;
			if( len ) {
				if( i1->value_length < len )
					Renew( i1->value, len + 1, char );
				i1->value_length = len;
				Copy( s_value, i1->value, len + 1, char );
			}
			else
				Safefree( i1->value );
			return;
		}
		goto _create_item;
	}
	Newxz( s1, 1, ini_section_t );
	if( ini->first_section == NULL )
		ini->first_section = s1;
	else {
		ini->last_section->next = s1;
		s1->prev = ini->last_section;
	}
	ini->last_section = s1;
	s1->name_length = strlen( s_section );
	Newx( s1->name, s1->name_length + 1, char );
	Copy( s_section, s1->name, s1->name_length + 1, char );
_create_item:
	Newxz( i1, 1, ini_item_t );
	if( s1->first_item == NULL )
		s1->first_item = i1;
	else {
		s1->last_item->next = i1;
		i1->prev = s1->last_item;
	}
	s1->last_item = i1;
	i1->ident_length = strlen( s_ident );
	Newx( i1->ident, i1->ident_length + 1, char );
	Copy( s_ident, i1->ident, i1->ident_length + 1, char );
	if( len ) {
		i1->value_length = len;
		Newx( i1->value, len + 1, char );
		Copy( s_value, i1->value, len + 1, char );
	}
	else
		Safefree( i1->value );
}

int inifile_read_integer(
	ini_file_t *ini, const char *s_section, const char *s_ident, int i_default
) {
	const char *val = inifile_read_string( ini, s_section, s_ident, NULL );
	if( val == NULL )
		return i_default;
	return atoi( val );
}

void inifile_write_integer(
	ini_file_t *ini, const char *s_section, const char *s_ident, int i_value
) {
	char tmp[11];
	my_itoa( tmp, i_value, 10 );
	inifile_write_string( ini, s_section, s_ident, tmp );
}

double inifile_read_float(
	ini_file_t *ini, const char *s_section, const char *s_ident,
	double d_default
) {
	const char *val = inifile_read_string( ini, s_section, s_ident, NULL );
	if( val == NULL )
		return d_default;
	return atof( val );
}

void inifile_write_float(
	ini_file_t *ini, const char *s_section, const char *s_ident, double d_value
) {
	char tmp[42];
	my_ftoa( tmp, d_value );
	inifile_write_string( ini, s_section, s_ident, tmp );
}

char inifile_read_char(
	ini_file_t *ini, const char *s_section, const char *s_ident,
	char c_default
) {
	const char *val = inifile_read_string( ini, s_section, s_ident, NULL );
	if( val == NULL )
		return c_default;
	return val[0];
}

void inifile_write_char(
	ini_file_t *ini, const char *s_section, const char *s_ident, char c_value
) {
	char tmp[2];
	tmp[0] = c_value;
	tmp[1] = '\0';
	inifile_write_string( ini, s_section, s_ident, tmp );
}

void inifile_delete_section( ini_file_t *ini, const char *s_section ) {
	ini_section_t *s1;
	ini_item_t *i1, *i2;
	if( ini == NULL )
		return;
	for( s1 = ini->first_section; s1 != NULL; s1 = s1->next ) {
		if( my_stricmp( s1->name, s_section ) != 0 )
			continue;
		if( ini->first_section == s1 )
			ini->first_section = s1->next;
		if( ini->last_section == s1 )
			ini->last_section = s1->prev;
		if( s1->prev )
			s1->prev->next = s1->next;
		if( s1->next )
			s1->next->prev = s1->prev;
		for( i1 = s1->first_item; i1 != NULL; ) {
			i2 = i1->next;
			Safefree( i1->ident );
			Safefree( i1->value );
			Safefree( i1->p_comment );
			Safefree( i1 );
			i1 = i2;
		}
		Safefree( s1->s_comment );
		Safefree( s1->p_comment );
		Safefree( s1->name );
		Safefree( s1 );
		ini->changed = 1;
		return;
	}
}

void inifile_delete_ident(
	ini_file_t *ini, const char *s_section, const char *s_ident
) {
	ini_section_t *s1;
	ini_item_t *i1;
	if( ini == NULL )
		return;
	for( s1 = ini->first_section; s1 != NULL; s1 = s1->next ) {
		if( my_stricmp( s1->name, s_section ) != 0 )
			continue;
		for( i1 = s1->first_item; i1 != NULL; i1 = i1->next ) {
			if( my_stricmp( i1->ident, s_ident ) != 0 )
				continue;
			if( s1->first_item == i1 )
				s1->first_item = i1->next;
			if( s1->last_item == i1 )
				s1->last_item = i1->prev;
			if( i1->prev )
				i1->prev->next = i1->next;
			if( i1->next )
				i1->next->prev = i1->prev;
			Safefree( i1->ident );
			Safefree( i1->value );
			Safefree( i1->p_comment );
			Safefree( i1 );
			ini->changed = 1;
			return;
		}
	}
}