2021-02-18 22:25:36 +01:00
#include "fastfetch.h"
2023-08-17 16:00:24 +08:00
#include "common/commandoption.h"
2022-07-25 13:15:01 +02:00
#include "common/printing.h"
2022-07-25 13:59:19 +02:00
#include "common/parsing.h"
2023-01-28 12:40:12 +01:00
#include "common/io/io.h"
2022-12-14 11:30:50 +08:00
#include "common/time.h"
2023-06-11 00:51:29 +08:00
#include "common/jsonconfig.h"
2023-08-21 09:22:03 +08:00
#include "detection/version/version.h"
2023-02-01 15:56:25 +01:00
#include "util/stringUtils.h"
2023-03-10 17:49:03 +08:00
#include "logo/logo.h"
2023-07-28 14:12:48 +08:00
#include "fastfetch_datatext.h"
2021-02-18 22:25:36 +01:00
2022-06-18 14:25:31 +02:00
#include <stdlib.h>
2022-08-21 21:08:11 +02:00
#include <ctype.h>
2021-04-09 14:07:05 +02:00
#include <string.h>
2022-12-14 11:30:50 +08:00
#include <inttypes.h>
2021-02-22 16:52:51 +01:00
2022-10-13 18:12:54 +08:00
#ifdef WIN32
#include "util/windows/getline.h"
#endif
2023-03-07 16:13:53 +08:00
#include "modules/modules.h"
2023-03-07 14:56:13 +08:00
2023-10-07 13:40:40 +08:00
static void printCommandFormatHelp ( const char * command )
2021-03-19 20:57:07 +01:00
{
2023-10-07 13:40:40 +08:00
FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS (( uint32_t ) ( strlen ( command ) - strlen ( "-format" )), command );
for ( FFModuleBaseInfo ** modules = ffModuleInfos [ toupper ( command [ 0 ]) - 'A' ]; * modules ; ++ modules )
{
FFModuleBaseInfo * baseInfo = * modules ;
if ( ffStrbufIgnCaseEqualS ( & type , baseInfo -> name ))
{
if ( baseInfo -> printHelpFormat )
baseInfo -> printHelpFormat ();
else
fprintf ( stderr , "Error: Module '%s' doesn't support output formatting \n " , baseInfo -> name );
return ;
}
}
2021-03-19 20:57:07 +01:00
2023-10-07 13:40:40 +08:00
fprintf ( stderr , "Error: Module '%s' is not supported \n " , type . chars );
2021-03-11 22:15:48 +01:00
}
2021-03-11 19:40:46 +01:00
static inline void printCommandHelp ( const char * command )
2021-02-20 19:32:57 +01:00
{
2021-10-22 21:53:29 +02:00
if ( command == NULL )
2022-12-12 13:43:10 +01:00
puts ( FASTFETCH_DATATEXT_HELP );
2023-08-24 14:36:32 +08:00
else if ( ffStrEqualsIgnCase ( command , "color" ))
2022-12-12 13:43:10 +01:00
puts ( FASTFETCH_DATATEXT_HELP_COLOR );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( command , "format" ))
2022-12-12 13:43:10 +01:00
puts ( FASTFETCH_DATATEXT_HELP_FORMAT );
2023-08-24 14:36:32 +08:00
else if ( ffStrEqualsIgnCase ( command , "load-config" ) || ffStrEqualsIgnCase ( command , "config" ))
2022-12-12 13:43:10 +01:00
puts ( FASTFETCH_DATATEXT_HELP_CONFIG );
2023-10-07 13:40:40 +08:00
else if ( isalpha ( command [ 0 ]) && ffStrEndsWithIgnCase ( command , "-format" )) // x-format
printCommandFormatHelp ( command );
2021-02-20 19:32:57 +01:00
else
2023-10-07 13:40:40 +08:00
fprintf ( stderr , "Error: No specific help for command '%s' provided \n " , command );
2021-02-20 19:32:57 +01:00
}
2023-06-24 10:59:53 +08:00
static void listAvailablePresets ( void )
2021-04-25 11:48:25 +02:00
{
2023-06-24 10:59:53 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . dataDirs )
2021-04-25 11:48:25 +02:00
{
2023-01-28 17:19:45 +01:00
ffStrbufAppendS ( path , "fastfetch/presets/" );
ffListFilesRecursively ( path -> chars );
2021-04-25 11:48:25 +02:00
}
}
2023-06-24 10:59:53 +08:00
static void listAvailableLogos ( void )
2021-04-25 11:48:25 +02:00
{
2023-06-24 10:59:53 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . dataDirs )
2023-01-08 01:41:09 +01:00
{
2023-01-28 17:19:45 +01:00
ffStrbufAppendS ( path , "fastfetch/logos/" );
ffListFilesRecursively ( path -> chars );
2023-01-08 01:41:09 +01:00
}
2021-04-25 11:48:25 +02:00
}
2023-06-24 10:59:53 +08:00
static void listConfigPaths ( void )
2023-01-02 16:51:11 +08:00
{
2023-06-24 10:59:53 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , folder , instance . state . platform . configDirs )
2023-01-02 16:51:11 +08:00
{
2023-06-10 21:34:41 +08:00
bool exists = false ;
ffStrbufAppendS ( folder , "fastfetch/config.jsonc" );
exists = ffPathExists ( folder -> chars , FF_PATHTYPE_FILE );
if ( ! exists )
{
ffStrbufSubstrBefore ( folder , folder -> length - ( uint32_t ) strlen ( "jsonc" ));
ffStrbufAppendS ( folder , "conf" );
exists = ffPathExists ( folder -> chars , FF_PATHTYPE_FILE );
}
printf ( "%s%s \n " , folder -> chars , exists ? " (*)" : "" );
2023-01-02 16:51:11 +08:00
}
}
2023-06-24 10:59:53 +08:00
static void listDataPaths ( void )
2023-01-08 01:41:09 +01:00
{
2023-06-24 10:59:53 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , folder , instance . state . platform . dataDirs )
2023-01-08 01:41:09 +01:00
{
ffStrbufAppendS ( folder , "fastfetch/" );
puts ( folder -> chars );
}
}
2023-08-20 20:51:55 +08:00
static void listModules ()
{
unsigned count = 0 ;
for ( int i = 0 ; i <= 'Z' - 'A' ; ++ i )
{
for ( FFModuleBaseInfo ** modules = ffModuleInfos [ i ]; * modules ; ++ modules )
{
++ count ;
printf ( "%d)%s%s \n " , count , count > 9 ? " " : " " , ( * modules ) -> name );
}
}
}
2023-06-24 10:59:53 +08:00
static void parseOption ( FFdata * data , const char * key , const char * value );
2021-04-25 11:48:25 +02:00
2023-06-24 10:59:53 +08:00
static bool parseJsoncFile ( const char * path )
2023-06-10 21:34:41 +08:00
{
2023-06-17 20:39:08 +08:00
yyjson_read_err error ;
yyjson_doc * doc = yyjson_read_file ( path , YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_INF_AND_NAN , NULL , & error );
2023-07-05 14:21:35 +08:00
if ( ! doc )
2023-06-10 21:34:41 +08:00
{
2023-07-05 14:21:35 +08:00
if ( error . code != YYJSON_READ_ERROR_FILE_OPEN )
{
2023-08-18 13:28:07 +08:00
fprintf ( stderr , "Error: failed to parse JSON config file `%s` at pos %zu: %s \n " , path , error . pos , error . msg );
2023-06-10 21:34:41 +08:00
exit ( 477 );
}
2023-07-05 14:21:35 +08:00
return false ;
2023-06-17 20:39:08 +08:00
}
2023-07-05 14:21:35 +08:00
if ( instance . state . configDoc )
yyjson_doc_free ( instance . state . configDoc ); // for `--load-config`
instance . state . configDoc = doc ;
return true ;
2023-06-10 21:34:41 +08:00
}
2023-06-24 10:59:53 +08:00
static bool parseConfigFile ( FFdata * data , const char * path )
2021-04-25 11:48:25 +02:00
{
2022-08-22 12:45:20 +02:00
FILE * file = fopen ( path , "r" );
if ( file == NULL )
return false ;
2022-08-21 21:08:11 +02:00
char * line = NULL ;
2021-04-25 11:48:25 +02:00
size_t len = 0 ;
ssize_t read ;
2023-08-15 16:39:20 +08:00
FF_STRBUF_AUTO_DESTROY unescaped = ffStrbufCreate ();
2021-04-25 11:48:25 +02:00
2022-08-21 21:08:11 +02:00
while (( read = getline ( & line , & len , file )) != - 1 )
2021-04-25 11:48:25 +02:00
{
2022-08-21 21:08:11 +02:00
char * lineStart = line ;
char * lineEnd = line + read - 1 ;
//Trim line left
while ( isspace ( * lineStart ))
++ lineStart ;
2021-04-25 11:48:25 +02:00
2022-08-21 21:08:11 +02:00
//Continue if line is empty or a comment
if ( * lineStart == '\0' || * lineStart == '#' )
2021-04-25 11:48:25 +02:00
continue ;
2022-08-21 21:08:11 +02:00
//Trim line right
while ( lineEnd > lineStart && isspace ( * lineEnd ))
-- lineEnd ;
* ( lineEnd + 1 ) = '\0' ;
char * valueStart = strchr ( lineStart , ' ' );
2021-04-25 11:48:25 +02:00
2022-08-21 21:08:11 +02:00
//If the line has no white space, it is only a key
if ( valueStart == NULL )
2021-04-25 11:48:25 +02:00
{
2023-06-24 10:59:53 +08:00
parseOption ( data , lineStart , NULL );
2021-04-25 11:48:25 +02:00
continue ;
}
2022-08-21 21:08:11 +02:00
//separate the key from the value
2021-04-25 11:48:25 +02:00
* valueStart = '\0' ;
++ valueStart ;
2022-08-21 21:08:11 +02:00
//Trim space of value left
while ( isspace ( * valueStart ))
2021-04-25 11:48:25 +02:00
++ valueStart ;
//If we want whitespace in values, we need to quote it. This is done to keep consistency with shell.
2022-08-21 21:08:11 +02:00
if (( * valueStart == '"' || * valueStart == '\'' ) && * valueStart == * lineEnd && lineEnd > valueStart )
2021-04-25 11:48:25 +02:00
{
2022-08-21 21:08:11 +02:00
++ valueStart ;
2022-08-22 12:14:08 +02:00
* lineEnd = '\0' ;
2022-08-21 21:08:11 +02:00
-- lineEnd ;
2021-04-25 11:48:25 +02:00
}
2023-08-15 16:39:20 +08:00
if ( strchr ( valueStart , '\\' ))
{
// Unescape all `\x`s
const char * value = valueStart ;
while ( * value != '\0' )
{
if ( * value != '\\' )
{
ffStrbufAppendC ( & unescaped , * value );
++ value ;
continue ;
}
++ value ;
switch ( * value )
{
case 'n' : ffStrbufAppendC ( & unescaped , '\n' ); break ;
case 't' : ffStrbufAppendC ( & unescaped , '\t' ); break ;
case 'e' : ffStrbufAppendC ( & unescaped , '\e' ); break ;
case '\\' : ffStrbufAppendC ( & unescaped , '\\' ); break ;
default :
ffStrbufAppendC ( & unescaped , '\\' );
ffStrbufAppendC ( & unescaped , * value );
break ;
}
++ value ;
}
parseOption ( data , lineStart , unescaped . chars );
ffStrbufClear ( & unescaped );
}
else
{
parseOption ( data , lineStart , valueStart );
}
2021-04-25 11:48:25 +02:00
}
2022-08-21 21:08:11 +02:00
if ( line != NULL )
free ( line );
2022-08-22 12:45:20 +02:00
fclose ( file );
return true ;
2021-04-25 11:48:25 +02:00
}
2023-08-07 14:15:11 +08:00
static void generateConfigFile ( bool force , const char * type )
2023-01-02 12:43:11 +08:00
{
2023-06-24 10:59:53 +08:00
FFstrbuf * filename = ( FFstrbuf * ) ffListGet ( & instance . state . platform . configDirs , 0 );
2023-01-02 12:43:11 +08:00
// Paths generated in `init.c/initConfigDirs` end with `/`
2023-08-07 14:15:11 +08:00
bool isJsonc = false ;
if ( type )
{
if ( ffStrEqualsIgnCase ( type , "jsonc" ))
isJsonc = true ;
else if ( ! ffStrEqualsIgnCase ( type , "conf" ))
{
fputs ( "config type can only be `jsonc` or `conf` \n " , stderr );
exit ( 1 );
}
}
ffStrbufAppendS ( filename , isJsonc ? "fastfetch/config.jsonc" : "fastfetch/config.conf" );
2023-01-02 12:43:11 +08:00
2023-01-28 11:52:23 +01:00
if ( ! force && ffPathExists ( filename -> chars , FF_PATHTYPE_FILE ))
2023-01-02 12:43:11 +08:00
{
fprintf ( stderr , "Config file exists in `%s`, use `--gen-config-force` to overwrite \n " , filename -> chars );
exit ( 1 );
}
else
{
2023-08-07 14:15:11 +08:00
ffWriteFileData (
filename -> chars ,
isJsonc ? strlen ( FASTFETCH_DATATEXT_CONFIG_USER_JSONC ) : strlen ( FASTFETCH_DATATEXT_CONFIG_USER ),
isJsonc ? FASTFETCH_DATATEXT_CONFIG_USER_JSONC : FASTFETCH_DATATEXT_CONFIG_USER );
2023-01-21 06:27:39 +08:00
printf ( "A sample config file has been written in `%s` \n " , filename -> chars );
2023-01-02 12:43:11 +08:00
exit ( 0 );
}
}
2023-06-24 10:59:53 +08:00
static void optionParseConfigFile ( FFdata * data , const char * key , const char * value )
2021-04-25 11:48:25 +02:00
{
if ( value == NULL )
{
fprintf ( stderr , "Error: usage: %s <file> \n " , key );
exit ( 413 );
}
2023-07-05 14:21:35 +08:00
uint32_t fileNameLen = ( uint32_t ) strlen ( value );
if ( fileNameLen == 0 )
{
fprintf ( stderr , "Error: usage: %s <file> \n " , key );
exit ( 413 );
}
bool isJsonConfig = fileNameLen > strlen ( ".jsonc" ) && strcasecmp ( value + fileNameLen - strlen ( ".jsonc" ), ".jsonc" ) == 0 ;
2021-04-25 11:48:25 +02:00
2022-08-22 12:45:20 +02:00
//Try to load as an absolute path
2023-07-05 14:21:35 +08:00
if ( isJsonConfig ? parseJsoncFile ( value ) : parseConfigFile ( data , value ))
2021-04-25 11:48:25 +02:00
return ;
2022-08-22 12:45:20 +02:00
2023-04-15 15:29:54 +08:00
FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA ( 128 );
2023-08-13 10:18:16 +08:00
if ( ffPathExpandEnv ( value , & absolutePath ))
{
2023-08-21 22:13:54 +08:00
bool success = isJsonConfig ? parseJsoncFile ( absolutePath . chars ) : parseConfigFile ( data , absolutePath . chars );
2023-08-13 10:18:16 +08:00
if ( success )
return ;
}
//Try to load as a relative path
2023-01-15 20:03:28 +01:00
2023-06-24 10:59:53 +08:00
FF_LIST_FOR_EACH ( FFstrbuf , path , instance . state . platform . dataDirs )
2021-04-25 11:48:25 +02:00
{
2023-01-15 20:03:28 +01:00
//We need to copy it, because if a config file loads a config file, the value of path must be unchanged
ffStrbufSet ( & absolutePath , path );
ffStrbufAppendS ( & absolutePath , "fastfetch/presets/" );
ffStrbufAppendS ( & absolutePath , value );
2022-08-22 12:45:20 +02:00
2023-08-21 22:13:54 +08:00
bool success = isJsonConfig ? parseJsoncFile ( absolutePath . chars ) : parseConfigFile ( data , absolutePath . chars );
2021-04-25 11:48:25 +02:00
2023-01-08 01:41:09 +01:00
if ( success )
return ;
2021-04-25 11:48:25 +02:00
}
2023-08-24 14:29:35 +08:00
{
//Try exe path
ffStrbufSet ( & absolutePath , & instance . state . platform . exePath );
ffStrbufSubstrBeforeLastC ( & absolutePath , '/' );
ffStrbufAppendS ( & absolutePath , "/presets/" );
ffStrbufAppendS ( & absolutePath , value );
bool success = isJsonConfig ? parseJsoncFile ( absolutePath . chars ) : parseConfigFile ( data , absolutePath . chars );
if ( success )
return ;
}
2022-08-22 12:45:20 +02:00
//File not found
2021-04-25 11:48:25 +02:00
fprintf ( stderr , "Error: couldn't find config: %s \n " , value );
exit ( 414 );
}
2021-12-20 12:12:55 +01:00
static inline void optionCheckString ( const char * key , const char * value , FFstrbuf * buffer )
2021-03-11 19:40:46 +01:00
{
if ( value == NULL )
{
2021-04-03 11:39:59 +02:00
fprintf ( stderr , "Error: usage: %s <str> \n " , key );
2021-03-11 19:40:46 +01:00
exit ( 477 );
}
2021-11-17 10:59:31 +01:00
ffStrbufEnsureFree ( buffer , 63 ); //This is not needed, as ffStrbufSetS will resize capacity if needed, but giving a higher start should improve performance
2021-12-20 12:12:55 +01:00
}
2023-08-17 16:00:24 +08:00
static void printVersion ()
2023-08-21 09:22:03 +08:00
{
FFVersionResult result = {};
ffDetectVersion ( & result );
printf ( "%s %s%s%s (%s) \n " , result . projectName , result . version , result . versionTweak , result . debugMode ? "-debug" : "" , result . architecture );
2023-08-17 16:00:24 +08:00
}
2023-01-19 14:57:54 +08:00
2023-08-17 16:00:24 +08:00
static void parseOption ( FFdata * data , const char * key , const char * value )
{
///////////////////////
//Informative options//
///////////////////////
if ( ffStrEqualsIgnCase ( key , "-h" ) || ffStrEqualsIgnCase ( key , "--help" ))
{
printCommandHelp ( value );
exit ( 0 );
}
else if ( ffStrEqualsIgnCase ( key , "-v" ) || ffStrEqualsIgnCase ( key , "--version" ))
{
printVersion ();
2022-03-20 17:12:52 +01:00
exit ( 0 );
}
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--version-raw" ))
2021-03-03 19:59:39 +01:00
{
2022-03-20 13:16:04 +01:00
puts ( FASTFETCH_PROJECT_VERSION );
2021-03-03 19:59:39 +01:00
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrStartsWithIgnCase ( key , "--print-" ))
2021-03-27 19:26:58 +01:00
{
2023-09-22 15:00:58 +08:00
const char * subkey = key + strlen ( "--print-" );
if ( ffStrEqualsIgnCase ( subkey , "config-system" ))
2022-10-22 13:02:04 +08:00
{
puts ( FASTFETCH_DATATEXT_CONFIG_SYSTEM );
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "config-user" ))
2022-10-22 13:02:04 +08:00
{
puts ( FASTFETCH_DATATEXT_CONFIG_USER );
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "structure" ))
2022-10-22 13:02:04 +08:00
{
puts ( FASTFETCH_DATATEXT_STRUCTURE );
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "logos" ))
2022-10-22 13:02:04 +08:00
{
2023-06-24 10:59:53 +08:00
ffLogoBuiltinPrint ();
2022-10-22 13:02:04 +08:00
exit ( 0 );
}
else
goto error ;
2021-03-03 19:59:39 +01:00
}
2023-09-22 15:00:58 +08:00
else if ( ffStrStartsWithIgnCase ( key , "--list-" ))
2021-03-03 19:59:39 +01:00
{
2023-09-22 15:00:58 +08:00
const char * subkey = key + strlen ( "--list-" );
if ( ffStrEqualsIgnCase ( subkey , "modules" ))
2022-10-22 13:02:04 +08:00
{
2023-08-20 20:51:55 +08:00
listModules ();
2022-10-22 13:02:04 +08:00
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "presets" ))
2022-10-22 13:02:04 +08:00
{
2023-06-24 10:59:53 +08:00
listAvailablePresets ();
2022-10-22 13:02:04 +08:00
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "config-paths" ))
2023-01-02 16:51:11 +08:00
{
2023-06-24 10:59:53 +08:00
listConfigPaths ();
2023-01-02 16:51:11 +08:00
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "data-paths" ))
2023-01-08 01:41:09 +01:00
{
2023-06-24 10:59:53 +08:00
listDataPaths ();
2023-01-08 01:41:09 +01:00
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "features" ))
2022-10-22 13:02:04 +08:00
{
ffListFeatures ();
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "logos" ))
2022-10-22 13:02:04 +08:00
{
2023-01-28 17:19:45 +01:00
puts ( "Builtin logos:" );
2022-10-22 13:02:04 +08:00
ffLogoBuiltinList ();
2023-01-28 17:19:45 +01:00
puts ( " \n Custom logos:" );
2023-06-24 10:59:53 +08:00
listAvailableLogos ();
2022-10-22 13:02:04 +08:00
exit ( 0 );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "logos-autocompletion" ))
2022-10-22 13:02:04 +08:00
{
ffLogoBuiltinListAutocompletion ();
exit ( 0 );
}
else
goto error ;
2022-04-02 14:35:43 +02:00
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( key , "--set" ) || ffStrEqualsIgnCase ( key , "--set-keyless" ))
2023-07-03 16:22:16 +08:00
{
FF_STRBUF_AUTO_DESTROY customValueStr = ffStrbufCreate ();
ffOptionParseString ( key , value , & customValueStr );
uint32_t index = ffStrbufFirstIndexC ( & customValueStr , '=' );
if ( index == 0 || index == customValueStr . length )
{
fprintf ( stderr , "Error: usage: %s <key>=<str> \n " , key );
exit ( 477 );
}
2023-08-12 09:40:37 +08:00
FF_STRBUF_AUTO_DESTROY customKey = ffStrbufCreateNS ( index , customValueStr . chars );
FFCustomValue * customValue = NULL ;
FF_LIST_FOR_EACH ( FFCustomValue , x , data -> customValues )
{
if ( ffStrbufEqual ( & x -> key , & customKey ))
{
ffStrbufDestroy ( & x -> key );
ffStrbufDestroy ( & x -> value );
customValue = x ;
break ;
}
}
if ( ! customValue ) customValue = ( FFCustomValue * ) ffListAdd ( & data -> customValues );
ffStrbufInitMove ( & customValue -> key , & customKey );
ffStrbufSubstrAfter ( & customValueStr , index );
ffStrbufInitMove ( & customValue -> value , & customValueStr );
2023-09-22 15:00:58 +08:00
customValue -> printKey = key [ 5 ] == '\0' ;
2023-07-03 16:22:16 +08:00
}
2021-03-05 23:00:28 +01:00
2023-10-25 13:39:13 +08:00
////////////
//Switches//
////////////
2021-03-03 19:59:39 +01:00
2023-09-23 09:56:02 +08:00
else if ( ffStrEqualsIgnCase ( key , "-c" ) || ffStrEqualsIgnCase ( key , "--load-config" ) || ffStrEqualsIgnCase ( key , "--config" ))
2023-06-24 10:59:53 +08:00
optionParseConfigFile ( data , key , value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--gen-config" ))
2023-08-07 14:15:11 +08:00
generateConfigFile ( false , value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--gen-config-force" ))
2023-08-07 14:15:11 +08:00
generateConfigFile ( true , value );
2023-10-23 13:39:07 +08:00
else if ( ffStrEqualsIgnCase ( key , "--migrate-config" ))
{
yyjson_mut_doc_free ( instance . state . migrateConfigDoc );
if ( ffOptionParseBoolean ( value ))
{
if ( instance . state . configDoc )
{
fputs ( "Error: existing jsonc config file detected. Exiting \n " , stderr );
exit ( 477 );
}
yyjson_mut_doc * doc = instance . state . migrateConfigDoc = yyjson_mut_doc_new ( NULL );
yyjson_mut_val * root = yyjson_mut_obj ( doc );
yyjson_mut_doc_set_root ( doc , root );
yyjson_mut_obj_add_str ( doc , root , "$schema" , "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json" );
}
else
instance . state . migrateConfigDoc = NULL ;
}
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--load-user-config" ))
2023-06-12 15:48:26 +08:00
data -> loadUserConfig = ffOptionParseBoolean ( value );
2023-08-29 11:13:55 +08:00
else if ( ffStrEqualsIgnCase ( key , "--format" ))
{
switch ( ffOptionParseEnum ( key , value , ( FFKeyValuePair []) {
{ "default" , 0 },
{ "json" , 1 },
{},
}))
{
case 0 :
if ( instance . state . resultDoc )
{
yyjson_mut_doc_free ( instance . state . resultDoc );
instance . state . resultDoc = NULL ;
}
break ;
case 1 :
if ( ! instance . state . resultDoc )
{
instance . state . resultDoc = yyjson_mut_doc_new ( NULL );
yyjson_mut_doc_set_root ( instance . state . resultDoc , yyjson_mut_arr ( instance . state . resultDoc ));
}
break ;
}
}
2022-04-02 14:35:43 +02:00
2023-10-25 13:39:13 +08:00
///////////////////
//General options//
///////////////////
2023-10-25 14:37:48 +08:00
else if ( ffOptionsParseGeneralCommandLine ( & instance . config . general , key , value )) {}
2023-06-12 15:22:46 +08:00
2022-04-02 14:35:43 +02:00
////////////////
//Logo options//
////////////////
2023-10-25 14:43:46 +08:00
else if ( ffOptionsParseLogoCommandLine ( & instance . config . logo , key , value )) {}
2022-04-02 14:35:43 +02:00
///////////////////
//Display options//
///////////////////
2023-10-25 13:39:13 +08:00
else if ( ffStrEqualsIgnCase ( key , "--stat" ))
{
if (( instance . config . stat = ffOptionParseBoolean ( value )))
instance . config . showErrors = true ;
}
else if ( ffStrEqualsIgnCase ( key , "--pipe" ))
instance . config . pipe = ffOptionParseBoolean ( value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--show-errors" ))
2023-06-24 10:59:53 +08:00
instance . config . showErrors = ffOptionParseBoolean ( value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--disable-linewrap" ))
2023-06-24 10:59:53 +08:00
instance . config . disableLinewrap = ffOptionParseBoolean ( value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--hide-cursor" ))
2023-06-24 10:59:53 +08:00
instance . config . hideCursor = ffOptionParseBoolean ( value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "-s" ) || ffStrEqualsIgnCase ( key , "--structure" ))
2023-06-12 15:48:26 +08:00
ffOptionParseString ( key , value , & data -> structure );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--separator" ))
2023-06-24 10:59:53 +08:00
ffOptionParseString ( key , value , & instance . config . keyValueSeparator );
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( key , "--color" ))
2023-06-10 21:34:41 +08:00
{
2023-09-22 15:00:58 +08:00
optionCheckString ( key , value , & instance . config . colorKeys );
ffOptionParseColor ( value , & instance . config . colorKeys );
ffStrbufSet ( & instance . config . colorTitle , & instance . config . colorKeys );
}
else if ( ffStrStartsWithIgnCase ( key , "--color-" ))
{
const char * subkey = key + strlen ( "--color-" );
if ( ffStrEqualsIgnCase ( subkey , "keys" ))
2023-08-17 16:00:24 +08:00
{
optionCheckString ( key , value , & instance . config . colorKeys );
ffOptionParseColor ( value , & instance . config . colorKeys );
}
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "title" ))
2023-08-17 16:00:24 +08:00
{
optionCheckString ( key , value , & instance . config . colorTitle );
ffOptionParseColor ( value , & instance . config . colorTitle );
}
else
goto error ;
2023-06-10 21:34:41 +08:00
}
2023-08-15 17:10:09 +08:00
else if ( ffStrEqualsIgnCase ( key , "--key-width" ))
instance . config . keyWidth = ffOptionParseUInt32 ( key , value );
2023-08-04 16:05:25 +08:00
else if ( ffStrEqualsIgnCase ( key , "--bright-color" ))
instance . config . brightColor = ffOptionParseBoolean ( value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--binary-prefix" ))
2022-08-26 16:02:04 +02:00
{
2023-06-24 10:59:53 +08:00
instance . config . binaryPrefixType = ( FFBinaryPrefixType ) ffOptionParseEnum ( key , value , ( FFKeyValuePair []) {
2023-06-12 15:48:26 +08:00
{ "iec" , FF_BINARY_PREFIX_TYPE_IEC },
{ "si" , FF_BINARY_PREFIX_TYPE_SI },
{ "jedec" , FF_BINARY_PREFIX_TYPE_JEDEC },
{}
});
2022-08-26 16:02:04 +02:00
}
2023-07-10 15:25:45 +08:00
else if ( ffStrEqualsIgnCase ( key , "--size-ndigits" ))
instance . config . sizeNdigits = ( uint8_t ) ffOptionParseUInt32 ( key , value );
else if ( ffStrEqualsIgnCase ( key , "--size-max-prefix" ))
2023-07-10 17:30:08 +08:00
{
instance . config . sizeMaxPrefix = ( uint8_t ) ffOptionParseEnum ( key , value , ( FFKeyValuePair []) {
{ "B" , 0 },
{ "kB" , 1 },
{ "MB" , 2 },
{ "GB" , 3 },
{ "TB" , 4 },
{ "PB" , 5 },
{ "EB" , 6 },
{ "ZB" , 7 },
{ "YB" , 8 },
{}
});
}
2023-07-27 15:58:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--temperature-unit" ))
{
instance . config . temperatureUnit = ( FFTemperatureUnit ) ffOptionParseEnum ( key , value , ( FFKeyValuePair []) {
{ "CELSIUS" , FF_TEMPERATURE_UNIT_CELSIUS },
{ "C" , FF_TEMPERATURE_UNIT_CELSIUS },
{ "FAHRENHEIT" , FF_TEMPERATURE_UNIT_FAHRENHEIT },
{ "F" , FF_TEMPERATURE_UNIT_FAHRENHEIT },
{ "KELVIN" , FF_TEMPERATURE_UNIT_KELVIN },
{ "K" , FF_TEMPERATURE_UNIT_KELVIN },
{},
});
}
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--percent-type" ))
2023-08-24 09:35:17 +08:00
instance . config . percentType = ( uint8_t ) ffOptionParseUInt32 ( key , value );
else if ( ffStrEqualsIgnCase ( key , "--percent-ndigits" ))
instance . config . percentNdigits = ( uint8_t ) ffOptionParseUInt32 ( key , value );
2023-06-19 15:21:49 +08:00
else if ( ffStrEqualsIgnCase ( key , "--no-buffer" ))
2023-06-24 10:59:53 +08:00
instance . config . noBuffer = ffOptionParseBoolean ( value );
2023-09-22 15:00:58 +08:00
else if ( ffStrStartsWithIgnCase ( key , "--bar-" ))
2023-08-17 16:00:24 +08:00
{
2023-09-22 15:00:58 +08:00
const char * subkey = key + strlen ( "--bar-" );
if ( ffStrEqualsIgnCase ( subkey , "char-elapsed" ))
2023-08-17 16:00:24 +08:00
ffOptionParseString ( key , value , & instance . config . barCharElapsed );
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "char-total" ))
2023-08-17 16:00:24 +08:00
ffOptionParseString ( key , value , & instance . config . barCharTotal );
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "width" ))
2023-08-17 16:00:24 +08:00
instance . config . barWidth = ( uint8_t ) ffOptionParseUInt32 ( key , value );
2023-09-22 15:00:58 +08:00
else if ( ffStrEqualsIgnCase ( subkey , "border" ))
2023-08-17 16:00:24 +08:00
instance . config . barBorder = ffOptionParseBoolean ( value );
else
goto error ;
}
2022-04-02 14:35:43 +02:00
///////////////////
//Library options//
///////////////////
2023-10-25 16:24:24 +08:00
else if ( ffOptionsParseLibraryCommandLine ( & instance . config . library , key , value )) {}
2022-04-02 14:35:43 +02:00
2023-08-17 16:00:24 +08:00
///////////////////////
//Module args options//
///////////////////////
else if ( ffParseModuleOptions ( key , value )) {}
2022-04-02 14:35:43 +02:00
//////////////////
//Unknown option//
//////////////////
2021-12-19 22:39:22 +01:00
2021-03-03 19:59:39 +01:00
else
{
2022-10-22 13:02:04 +08:00
error :
2021-04-03 11:39:59 +02:00
fprintf ( stderr , "Error: unknown option: %s \n " , key );
2021-03-05 23:00:28 +01:00
exit ( 400 );
2021-03-03 19:59:39 +01:00
}
}
2023-06-24 10:59:53 +08:00
static void parseConfigFiles ( FFdata * data )
2022-07-21 14:37:13 +02:00
{
2023-10-23 13:39:07 +08:00
if ( __builtin_expect ( ! instance . state . migrateConfigDoc , true ))
2023-06-10 21:34:41 +08:00
{
2023-10-23 13:39:07 +08:00
for ( uint32_t i = instance . state . platform . configDirs . length ; i > 0 ; -- i )
{
FFstrbuf * dir = ffListGet ( & instance . state . platform . configDirs , i - 1 );
uint32_t dirLength = dir -> length ;
2023-06-10 21:34:41 +08:00
2023-10-23 13:39:07 +08:00
ffStrbufAppendS ( dir , "fastfetch/config.jsonc" );
bool success = parseJsoncFile ( dir -> chars );
ffStrbufSubstrBefore ( dir , dirLength );
if ( success ) return ;
}
2023-06-10 21:34:41 +08:00
}
2023-10-23 13:39:07 +08:00
for ( uint32_t i = instance . state . platform . configDirs . length ; i > 0 ; -- i )
2023-01-02 17:27:06 +08:00
{
2023-10-23 13:39:07 +08:00
if ( ! data -> loadUserConfig )
2023-01-07 22:51:47 +01:00
return ;
2021-02-27 20:31:54 +01:00
2023-06-24 10:59:53 +08:00
FFstrbuf * dir = ffListGet ( & instance . state . platform . configDirs , i - 1 );
2023-01-07 22:51:47 +01:00
uint32_t dirLength = dir -> length ;
2023-01-02 17:27:06 +08:00
2023-01-07 22:51:47 +01:00
ffStrbufAppendS ( dir , "fastfetch/config.conf" );
2023-06-24 10:59:53 +08:00
parseConfigFile ( data , dir -> chars );
2023-01-07 22:51:47 +01:00
ffStrbufSubstrBefore ( dir , dirLength );
2023-01-02 17:27:06 +08:00
}
2021-03-03 19:59:39 +01:00
}
2023-06-24 10:59:53 +08:00
static void parseArguments ( FFdata * data , int argc , const char ** argv )
2021-03-03 19:59:39 +01:00
{
2022-07-21 14:37:13 +02:00
if ( ! data -> loadUserConfig )
return ;
2021-03-03 19:59:39 +01:00
for ( int i = 1 ; i < argc ; i ++ )
{
2023-08-17 16:00:24 +08:00
const char * key = argv [ i ];
if ( * key != '-' )
{
fprintf ( stderr , "Error: invalid option: %s. An option must start with `-` \n " , key );
exit ( 400 );
}
2021-11-18 18:25:24 +01:00
if ( i == argc - 1 || (
2023-08-25 10:32:03 +08:00
argv [ i + 1 ][ 0 ] == '-' &&
argv [ i + 1 ][ 1 ] != '\0' && // `-` is used as an alias for `/dev/stdin`
2021-11-18 18:25:24 +01:00
strcasecmp ( argv [ i ], "--separator-string" ) != 0 // Separator string can start with a -
)) {
2023-06-24 10:59:53 +08:00
parseOption ( data , argv [ i ], NULL );
2021-03-03 19:59:39 +01:00
}
else
{
2023-06-24 10:59:53 +08:00
parseOption ( data , argv [ i ], argv [ i + 1 ]);
2021-03-03 19:59:39 +01:00
++ i ;
}
}
2021-02-27 20:31:54 +01:00
}
2023-10-25 10:18:07 +08:00
static void run ( FFdata * data )
2021-03-03 19:59:39 +01:00
{
2023-07-05 14:21:35 +08:00
if ( instance . state . configDoc )
{
const char * error = NULL ;
2023-10-25 13:39:13 +08:00
yyjson_val * const root = yyjson_doc_get_root ( instance . state . configDoc );
if ( ! yyjson_is_obj ( root ))
error = "Invalid JSON config format. Root value must be an object" ;
2023-07-05 14:21:35 +08:00
if (
2023-10-25 13:39:13 +08:00
error ||
2023-10-25 14:43:46 +08:00
( error = ffOptionsParseLogoJsonConfig ( & instance . config . logo , root )) ||
2023-10-25 14:37:48 +08:00
( error = ffOptionsParseGeneralJsonConfig ( & instance . config . general , root )) ||
2023-10-25 09:13:04 +08:00
( error = ffParseDisplayJsonConfig ( & instance . config )) ||
2023-10-25 16:24:24 +08:00
( error = ffOptionsParseLibraryJsonConfig ( & instance . config . library , root )) ||
2023-07-05 14:21:35 +08:00
false
) {
fputs ( error , stderr );
exit ( 477 );
}
}
2023-10-23 13:39:07 +08:00
const bool useJsonConfig = data -> structure . length == 0 && instance . state . configDoc ;
2023-08-18 13:28:07 +08:00
2023-10-23 13:39:07 +08:00
if ( useJsonConfig )
2023-08-29 11:13:55 +08:00
ffPrintJsonConfig ( true /* prepare */ );
2023-08-18 13:28:07 +08:00
else
2023-10-23 13:39:07 +08:00
ffPrepareCommandOption ( data );
2022-09-29 17:14:50 +08:00
2023-06-24 10:59:53 +08:00
ffStart ();
2021-03-03 19:59:39 +01:00
2023-06-13 11:08:49 +08:00
#if defined(_WIN32)
if ( ! instance . config . noBuffer ) fflush ( stdout );
2023-01-12 00:11:25 +08:00
#endif
2023-08-18 13:28:07 +08:00
if ( useJsonConfig )
ffPrintJsonConfig ( false );
2023-06-11 00:51:29 +08:00
else
2023-10-23 13:39:07 +08:00
ffPrintCommandOption ( data );
2023-06-11 00:51:29 +08:00
2023-08-29 11:13:55 +08:00
if ( instance . state . resultDoc )
2023-09-18 20:12:33 +08:00
yyjson_mut_write_fp ( stdout , instance . state . resultDoc , YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES , NULL , NULL );
2023-10-23 13:39:07 +08:00
else
ffFinish ();
}
2023-10-25 10:18:07 +08:00
static void migrateConfig ( FFdata * data )
{
2023-10-25 14:43:46 +08:00
ffOptionsGenerateLogoJsonConfig ( & instance . config . logo , instance . state . migrateConfigDoc );
2023-10-25 10:18:07 +08:00
ffMigrateCommandOptionToJsonc ( data );
}
2023-10-23 13:39:07 +08:00
int main ( int argc , const char ** argv )
{
ffInitInstance ();
//Data stores things only needed for the configuration of fastfetch
FFdata data = {
. structure = ffStrbufCreate (),
. customValues = ffListCreate ( sizeof ( FFCustomValue )),
. loadUserConfig = true ,
};
2022-09-22 22:02:58 +02:00
2023-10-23 13:39:07 +08:00
if ( ! getenv ( "NO_CONFIG" ))
parseConfigFiles ( & data );
parseArguments ( & data , argc , argv );
if ( __builtin_expect ( ! instance . state . migrateConfigDoc , true ))
2023-10-25 10:18:07 +08:00
run ( & data );
2023-10-23 13:39:07 +08:00
else
2023-10-25 10:18:07 +08:00
migrateConfig ( & data );
2022-09-22 22:02:58 +02:00
2022-09-22 21:28:20 +08:00
ffStrbufDestroy ( & data . structure );
2023-07-03 16:22:16 +08:00
FF_LIST_FOR_EACH ( FFCustomValue , customValue , data . customValues )
{
ffStrbufDestroy ( & customValue -> key );
ffStrbufDestroy ( & customValue -> value );
}
ffListDestroy ( & data . customValues );
2021-03-03 19:59:39 +01:00
2023-06-24 10:59:53 +08:00
ffDestroyInstance ();
2021-02-18 22:25:36 +01:00
}