diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index e2f4a6b232..2b2b902543 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -1,562 +1,601 @@ -#include "packages.h" +#include "common/FFstrbuf.h" #include "common/io.h" #include "common/parsing.h" #include "common/properties.h" #include "common/settings.h" #include "common/stringUtils.h" #include "detection/os/os.h" +#include "packages.h" -static uint32_t getNumElements(FFstrbuf* baseDir, const char* dirname, bool isdir) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - uint32_t num_elements = ffPackagesGetNumElements(baseDir->chars, isdir); - ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; +static uint32_t getNumElements(FFstrbuf *baseDir, const char *dirname, + bool isdir) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t num_elements = ffPackagesGetNumElements(baseDir->chars, isdir); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; } -static uint32_t getNumStringsImpl(const char* filename, const char* needle) -{ - FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); - if (!ffReadFileBuffer(filename, &content)) - return 0; - - uint32_t count = 0; - char *iter = content.chars; - size_t needleLength = strlen(needle); - while ((iter = memmem(iter, content.length - (size_t)(iter - content.chars), needle, needleLength)) != NULL) - { - ++count; - iter += needleLength; - } - - return count; +static uint32_t getNumStringsImpl(const char *filename, const char *needle) { + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + if (!ffReadFileBuffer(filename, &content)) + return 0; + + uint32_t count = 0; + char *iter = content.chars; + size_t needleLength = strlen(needle); + while ((iter = memmem(iter, content.length - (size_t)(iter - content.chars), + needle, needleLength)) != NULL) { + ++count; + iter += needleLength; + } + + return count; } -static uint32_t getNumStrings(FFstrbuf* baseDir, const char* filename, const char* needle, const char* packageId) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, filename); +static uint32_t getNumStrings(FFstrbuf *baseDir, const char *filename, + const char *needle, const char *packageId) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, filename); - FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); - FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); - - uint32_t num_elements; - if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) - { - ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; - } + FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); - num_elements = getNumStringsImpl(baseDir->chars, needle); + uint32_t num_elements; + if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, + &num_elements)) { ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; + } - ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); + num_elements = getNumStringsImpl(baseDir->chars, needle); + ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; -} + ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); -static uint32_t getSQLite3Int(FFstrbuf* baseDir, const char* dbPath, const char* query, const char* packageId) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dbPath); + return num_elements; +} - FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); - FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); +static uint32_t getSQLite3Int(FFstrbuf *baseDir, const char *dbPath, + const char *query, const char *packageId) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dbPath); - uint32_t num_elements; - if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) - { - ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; - } + FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); - num_elements = (uint32_t) ffSettingsGetSQLite3Int(baseDir->chars, query); + uint32_t num_elements; + if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, + &num_elements)) { ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; + } - ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); + num_elements = (uint32_t)ffSettingsGetSQLite3Int(baseDir->chars, query); + ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; + ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); + + return num_elements; } -static uint32_t countFilesRecursiveImpl(FFstrbuf* baseDirPath, const char* filename) -{ - uint32_t baseDirPathLength = baseDirPath->length; +static uint32_t countFilesRecursiveImpl(FFstrbuf *baseDirPath, + const char *filename) { + uint32_t baseDirPathLength = baseDirPath->length; - ffStrbufAppendC(baseDirPath, '/'); - ffStrbufAppendS(baseDirPath, filename); - bool exists = ffPathExists(baseDirPath->chars, FF_PATHTYPE_FILE); - ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); - if(exists) - return 1; + ffStrbufAppendC(baseDirPath, '/'); + ffStrbufAppendS(baseDirPath, filename); + bool exists = ffPathExists(baseDirPath->chars, FF_PATHTYPE_FILE); + ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); + if (exists) + return 1; - DIR* dirp = opendir(baseDirPath->chars); - if(dirp == NULL) - return 0; + DIR *dirp = opendir(baseDirPath->chars); + if (dirp == NULL) + return 0; - ffStrbufAppendC(baseDirPath, '/'); - baseDirPathLength = baseDirPath->length; + ffStrbufAppendC(baseDirPath, '/'); + baseDirPathLength = baseDirPath->length; - uint32_t sum = 0; + uint32_t sum = 0; - struct dirent *entry; - while((entry = readdir(dirp)) != NULL) { - // According to the PMS, neither category nor package name can begin with '.', so no need to check for . or .. specifically - if(entry->d_type != DT_DIR || entry->d_name[0] == '.') - continue; + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + // According to the PMS, neither category nor package name can begin with + // '.', so no need to check for . or .. specifically + if (entry->d_type != DT_DIR || entry->d_name[0] == '.') + continue; - ffStrbufAppendS(baseDirPath, entry->d_name); - sum += countFilesRecursiveImpl(baseDirPath, filename); - ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); - } + ffStrbufAppendS(baseDirPath, entry->d_name); + sum += countFilesRecursiveImpl(baseDirPath, filename); + ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); + } - closedir(dirp); - return sum; + closedir(dirp); + return sum; } -static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, const char* filename) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - uint32_t sum = countFilesRecursiveImpl(baseDir, filename); - ffStrbufSubstrBefore(baseDir, baseDirLength); - return sum; +static uint32_t countFilesRecursive(FFstrbuf *baseDir, const char *dirname, + const char *filename) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t sum = countFilesRecursiveImpl(baseDir, filename); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return sum; } -static uint32_t getXBPSImpl(FFstrbuf* baseDir) -{ - DIR* dir = opendir(baseDir->chars); - if(dir == NULL) - return 0; +static uint32_t getXBPSImpl(FFstrbuf *baseDir) { + DIR *dir = opendir(baseDir->chars); + if (dir == NULL) + return 0; - uint32_t result = 0; + uint32_t result = 0; - struct dirent *entry; - while((entry = readdir(dir)) != NULL) - { - if(entry->d_type != DT_REG || !ffStrStartsWithIgnCase(entry->d_name, "pkgdb-")) - continue; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_REG || + !ffStrStartsWithIgnCase(entry->d_name, "pkgdb-")) + continue; - ffStrbufAppendC(baseDir, '/'); - ffStrbufAppendS(baseDir, entry->d_name); - result = getNumStringsImpl(baseDir->chars, "installed"); - break; - } + ffStrbufAppendC(baseDir, '/'); + ffStrbufAppendS(baseDir, entry->d_name); + result = getNumStringsImpl(baseDir->chars, "installed"); + break; + } - closedir(dir); - return result; + closedir(dir); + return result; } -static uint32_t getXBPS(FFstrbuf* baseDir, const char* dirname) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - uint32_t result = getXBPSImpl(baseDir); - ffStrbufSubstrBefore(baseDir, baseDirLength); - return result; +static uint32_t getXBPS(FFstrbuf *baseDir, const char *dirname) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t result = getXBPSImpl(baseDir); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return result; } -static uint32_t getSnap(FFstrbuf* baseDir) -{ - uint32_t result = getNumElements(baseDir, "/snap", true); +static uint32_t getSnap(FFstrbuf *baseDir) { + uint32_t result = getNumElements(baseDir, "/snap", true); - if (result == 0) - result = getNumElements(baseDir, "/var/lib/snapd/snap", true); + if (result == 0) + result = getNumElements(baseDir, "/var/lib/snapd/snap", true); - //Accounting for the /snap/bin folder - return result > 0 ? result - 1 : 0; + // Accounting for the /snap/bin folder + return result > 0 ? result - 1 : 0; } #ifdef FF_HAVE_RPM #include "common/library.h" -#include -#include #include +#include #include +#include -static uint32_t getRpmFromLibrpm(void) -{ - FF_LIBRARY_LOAD(rpm, 0, "librpm" FF_LIBRARY_EXTENSION, 12) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmReadConfigFiles, 0) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsCreate, 0) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsInitIterator, 0) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbGetIteratorCount, 0) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbFreeIterator, 0) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsFree, 0) - FF_LIBRARY_LOAD_SYMBOL(rpm, rpmlogSetMask, 0) - - // Don't print any error messages - ffrpmlogSetMask(RPMLOG_MASK(RPMLOG_EMERG)); - - if(ffrpmReadConfigFiles(NULL, NULL) != 0) - return 0; - - rpmts ts = ffrpmtsCreate(); - if(ts == NULL) - return 0; - - rpmdbMatchIterator mi = ffrpmtsInitIterator(ts, RPMDBI_LABEL, NULL, 0); - if(mi == NULL) - { - ffrpmtsFree(ts); - return 0; - } +static uint32_t getRpmFromLibrpm(void) { + FF_LIBRARY_LOAD(rpm, 0, "librpm" FF_LIBRARY_EXTENSION, 12) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmReadConfigFiles, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsCreate, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsInitIterator, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbGetIteratorCount, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbFreeIterator, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsFree, 0) + FF_LIBRARY_LOAD_SYMBOL(rpm, rpmlogSetMask, 0) + + // Don't print any error messages + ffrpmlogSetMask(RPMLOG_MASK(RPMLOG_EMERG)); + + if (ffrpmReadConfigFiles(NULL, NULL) != 0) + return 0; - int count = ffrpmdbGetIteratorCount(mi); + rpmts ts = ffrpmtsCreate(); + if (ts == NULL) + return 0; - ffrpmdbFreeIterator(mi); + rpmdbMatchIterator mi = ffrpmtsInitIterator(ts, RPMDBI_LABEL, NULL, 0); + if (mi == NULL) { ffrpmtsFree(ts); + return 0; + } - return count > 0 ? (uint32_t) count : 0; + int count = ffrpmdbGetIteratorCount(mi); + + ffrpmdbFreeIterator(mi); + ffrpmtsFree(ts); + + return count > 0 ? (uint32_t)count : 0; } -#endif //FF_HAVE_RPM - -static uint32_t getAMPackages(FFstrbuf* baseDir) -{ - uint32_t baseLength = baseDir->length; - FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); - if (!dirp) return 0; - - uint32_t result = 0; - struct dirent *entry; - while ((entry = readdir(dirp)) != NULL) - { - if (entry->d_name[0] == '.') continue; - if (entry->d_type == DT_DIR) - { - ffStrbufAppendF(baseDir, "/%s/remove", entry->d_name); - if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) - ++result; - ffStrbufSubstrBefore(baseDir, baseLength); - } +#endif // FF_HAVE_RPM + +static uint32_t getAMPackages(FFstrbuf *baseDir) { + uint32_t baseLength = baseDir->length; + FF_AUTO_CLOSE_DIR DIR *dirp = opendir(baseDir->chars); + if (!dirp) + return 0; + + uint32_t result = 0; + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') + continue; + if (entry->d_type == DT_DIR) { + ffStrbufAppendF(baseDir, "/%s/remove", entry->d_name); + if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) + ++result; + ffStrbufSubstrBefore(baseDir, baseLength); } - return result; + } + return result; } -static uint32_t getAMSystem(FFstrbuf* baseDir) -{ - // #771 - uint32_t baseDirLength = baseDir->length; +static uint32_t getAMSystem(FFstrbuf *baseDir) { + // #771 + uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, "/opt"); - uint32_t optDirLength = baseDir->length; + ffStrbufAppendS(baseDir, "/opt"); + uint32_t optDirLength = baseDir->length; - uint32_t result = 0; + uint32_t result = 0; - ffStrbufAppendS(baseDir, "/am/APP-MANAGER"); - if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) - { - ++result; // `am` itself is counted as a package too - ffStrbufSubstrBefore(baseDir, optDirLength); - result = getAMPackages(baseDir); - } + ffStrbufAppendS(baseDir, "/am/APP-MANAGER"); + if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) { + ++result; // `am` itself is counted as a package too + ffStrbufSubstrBefore(baseDir, optDirLength); + result = getAMPackages(baseDir); + } - ffStrbufSubstrBefore(baseDir, baseDirLength); - return result; + ffStrbufSubstrBefore(baseDir, baseDirLength); + return result; } -static uint32_t getAMUser(void) -{ - if (instance.state.platform.configDirs.length == 0) return 0; - - // check if $XDG_CONFIG_HOME/appman/appman-config exists - FFstrbuf* baseDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); - uint32_t baseLen = baseDir->length; - ffStrbufAppendS(baseDir, "appman/appman-config"); - FF_STRBUF_AUTO_DESTROY packagesPath = ffStrbufCreate(); - if (ffReadFileBuffer(baseDir->chars, &packagesPath)) - ffStrbufTrimRightSpace(&packagesPath); - ffStrbufSubstrBefore(baseDir, baseLen); - - return packagesPath.length > 0 ? getAMPackages(&packagesPath) : 0; +static uint32_t getAMUser(void) { + if (instance.state.platform.configDirs.length == 0) + return 0; + + // check if $XDG_CONFIG_HOME/appman/appman-config exists + FFstrbuf *baseDir = + FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); + uint32_t baseLen = baseDir->length; + ffStrbufAppendS(baseDir, "appman/appman-config"); + FF_STRBUF_AUTO_DESTROY packagesPath = ffStrbufCreate(); + if (ffReadFileBuffer(baseDir->chars, &packagesPath)) + ffStrbufTrimRightSpace(&packagesPath); + ffStrbufSubstrBefore(baseDir, baseLen); + + return packagesPath.length > 0 ? getAMPackages(&packagesPath) : 0; } -static int compareHash(const void* a, const void* b) -{ - return memcmp(a, b, 32); +static int compareHash(const void *a, const void *b) { + return memcmp(a, b, 32); } -static uint32_t getGuixPackagesImpl(char* filename) -{ - FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); - if (!ffAppendFileBuffer(filename, &content)) - return 0; - - // Count number of unique /gnu/store/ paths in PROFILE/manifest based on their hash value. - // Contains packages explicitly installed and their propagated inputs. - char* pend = content.chars; - - for (const char* pattern = content.chars; (pattern = strstr(pattern, "/gnu/store/")); pattern += 32) - { - pattern += strlen("/gnu/store/"); - memmove(pend, pattern, 32); - pend += 32; - } +static uint32_t getGuixPackagesImpl(char *filename) { + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + if (!ffAppendFileBuffer(filename, &content)) + return 0; + + // Count number of unique /gnu/store/ paths in PROFILE/manifest based on their + // hash value. Contains packages explicitly installed and their propagated + // inputs. + char *pend = content.chars; + + for (const char *pattern = content.chars; + (pattern = strstr(pattern, "/gnu/store/")); pattern += 32) { + pattern += strlen("/gnu/store/"); + memmove(pend, pattern, 32); + pend += 32; + } - if (pend == content.chars) - return 0; + if (pend == content.chars) + return 0; - qsort(content.chars, (size_t) (pend - content.chars) / 32, 32, compareHash); + qsort(content.chars, (size_t)(pend - content.chars) / 32, 32, compareHash); - uint32_t count = 1; - for (const char* p = content.chars + 32; p < pend; p += 32) - count += compareHash(p - 32, p) != 0; + uint32_t count = 1; + for (const char *p = content.chars + 32; p < pend; p += 32) + count += compareHash(p - 32, p) != 0; - return count; + return count; } -static uint32_t getGuixPackages(FFstrbuf* baseDir, const char* dirname) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - ffStrbufAppendS(baseDir, "/manifest"); - uint32_t num_elements = getGuixPackagesImpl(baseDir->chars); - ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; +static uint32_t getGuixPackages(FFstrbuf *baseDir, const char *dirname) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + ffStrbufAppendS(baseDir, "/manifest"); + uint32_t num_elements = getGuixPackagesImpl(baseDir->chars); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; } -static inline uint32_t getFlatpakRuntimePackagesArch(FFstrbuf* baseDir) -{ - FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); - if (dirp == NULL) - return 0; - - uint32_t num_elements = 0; - - struct dirent *entry; - while ((entry = readdir(dirp)) != NULL) - { - if(entry->d_type == DT_DIR && entry->d_name[0] != '.') - { - num_elements += getNumElements(baseDir, entry->d_name, true); - } +static inline uint32_t getFlatpakRuntimePackagesArch(FFstrbuf *baseDir) { + FF_AUTO_CLOSE_DIR DIR *dirp = opendir(baseDir->chars); + if (dirp == NULL) + return 0; + + uint32_t num_elements = 0; + + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { + num_elements += getNumElements(baseDir, entry->d_name, true); } + } - return num_elements; + return num_elements; } -static inline uint32_t getFlatpakRuntimePackages(FFstrbuf* baseDir) -{ - ffStrbufAppendS(baseDir, "runtime/"); - FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); - if (dirp == NULL) - return 0; - - uint32_t runtimeDirLength = baseDir->length; - uint32_t num_elements = 0; - - struct dirent *entry; - while ((entry = readdir(dirp)) != NULL) - { - if(entry->d_type == DT_DIR && entry->d_name[0] != '.') - { - // `flatpak list` ignores `.Locale` and `.Debug` packages, and maybe others - const char* dot = strrchr(entry->d_name, '.'); - if (__builtin_expect(!dot, false)) continue; - dot++; - - if (ffStrEquals(dot, "Locale") || ffStrEquals(dot, "Debug")) - continue; - - ffStrbufAppendS(baseDir, entry->d_name); - ffStrbufAppendC(baseDir, '/'); - num_elements += getFlatpakRuntimePackagesArch(baseDir); - ffStrbufSubstrBefore(baseDir, runtimeDirLength); - } +static inline uint32_t getFlatpakRuntimePackages(FFstrbuf *baseDir) { + ffStrbufAppendS(baseDir, "runtime/"); + FF_AUTO_CLOSE_DIR DIR *dirp = opendir(baseDir->chars); + if (dirp == NULL) + return 0; + + uint32_t runtimeDirLength = baseDir->length; + uint32_t num_elements = 0; + + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { + // `flatpak list` ignores `.Locale` and `.Debug` packages, and maybe + // others + const char *dot = strrchr(entry->d_name, '.'); + if (__builtin_expect(!dot, false)) + continue; + dot++; + + if (ffStrEquals(dot, "Locale") || ffStrEquals(dot, "Debug")) + continue; + + ffStrbufAppendS(baseDir, entry->d_name); + ffStrbufAppendC(baseDir, '/'); + num_elements += getFlatpakRuntimePackagesArch(baseDir); + ffStrbufSubstrBefore(baseDir, runtimeDirLength); } + } - return num_elements; + return num_elements; } -static inline uint32_t getFlatpakAppPackages(FFstrbuf* baseDir) -{ - ffStrbufAppendS(baseDir, "app/"); - FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); - if (dirp == NULL) - return 0; - - uint32_t appDirLength = baseDir->length; - uint32_t num_elements = 0; - - struct dirent *entry; - while ((entry = readdir(dirp)) != NULL) - { - if(entry->d_type == DT_DIR && entry->d_name[0] != '.') - { - ffStrbufAppendS(baseDir, entry->d_name); - ffStrbufAppendS(baseDir, "/current"); - if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) // Exclude deleted apps, #1856 - ++num_elements; - ffStrbufSubstrBefore(baseDir, appDirLength); - } +static inline uint32_t getFlatpakAppPackages(FFstrbuf *baseDir) { + ffStrbufAppendS(baseDir, "app/"); + FF_AUTO_CLOSE_DIR DIR *dirp = opendir(baseDir->chars); + if (dirp == NULL) + return 0; + + uint32_t appDirLength = baseDir->length; + uint32_t num_elements = 0; + + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { + ffStrbufAppendS(baseDir, entry->d_name); + ffStrbufAppendS(baseDir, "/current"); + if (ffPathExists(baseDir->chars, + FF_PATHTYPE_ANY)) // Exclude deleted apps, #1856 + ++num_elements; + ffStrbufSubstrBefore(baseDir, appDirLength); } - return num_elements; + } + return num_elements; } -static uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname) -{ - uint32_t num_elements = 0; - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - ffStrbufAppendS(baseDir, "/flatpak/"); - uint32_t flatpakDirLength = baseDir->length; +static uint32_t getFlatpakPackages(FFstrbuf *baseDir, const char *dirname) { + uint32_t num_elements = 0; + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + ffStrbufAppendS(baseDir, "/flatpak/"); + uint32_t flatpakDirLength = baseDir->length; - num_elements += getFlatpakAppPackages(baseDir); - ffStrbufSubstrBefore(baseDir, flatpakDirLength); + num_elements += getFlatpakAppPackages(baseDir); + ffStrbufSubstrBefore(baseDir, flatpakDirLength); - num_elements += getFlatpakRuntimePackages(baseDir); + num_elements += getFlatpakRuntimePackages(baseDir); - ffStrbufSubstrBefore(baseDir, baseDirLength); + ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; + return num_elements; } -static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) -{ - if (!(options->disabled & FF_PACKAGES_FLAG_APK_BIT)) packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk"); - if (!(options->disabled & FF_PACKAGES_FLAG_DPKG_BIT)) packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg"); - if (!(options->disabled & FF_PACKAGES_FLAG_LPKG_BIT)) packageCounts->lpkg += getNumStrings(baseDir, "/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list", "\n", "lpkg"); - if (!(options->disabled & FF_PACKAGES_FLAG_EMERGE_BIT)) packageCounts->emerge += countFilesRecursive(baseDir, "/var/db/pkg", "SIZE"); - if (!(options->disabled & FF_PACKAGES_FLAG_EOPKG_BIT)) packageCounts->eopkg += getNumElements(baseDir, "/var/lib/eopkg/package", true); - if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); - if (!(options->disabled & FF_PACKAGES_FLAG_KISS_BIT)) packageCounts->kiss += getNumElements(baseDir, "/var/db/kiss/installed", true); - if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) - { - packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); - packageCounts->nixSystem += ffPackagesGetNix(baseDir, "/run/current-system"); - } - if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) packageCounts->pacman += getNumElements(baseDir, "/var/lib/pacman/local", true); - if (!(options->disabled & FF_PACKAGES_FLAG_LPKGBUILD_BIT)) packageCounts->lpkgbuild += getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false); - if (!(options->disabled & FF_PACKAGES_FLAG_PKGTOOL_BIT)) packageCounts->pkgtool += getNumElements(baseDir, "/var/log/packages", false); - if (!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT)) - { - // `Sigmd5` is the only table that doesn't contain the virtual `gpg-pubkey` package - packageCounts->rpm += getSQLite3Int(baseDir, "/var/lib/rpm/rpmdb.sqlite", "SELECT count(*) FROM Sigmd5", "rpm"); - } - if (!(options->disabled & FF_PACKAGES_FLAG_SNAP_BIT)) packageCounts->snap += getSnap(baseDir); - if (!(options->disabled & FF_PACKAGES_FLAG_XBPS_BIT)) packageCounts->xbps += getXBPS(baseDir, "/var/db/xbps"); - if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT)) - { - packageCounts->brewCask += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Caskroom", true); - packageCounts->brew += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Cellar", true); - } - if (!(options->disabled & FF_PACKAGES_FLAG_PALUDIS_BIT)) packageCounts->paludis += countFilesRecursive(baseDir, "/var/db/paludis/repositories", "environment.bz2"); - if (!(options->disabled & FF_PACKAGES_FLAG_OPKG_BIT)) packageCounts->opkg += getNumStrings(baseDir, "/usr/lib/opkg/status", "Package:", "opkg"); // openwrt - if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) packageCounts->amSystem = getAMSystem(baseDir); - if (!(options->disabled & FF_PACKAGES_FLAG_SORCERY_BIT)) packageCounts->sorcery += getNumStrings(baseDir, "/var/state/sorcery/packages", ":installed:", "sorcery"); - if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) - { - packageCounts->guixSystem += getGuixPackages(baseDir, "/run/current-system/profile"); - } - if (!(options->disabled & FF_PACKAGES_FLAG_LINGLONG_BIT)) packageCounts->linglong += getNumElements(baseDir, "/var/lib/linglong/layers", true); - if (!(options->disabled & FF_PACKAGES_FLAG_PACSTALL_BIT)) packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false); - if (!(options->disabled & FF_PACKAGES_FLAG_PISI_BIT)) packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true); - if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); +static uint32_t getPacmanPackages(FFstrbuf *baseDir) { + FF_STRBUF_AUTO_DESTROY pacmanDir = ffStrbufCreate(); + + uint32_t baseDirLen = baseDir->length; + ffStrbufAppendS(&pacmanDir, "/etc/pacman.conf"); + if (!ffParsePropFile(baseDir->chars, "DBPath =", &pacmanDir)) + ffStrbufSetS(&pacmanDir, "/var/lib/pacman"); + ffStrbufSubstrBefore(&pacmanDir, baseDirLen); + ffStrbufTrimRight(&pacmanDir, '/'); + ffStrbufAppendS(&pacmanDir, "/local"); + return getNumElements(baseDir, pacmanDir.chars, true); } -static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) -{ - getPackageCounts(baseDir, packageCounts, options); - - if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) - { - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, FASTFETCH_TARGET_DIR_ETC "/pacman-mirrors.conf"); - if(ffParsePropFile(baseDir->chars, "Branch =", &packageCounts->pacmanBranch) && packageCounts->pacmanBranch.length == 0) - ffStrbufAppendS(&packageCounts->pacmanBranch, "stable"); - ffStrbufSubstrBefore(baseDir, baseDirLength); - } +static void getPackageCounts(FFstrbuf *baseDir, FFPackagesResult *packageCounts, + FFPackagesOptions *options) { + if (!(options->disabled & FF_PACKAGES_FLAG_APK_BIT)) + packageCounts->apk += + getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk"); + if (!(options->disabled & FF_PACKAGES_FLAG_DPKG_BIT)) + packageCounts->dpkg += + getNumStrings(baseDir, "/var/lib/dpkg/status", + "Status: install ok installed", "dpkg"); + if (!(options->disabled & FF_PACKAGES_FLAG_LPKG_BIT)) + packageCounts->lpkg += getNumStrings( + baseDir, "/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list", + "\n", "lpkg"); + if (!(options->disabled & FF_PACKAGES_FLAG_EMERGE_BIT)) + packageCounts->emerge += + countFilesRecursive(baseDir, "/var/db/pkg", "SIZE"); + if (!(options->disabled & FF_PACKAGES_FLAG_EOPKG_BIT)) + packageCounts->eopkg += + getNumElements(baseDir, "/var/lib/eopkg/package", true); + if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) + packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); + if (!(options->disabled & FF_PACKAGES_FLAG_KISS_BIT)) + packageCounts->kiss += + getNumElements(baseDir, "/var/db/kiss/installed", true); + if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { + packageCounts->nixDefault += + ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); + packageCounts->nixSystem += + ffPackagesGetNix(baseDir, "/run/current-system"); + } + // TODO: change to getPacmanPackages(): + if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) + packageCounts->pacman += getPacmanPackages(baseDir); + if (!(options->disabled & FF_PACKAGES_FLAG_LPKGBUILD_BIT)) + packageCounts->lpkgbuild += + getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false); + if (!(options->disabled & FF_PACKAGES_FLAG_PKGTOOL_BIT)) + packageCounts->pkgtool += + getNumElements(baseDir, "/var/log/packages", false); + if (!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT)) { + // `Sigmd5` is the only table that doesn't contain the virtual `gpg-pubkey` + // package + packageCounts->rpm += getSQLite3Int(baseDir, "/var/lib/rpm/rpmdb.sqlite", + "SELECT count(*) FROM Sigmd5", "rpm"); + } + if (!(options->disabled & FF_PACKAGES_FLAG_SNAP_BIT)) + packageCounts->snap += getSnap(baseDir); + if (!(options->disabled & FF_PACKAGES_FLAG_XBPS_BIT)) + packageCounts->xbps += getXBPS(baseDir, "/var/db/xbps"); + if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT)) { + packageCounts->brewCask += + getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Caskroom", true); + packageCounts->brew += + getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Cellar", true); + } + if (!(options->disabled & FF_PACKAGES_FLAG_PALUDIS_BIT)) + packageCounts->paludis += countFilesRecursive( + baseDir, "/var/db/paludis/repositories", "environment.bz2"); + if (!(options->disabled & FF_PACKAGES_FLAG_OPKG_BIT)) + packageCounts->opkg += getNumStrings(baseDir, "/usr/lib/opkg/status", + "Package:", "opkg"); // openwrt + if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) + packageCounts->amSystem = getAMSystem(baseDir); + if (!(options->disabled & FF_PACKAGES_FLAG_SORCERY_BIT)) + packageCounts->sorcery += getNumStrings( + baseDir, "/var/state/sorcery/packages", ":installed:", "sorcery"); + if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) { + packageCounts->guixSystem += + getGuixPackages(baseDir, "/run/current-system/profile"); + } + if (!(options->disabled & FF_PACKAGES_FLAG_LINGLONG_BIT)) + packageCounts->linglong += + getNumElements(baseDir, "/var/lib/linglong/layers", true); + if (!(options->disabled & FF_PACKAGES_FLAG_PACSTALL_BIT)) + packageCounts->pacstall += + getNumElements(baseDir, "/var/lib/pacstall/metadata", false); + if (!(options->disabled & FF_PACKAGES_FLAG_PISI_BIT)) + packageCounts->pisi += + getNumElements(baseDir, "/var/lib/pisi/package", true); + if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) + packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); } -static void getPackageCountsBedrock(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) -{ - uint32_t baseDirLength = baseDir->length; +static void getPackageCountsRegular(FFstrbuf *baseDir, + FFPackagesResult *packageCounts, + FFPackagesOptions *options) { + getPackageCounts(baseDir, packageCounts, options); - ffStrbufAppendS(baseDir, "/bedrock/strata"); + if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) { + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, FASTFETCH_TARGET_DIR_ETC "/pacman-mirrors.conf"); + if (ffParsePropFile(baseDir->chars, + "Branch =", &packageCounts->pacmanBranch) && + packageCounts->pacmanBranch.length == 0) + ffStrbufAppendS(&packageCounts->pacmanBranch, "stable"); + ffStrbufSubstrBefore(baseDir, baseDirLength); + } +} - FF_AUTO_CLOSE_DIR DIR* dir = opendir(baseDir->chars); - if(dir == NULL) - { - ffStrbufSubstrBefore(baseDir, baseDirLength); - return; - } +static void getPackageCountsBedrock(FFstrbuf *baseDir, + FFPackagesResult *packageCounts, + FFPackagesOptions *options) { + uint32_t baseDirLength = baseDir->length; - ffStrbufAppendC(baseDir, '/'); - uint32_t baseDirLength2 = baseDir->length; - - struct dirent* entry; - while((entry = readdir(dir)) != NULL) - { - if(entry->d_type != DT_DIR) - continue; - if(entry->d_name[0] == '.') - continue; - - ffStrbufAppendS(baseDir, entry->d_name); - getPackageCounts(baseDir, packageCounts, options); - ffStrbufSubstrBefore(baseDir, baseDirLength2); - } + ffStrbufAppendS(baseDir, "/bedrock/strata"); + FF_AUTO_CLOSE_DIR DIR *dir = opendir(baseDir->chars); + if (dir == NULL) { ffStrbufSubstrBefore(baseDir, baseDirLength); -} + return; + } -void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) -{ - FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(512); - ffStrbufAppendS(&baseDir, FASTFETCH_TARGET_DIR_ROOT); - - if(ffStrbufIgnCaseEqualS(&ffDetectOS()->id, "bedrock")) - getPackageCountsBedrock(&baseDir, result, options); - else - getPackageCountsRegular(&baseDir, result, options); - - // If SQL failed, we can still try with librpm. - // This is needed on openSUSE, which seems to use a proprietary database file - // This method doesn't work on bedrock, so we do it here. - #ifdef FF_HAVE_RPM - if(!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT) && result->rpm == 0) - result->rpm = getRpmFromLibrpm(); - #endif - - ffStrbufSet(&baseDir, &instance.state.platform.homeDir); - if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) - { - // Count packages from $HOME/.nix-profile - result->nixUser += ffPackagesGetNix(&baseDir, ".nix-profile"); - - // Check in $XDG_STATE_HOME/nix/profile - FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate(); - const char* stateHomeEnv = getenv("XDG_STATE_HOME"); - if (ffStrSet(stateHomeEnv)) - { - ffStrbufSetS(&stateHome, stateHomeEnv); - ffStrbufEnsureEndsWithC(&stateHome, '/'); - } - else - { - ffStrbufSet(&stateHome, &instance.state.platform.homeDir); - ffStrbufAppendS(&stateHome, ".local/state/"); - } - result->nixUser += ffPackagesGetNix(&stateHome, "nix/profile"); - - // Check in /etc/profiles/per-user/$USER - FF_STRBUF_AUTO_DESTROY userPkgsDir = ffStrbufCreateStatic("/etc/profiles/per-user/"); - result->nixUser += ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars); - } + ffStrbufAppendC(baseDir, '/'); + uint32_t baseDirLength2 = baseDir->length; - if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) - { - result->guixUser += getGuixPackages(&baseDir, ".guix-profile"); - result->guixHome += getGuixPackages(&baseDir, ".guix-home/profile"); - } + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_DIR) + continue; + if (entry->d_name[0] == '.') + continue; - if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) - result->flatpakUser = getFlatpakPackages(&baseDir, "/.local/share"); + ffStrbufAppendS(baseDir, entry->d_name); + getPackageCounts(baseDir, packageCounts, options); + ffStrbufSubstrBefore(baseDir, baseDirLength2); + } - if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) - result->amUser = getAMUser(); + ffStrbufSubstrBefore(baseDir, baseDirLength); +} - if (!(options->disabled & FF_PACKAGES_FLAG_SOAR_BIT)) - result->soar += getSQLite3Int(&baseDir, ".local/share/soar/db/soar.db", "SELECT COUNT(DISTINCT pkg_id || pkg_name) FROM packages WHERE is_installed = true", "soar"); +void ffDetectPackagesImpl(FFPackagesResult *result, + FFPackagesOptions *options) { + FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(512); + ffStrbufAppendS(&baseDir, FASTFETCH_TARGET_DIR_ROOT); + + if (ffStrbufIgnCaseEqualS(&ffDetectOS()->id, "bedrock")) + getPackageCountsBedrock(&baseDir, result, options); + else + getPackageCountsRegular(&baseDir, result, options); + +// If SQL failed, we can still try with librpm. +// This is needed on openSUSE, which seems to use a proprietary database file +// This method doesn't work on bedrock, so we do it here. +#ifdef FF_HAVE_RPM + if (!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT) && result->rpm == 0) + result->rpm = getRpmFromLibrpm(); +#endif + + ffStrbufSet(&baseDir, &instance.state.platform.homeDir); + if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { + // Count packages from $HOME/.nix-profile + result->nixUser += ffPackagesGetNix(&baseDir, ".nix-profile"); + + // Check in $XDG_STATE_HOME/nix/profile + FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate(); + const char *stateHomeEnv = getenv("XDG_STATE_HOME"); + if (ffStrSet(stateHomeEnv)) { + ffStrbufSetS(&stateHome, stateHomeEnv); + ffStrbufEnsureEndsWithC(&stateHome, '/'); + } else { + ffStrbufSet(&stateHome, &instance.state.platform.homeDir); + ffStrbufAppendS(&stateHome, ".local/state/"); + } + result->nixUser += ffPackagesGetNix(&stateHome, "nix/profile"); + + // Check in /etc/profiles/per-user/$USER + FF_STRBUF_AUTO_DESTROY userPkgsDir = + ffStrbufCreateStatic("/etc/profiles/per-user/"); + result->nixUser += + ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars); + } + + if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) { + result->guixUser += getGuixPackages(&baseDir, ".guix-profile"); + result->guixHome += getGuixPackages(&baseDir, ".guix-home/profile"); + } + + if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) + result->flatpakUser = getFlatpakPackages(&baseDir, "/.local/share"); + + if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) + result->amUser = getAMUser(); + + if (!(options->disabled & FF_PACKAGES_FLAG_SOAR_BIT)) + result->soar += getSQLite3Int(&baseDir, ".local/share/soar/db/soar.db", + "SELECT COUNT(DISTINCT pkg_id || pkg_name) " + "FROM packages WHERE is_installed = true", + "soar"); } diff --git a/src/fastfetch.c b/src/fastfetch.c index 60df8051f4..429be32d1c 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -1,191 +1,188 @@ #include "fastfetch.h" -#include "common/ffdata.h" -#include "detection/version/version.h" -#include "logo/logo.h" #include "common/commandoption.h" +#include "common/ffdata.h" #include "common/init.h" #include "common/io.h" #include "common/jsonconfig.h" -#include "common/time.h" -#include "common/stringUtils.h" #include "common/mallocHelper.h" +#include "common/stringUtils.h" +#include "common/time.h" +#include "detection/version/version.h" #include "fastfetch_datatext.h" +#include "logo/logo.h" -#include #include +#include #include #ifdef _WIN32 - #include "common/windows/getline.h" +#include "common/windows/getline.h" #endif -static void printCommandFormatHelpJson(void) -{ - yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val* root = yyjson_mut_obj(doc); - yyjson_mut_doc_set_root(doc, root); - - for (uint32_t i = 0; i <= 'Z' - 'A'; ++i) - { - for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules) - { - FFModuleBaseInfo* baseInfo = *modules; - if (!baseInfo->formatArgs.count) continue; - - FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name); - ffStrbufLowerCase(&type); - ffStrbufAppendS(&type, "Format"); - - yyjson_mut_val* obj = yyjson_mut_obj(doc); - if (yyjson_mut_obj_add(root, yyjson_mut_strbuf(doc, &type), obj)) - { - FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateF("Output format of the module `%s`. See Wiki for formatting syntax\n", baseInfo->name); - for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) - { - const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i]; - ffStrbufAppendF(&content, " %u. {%s}: %s\n", i + 1, arg->name, arg->desc); - } - ffStrbufTrimRight(&content, '\n'); - yyjson_mut_obj_add_strbuf(doc, obj, "description", &content); - yyjson_mut_obj_add_str(doc, obj, "type", "string"); - } +static void printCommandFormatHelpJson(void) { + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); + yyjson_mut_val *root = yyjson_mut_obj(doc); + yyjson_mut_doc_set_root(doc, root); + + for (uint32_t i = 0; i <= 'Z' - 'A'; ++i) { + for (FFModuleBaseInfo **modules = ffModuleInfos[i]; *modules; ++modules) { + FFModuleBaseInfo *baseInfo = *modules; + if (!baseInfo->formatArgs.count) + continue; + + FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name); + ffStrbufLowerCase(&type); + ffStrbufAppendS(&type, "Format"); + + yyjson_mut_val *obj = yyjson_mut_obj(doc); + if (yyjson_mut_obj_add(root, yyjson_mut_strbuf(doc, &type), obj)) { + FF_STRBUF_AUTO_DESTROY content = + ffStrbufCreateF("Output format of the module `%s`. See Wiki for " + "formatting syntax\n", + baseInfo->name); + for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) { + const FFModuleFormatArg *arg = &baseInfo->formatArgs.args[i]; + ffStrbufAppendF(&content, " %u. {%s}: %s\n", i + 1, arg->name, + arg->desc); } - } - yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_PRETTY, NULL, NULL); - putchar('\n'); - yyjson_mut_doc_free(doc); + ffStrbufTrimRight(&content, '\n'); + yyjson_mut_obj_add_strbuf(doc, obj, "description", &content); + yyjson_mut_obj_add_str(doc, obj, "type", "string"); + } + } + } + yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_PRETTY, NULL, NULL); + putchar('\n'); + yyjson_mut_doc_free(doc); } -static void printCommandFormatHelp(const char* command) -{ - FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS((uint32_t) (strlen(command) - strlen("-format")), command); - ffStrbufLowerCase(&type); - for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(command[0]) - 'A']; *modules; ++modules) - { - FFModuleBaseInfo* baseInfo = *modules; - if (ffStrbufIgnCaseEqualS(&type, baseInfo->name)) - { - if (baseInfo->formatArgs.count > 0) - { - FF_STRBUF_AUTO_DESTROY variable = ffStrbufCreate(); - printf("-- In config file: { \"type\": \"%s\", \"format\": \"{}\" }\n", type.chars); - printf("Sets the format string for %s output.\n", baseInfo->name); - puts("To see how a format string is constructed, take a look at https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide."); - puts("The following variables are passed:"); - - for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) - { - const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i]; - ffStrbufSetF(&variable, "{%s}", arg->name); - printf("%20s: %s\n", variable.chars, arg->desc); - } - } - else - fprintf(stderr, "Error: Module '%s' doesn't support output formatting\n", baseInfo->name); - return; +static void printCommandFormatHelp(const char *command) { + FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS( + (uint32_t)(strlen(command) - strlen("-format")), command); + ffStrbufLowerCase(&type); + for (FFModuleBaseInfo **modules = ffModuleInfos[toupper(command[0]) - 'A']; + *modules; ++modules) { + FFModuleBaseInfo *baseInfo = *modules; + if (ffStrbufIgnCaseEqualS(&type, baseInfo->name)) { + if (baseInfo->formatArgs.count > 0) { + FF_STRBUF_AUTO_DESTROY variable = ffStrbufCreate(); + printf("-- In config file: { \"type\": \"%s\", \"format\": " + "\"{}\" }\n", + type.chars); + printf("Sets the format string for %s output.\n", baseInfo->name); + puts("To see how a format string is constructed, take a look at " + "https://github.com/fastfetch-cli/fastfetch/wiki/" + "Format-String-Guide."); + puts("The following variables are passed:"); + + for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) { + const FFModuleFormatArg *arg = &baseInfo->formatArgs.args[i]; + ffStrbufSetF(&variable, "{%s}", arg->name); + printf("%20s: %s\n", variable.chars, arg->desc); } + } else + fprintf(stderr, + "Error: Module '%s' doesn't support output formatting\n", + baseInfo->name); + return; } + } - fprintf(stderr, "Error: Module '%s' is not supported\n", type.chars); + fprintf(stderr, "Error: Module '%s' is not supported\n", type.chars); } -static void printFullHelp() -{ - fputs("Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way\n\n", stdout); +static void printFullHelp() { + fputs("Fastfetch is a neofetch-like tool for fetching system information and " + "displaying them in a pretty way\n\n", + stdout); + if (!instance.config.display.pipe) + fputs("\e[1;4mUsage:\e[m \e[1mfastfetch\e[m \e[3m\e[m\n\n", + stdout); + else + fputs("Usage: fastfetch \n\n", stdout); + + yyjson_doc *doc = + yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, + strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG); + assert(doc); + yyjson_val *groupKey, *flagArr; + size_t groupIdx, groupMax; + yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, + flagArr) { if (!instance.config.display.pipe) - fputs("\e[1;4mUsage:\e[m \e[1mfastfetch\e[m \e[3m\e[m\n\n", stdout); - else - fputs("Usage: fastfetch \n\n", stdout); - - yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG); - assert(doc); - yyjson_val *groupKey, *flagArr; - size_t groupIdx, groupMax; - yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr) - { + fputs("\e[1;4m", stdout); + printf("%s options:", yyjson_get_str(groupKey)); + if (!instance.config.display.pipe) + fputs("\e[m", stdout); + putchar('\n'); + + yyjson_val *flagObj; + size_t flagIdx, flagMax; + yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj) { + yyjson_val *shortKey = yyjson_obj_get(flagObj, "short"); + if (shortKey) { + fputs(" ", stdout); if (!instance.config.display.pipe) - fputs("\e[1;4m", stdout); - printf("%s options:", yyjson_get_str(groupKey)); + fputs("\e[1m", stdout); + printf("-%s", yyjson_get_str(shortKey)); if (!instance.config.display.pipe) - fputs("\e[m", stdout); - putchar('\n'); - - yyjson_val* flagObj; - size_t flagIdx, flagMax; - yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj) - { - yyjson_val* shortKey = yyjson_obj_get(flagObj, "short"); - if (shortKey) - { - fputs(" ", stdout); - if (!instance.config.display.pipe) - fputs("\e[1m", stdout); - printf("-%s", yyjson_get_str(shortKey)); - if (!instance.config.display.pipe) - fputs("\e[m", stdout); - fputs(", ", stdout); - } - else - { - fputs(" ", stdout); - } - yyjson_val* longKey = yyjson_obj_get(flagObj, "long"); - assert(longKey); - if (!instance.config.display.pipe) - fputs("\e[1m", stdout); - printf("--%s", yyjson_get_str(longKey)); - if (!instance.config.display.pipe) - fputs("\e[m", stdout); - - yyjson_val* argObj = yyjson_obj_get(flagObj, "arg"); - if (argObj) - { - yyjson_val* typeKey = yyjson_obj_get(argObj, "type"); - assert(typeKey); - yyjson_val* optionalKey = yyjson_obj_get(argObj, "optional"); - bool optional = optionalKey && yyjson_get_bool(optionalKey); - putchar(' '); - if (!instance.config.display.pipe) - fputs("\e[3m", stdout); - printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey)); - if (!instance.config.display.pipe) - fputs("\e[m", stdout); - } - - yyjson_val* descKey = yyjson_obj_get(flagObj, "desc"); - assert(descKey); - if (yyjson_is_arr(descKey)) - { - if (instance.config.display.pipe) - putchar(':'); - - yyjson_val* descStr; - size_t descIdx, descMax; - yyjson_arr_foreach(descKey, descIdx, descMax, descStr) - { - if (!instance.config.display.pipe) - printf("\e[46G%s\n", yyjson_get_str(descStr)); - else - printf(" %s", yyjson_get_str(descStr)); - } - if (instance.config.display.pipe) - putchar('\n'); - } - else - { - if (!instance.config.display.pipe) - fputs("\e[46G", stdout); - else - fputs(": ", stdout); - puts(yyjson_get_str(descKey)); - } + fputs("\e[m", stdout); + fputs(", ", stdout); + } else { + fputs(" ", stdout); + } + yyjson_val *longKey = yyjson_obj_get(flagObj, "long"); + assert(longKey); + if (!instance.config.display.pipe) + fputs("\e[1m", stdout); + printf("--%s", yyjson_get_str(longKey)); + if (!instance.config.display.pipe) + fputs("\e[m", stdout); + + yyjson_val *argObj = yyjson_obj_get(flagObj, "arg"); + if (argObj) { + yyjson_val *typeKey = yyjson_obj_get(argObj, "type"); + assert(typeKey); + yyjson_val *optionalKey = yyjson_obj_get(argObj, "optional"); + bool optional = optionalKey && yyjson_get_bool(optionalKey); + putchar(' '); + if (!instance.config.display.pipe) + fputs("\e[3m", stdout); + printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey)); + if (!instance.config.display.pipe) + fputs("\e[m", stdout); + } + + yyjson_val *descKey = yyjson_obj_get(flagObj, "desc"); + assert(descKey); + if (yyjson_is_arr(descKey)) { + if (instance.config.display.pipe) + putchar(':'); + + yyjson_val *descStr; + size_t descIdx, descMax; + yyjson_arr_foreach(descKey, descIdx, descMax, descStr) { + if (!instance.config.display.pipe) + printf("\e[46G%s\n", yyjson_get_str(descStr)); + else + printf(" %s", yyjson_get_str(descStr)); } - - putchar('\n'); + if (instance.config.display.pipe) + putchar('\n'); + } else { + if (!instance.config.display.pipe) + fputs("\e[46G", stdout); + else + fputs(": ", stdout); + puts(yyjson_get_str(descKey)); + } } - yyjson_doc_free(doc); - puts("\n\ + putchar('\n'); + } + yyjson_doc_free(doc); + + puts("\n\ Command flags are not case sensitive. E.g. `--print-logos` is equal to `--Print-Logos`\n\ If a value starts with a ?, it is optional. An optional boolean value defaults to true if not specified.\n\ More detailed help messages for each options can be printed with `-h `\n\ @@ -193,714 +190,695 @@ For detailed information on logo options, module configuration, and formatting, https://github.com/fastfetch-cli/fastfetch/wiki/Configuration"); } -static bool printSpecificCommandHelp(const char* command) -{ - yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG); - assert(doc); - yyjson_val *groupKey, *flagArr; - size_t groupIdx, groupMax; - yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr) - { - yyjson_val* flagObj; - size_t flagIdx, flagMax; - yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj) - { - yyjson_val* pseudo = yyjson_obj_get(flagObj, "pseudo"); - if (pseudo && yyjson_get_bool(pseudo)) - continue; - - yyjson_val* longKey = yyjson_obj_get(flagObj, "long"); - assert(longKey); - if (ffStrEqualsIgnCase(command, yyjson_get_str(longKey))) - { - puts(yyjson_get_str(yyjson_obj_get(flagObj, "desc"))); - - printf("%10s: ", "Usage"); - yyjson_val* shortKey = yyjson_obj_get(flagObj, "short"); - if (shortKey) - { - if (!instance.config.display.pipe) - fputs("\e[1m", stdout); - printf("-%s", yyjson_get_str(shortKey)); - if (!instance.config.display.pipe) - fputs("\e[m", stdout); - fputs(", ", stdout); - } - if (!instance.config.display.pipe) - fputs("\e[1m", stdout); - printf("--%s", yyjson_get_str(longKey)); - if (!instance.config.display.pipe) - fputs("\e[m", stdout); - - yyjson_val* argObj = yyjson_obj_get(flagObj, "arg"); - if (argObj) - { - yyjson_val* typeKey = yyjson_obj_get(argObj, "type"); - assert(typeKey); - yyjson_val* optionalKey = yyjson_obj_get(argObj, "optional"); - bool optional = optionalKey && yyjson_get_bool(optionalKey); - putchar(' '); - if (!instance.config.display.pipe) - fputs("\e[3m", stdout); - printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey)); - if (!instance.config.display.pipe) - fputs("\e[m", stdout); - putchar('\n'); - - yyjson_val* defaultKey = yyjson_obj_get(argObj, "default"); - if (defaultKey) - { - if (ffStrEqualsIgnCase(yyjson_get_str(typeKey), "structure")) - printf("%10s: %s\n", "Default", FASTFETCH_DATATEXT_STRUCTURE); - else if (yyjson_is_bool(defaultKey)) - printf("%10s: %s\n", "Default", yyjson_get_bool(defaultKey) ? "true" : "false"); - else if (yyjson_is_num(defaultKey)) - printf("%10s: %d\n", "Default", yyjson_get_int(defaultKey)); - else if (yyjson_is_str(defaultKey)) - printf("%10s: %s\n", "Default", yyjson_get_str(defaultKey)); - else - printf("%10s: Unknown\n", "Default"); - } - - yyjson_val* enumKey = yyjson_obj_get(argObj, "enum"); - if (enumKey) - { - printf("%10s:\n", "Options"); - yyjson_val *optKey, *optVal; - size_t optIdx, optMax; - yyjson_obj_foreach(enumKey, optIdx, optMax, optKey, optVal) - printf("%12s: %s\n", yyjson_get_str(optKey), yyjson_get_str(optVal)); - } - } - else - putchar('\n'); - - yyjson_val* remarkKey = yyjson_obj_get(flagObj, "remark"); - if (remarkKey) - { - if (yyjson_is_str(remarkKey)) - printf("%10s: %s\n", "Remark", yyjson_get_str(remarkKey)); - else if (yyjson_is_arr(remarkKey) && yyjson_arr_size(remarkKey) > 0) - { - yyjson_val* remarkStr; - size_t remarkIdx, remarkMax; - yyjson_arr_foreach(remarkKey, remarkIdx, remarkMax, remarkStr) - { - if (remarkIdx == 0) - printf("%10s: %s\n", "Remark", yyjson_get_str(remarkStr)); - else - printf(" %s\n", yyjson_get_str(remarkStr)); - } - } - } - - yyjson_doc_free(doc); - return true; +static bool printSpecificCommandHelp(const char *command) { + yyjson_doc *doc = + yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, + strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG); + assert(doc); + yyjson_val *groupKey, *flagArr; + size_t groupIdx, groupMax; + yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, + flagArr) { + yyjson_val *flagObj; + size_t flagIdx, flagMax; + yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj) { + yyjson_val *pseudo = yyjson_obj_get(flagObj, "pseudo"); + if (pseudo && yyjson_get_bool(pseudo)) + continue; + + yyjson_val *longKey = yyjson_obj_get(flagObj, "long"); + assert(longKey); + if (ffStrEqualsIgnCase(command, yyjson_get_str(longKey))) { + puts(yyjson_get_str(yyjson_obj_get(flagObj, "desc"))); + + printf("%10s: ", "Usage"); + yyjson_val *shortKey = yyjson_obj_get(flagObj, "short"); + if (shortKey) { + if (!instance.config.display.pipe) + fputs("\e[1m", stdout); + printf("-%s", yyjson_get_str(shortKey)); + if (!instance.config.display.pipe) + fputs("\e[m", stdout); + fputs(", ", stdout); + } + if (!instance.config.display.pipe) + fputs("\e[1m", stdout); + printf("--%s", yyjson_get_str(longKey)); + if (!instance.config.display.pipe) + fputs("\e[m", stdout); + + yyjson_val *argObj = yyjson_obj_get(flagObj, "arg"); + if (argObj) { + yyjson_val *typeKey = yyjson_obj_get(argObj, "type"); + assert(typeKey); + yyjson_val *optionalKey = yyjson_obj_get(argObj, "optional"); + bool optional = optionalKey && yyjson_get_bool(optionalKey); + putchar(' '); + if (!instance.config.display.pipe) + fputs("\e[3m", stdout); + printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey)); + if (!instance.config.display.pipe) + fputs("\e[m", stdout); + putchar('\n'); + + yyjson_val *defaultKey = yyjson_obj_get(argObj, "default"); + if (defaultKey) { + if (ffStrEqualsIgnCase(yyjson_get_str(typeKey), "structure")) + printf("%10s: %s\n", "Default", FASTFETCH_DATATEXT_STRUCTURE); + else if (yyjson_is_bool(defaultKey)) + printf("%10s: %s\n", "Default", + yyjson_get_bool(defaultKey) ? "true" : "false"); + else if (yyjson_is_num(defaultKey)) + printf("%10s: %d\n", "Default", yyjson_get_int(defaultKey)); + else if (yyjson_is_str(defaultKey)) + printf("%10s: %s\n", "Default", yyjson_get_str(defaultKey)); + else + printf("%10s: Unknown\n", "Default"); + } + + yyjson_val *enumKey = yyjson_obj_get(argObj, "enum"); + if (enumKey) { + printf("%10s:\n", "Options"); + yyjson_val *optKey, *optVal; + size_t optIdx, optMax; + yyjson_obj_foreach(enumKey, optIdx, optMax, optKey, optVal) printf( + "%12s: %s\n", yyjson_get_str(optKey), yyjson_get_str(optVal)); + } + } else + putchar('\n'); + + yyjson_val *remarkKey = yyjson_obj_get(flagObj, "remark"); + if (remarkKey) { + if (yyjson_is_str(remarkKey)) + printf("%10s: %s\n", "Remark", yyjson_get_str(remarkKey)); + else if (yyjson_is_arr(remarkKey) && yyjson_arr_size(remarkKey) > 0) { + yyjson_val *remarkStr; + size_t remarkIdx, remarkMax; + yyjson_arr_foreach(remarkKey, remarkIdx, remarkMax, remarkStr) { + if (remarkIdx == 0) + printf("%10s: %s\n", "Remark", yyjson_get_str(remarkStr)); + else + printf(" %s\n", yyjson_get_str(remarkStr)); } + } } - } - - yyjson_doc_free(doc); - return false; -} - -static void printCommandHelp(const char* command) -{ - if(command == NULL) - printFullHelp(); - else if(ffStrEqualsIgnCase(command, "format-json")) - printCommandFormatHelpJson(); - else if(ffCharIsEnglishAlphabet(command[0]) && ffStrEndsWithIgnCase(command, "-format")) // -format - printCommandFormatHelp(command); - else if(!printSpecificCommandHelp(command)) - fprintf(stderr, "Error: No specific help for command '%s' provided\n", command); -} -static void listAvailablePresets(bool pretty) -{ - FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) - { - ffStrbufAppendS(path, "fastfetch/presets/"); - ffListFilesRecursively(path->chars, pretty); + yyjson_doc_free(doc); + return true; + } } + } - if (instance.state.platform.exePath.length) - { - FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy(&instance.state.platform.exePath); - ffStrbufSubstrBeforeLastC(&absolutePath, '/'); - ffStrbufAppendS(&absolutePath, "/presets/"); - ffListFilesRecursively(absolutePath.chars, pretty); - } + yyjson_doc_free(doc); + return false; } -static void listAvailableLogos(void) -{ - FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) - { - ffStrbufAppendS(path, "fastfetch/logos/"); - ffListFilesRecursively(path->chars, true); - } +static void printCommandHelp(const char *command) { + if (command == NULL) + printFullHelp(); + else if (ffStrEqualsIgnCase(command, "format-json")) + printCommandFormatHelpJson(); + else if (ffCharIsEnglishAlphabet(command[0]) && + ffStrEndsWithIgnCase(command, "-format")) // -format + printCommandFormatHelp(command); + else if (!printSpecificCommandHelp(command)) + fprintf(stderr, "Error: No specific help for command '%s' provided\n", + command); } -static void listConfigPaths(void) -{ - FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.configDirs) - { - bool exists = false; - uint32_t length = folder->length + (uint32_t) strlen("fastfetch") + 1 /* trailing slash */; - ffStrbufAppendS(folder, "fastfetch/config.jsonc"); - exists = ffPathExists(folder->chars, FF_PATHTYPE_FILE); - ffStrbufSubstrBefore(folder, length); - printf("%s%s\n", folder->chars, exists ? " (*)" : ""); - } +static void listAvailablePresets(bool pretty) { + FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { + ffStrbufAppendS(path, "fastfetch/presets/"); + ffListFilesRecursively(path->chars, pretty); + } + + if (instance.state.platform.exePath.length) { + FF_STRBUF_AUTO_DESTROY absolutePath = + ffStrbufCreateCopy(&instance.state.platform.exePath); + ffStrbufSubstrBeforeLastC(&absolutePath, '/'); + ffStrbufAppendS(&absolutePath, "/presets/"); + ffListFilesRecursively(absolutePath.chars, pretty); + } } -static void listDataPaths(void) -{ - FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.dataDirs) - { - ffStrbufAppendS(folder, "fastfetch/"); - puts(folder->chars); - } +static void listAvailableLogos(void) { + FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { + ffStrbufAppendS(path, "fastfetch/logos/"); + ffListFilesRecursively(path->chars, true); + } } -static void listModules(bool pretty) -{ - unsigned count = 0; - for (int i = 0; i <= 'Z' - 'A'; ++i) - { - for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules) - { - ++count; - if (pretty) - printf("%d)%s%-14s: %s\n", count, count > 9 ? " " : " ", (*modules)->name, (*modules)->description); - else - printf("%s:%s\n", (*modules)->name, (*modules)->description); - } - } +static void listConfigPaths(void) { + FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.configDirs) { + bool exists = false; + uint32_t length = + folder->length + (uint32_t)strlen("fastfetch") + 1 /* trailing slash */; + ffStrbufAppendS(folder, "fastfetch/config.jsonc"); + exists = ffPathExists(folder->chars, FF_PATHTYPE_FILE); + ffStrbufSubstrBefore(folder, length); + printf("%s%s\n", folder->chars, exists ? " (*)" : ""); + } } -static bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg) -{ - assert(!data->configDoc); - - { - yyjson_read_err error; - data->configDoc = path - ? yyjson_read_file(path, flg, NULL, &error) - : yyjson_read_fp(stdin, flg, NULL, &error); - if (!data->configDoc) - { - if (error.code != YYJSON_READ_ERROR_FILE_OPEN) - { - if (path) - { - size_t row = 0, col = error.pos; - FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); - if (ffAppendFileBuffer(path, &content)) - yyjson_locate_pos(content.chars, content.length, error.pos, &row, &col, NULL); - fprintf(stderr, "Error: failed to parse JSON config file `%s` at (%zu, %zu): %s\n", path, row, col, error.msg); - } - else - fprintf(stderr, "Error: failed to parse JSON from stdin at %zu: %s\n", error.pos, error.msg); - - exit(477); - } - return false; - } - } - - { - const char* error = NULL; - - yyjson_val* const root = yyjson_doc_get_root(data->configDoc); - if (!yyjson_is_obj(root)) - error = "Invalid JSON config format. Root value must be an object"; - - if ( - error || - (error = ffOptionsParseLogoJsonConfig(&instance.config.logo, root)) || - (error = ffOptionsParseGeneralJsonConfig(&instance.config.general, root)) || - (error = ffOptionsParseDisplayJsonConfig(&instance.config.display, root)) || - false - ) { - fprintf(stderr, "JsonConfig Error: %s\n", error); - exit(477); - } - } +static void listDataPaths(void) { + FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.dataDirs) { + ffStrbufAppendS(folder, "fastfetch/"); + puts(folder->chars); + } +} - return true; +static void listModules(bool pretty) { + unsigned count = 0; + for (int i = 0; i <= 'Z' - 'A'; ++i) { + for (FFModuleBaseInfo **modules = ffModuleInfos[i]; *modules; ++modules) { + ++count; + if (pretty) + printf("%d)%s%-14s: %s\n", count, count > 9 ? " " : " ", + (*modules)->name, (*modules)->description); + else + printf("%s:%s\n", (*modules)->name, (*modules)->description); + } + } } +static bool parseJsoncFile(FFdata *data, const char *path, + yyjson_read_flag flg) { + assert(!data->configDoc); + + { + yyjson_read_err error; + data->configDoc = path ? yyjson_read_file(path, flg, NULL, &error) + : yyjson_read_fp(stdin, flg, NULL, &error); + if (!data->configDoc) { + if (error.code != YYJSON_READ_ERROR_FILE_OPEN) { + if (path) { + size_t row = 0, col = error.pos; + FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); + if (ffAppendFileBuffer(path, &content)) + yyjson_locate_pos(content.chars, content.length, error.pos, &row, + &col, NULL); + fprintf(stderr, + "Error: failed to parse JSON config file `%s` at (%zu, %zu): " + "%s\n", + path, row, col, error.msg); + } else + fprintf(stderr, "Error: failed to parse JSON from stdin at %zu: %s\n", + error.pos, error.msg); -static void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig) -{ - if (data->resultDoc) - { - fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); exit(477); + } + return false; } + } - if (!filePath) - { - if (instance.state.platform.configDirs.length == 0) - { - fprintf(stderr, "Error: No config directory found to generate config file in. Use --gen-config to specify a path\n"); - exit(477); - } + { + const char *error = NULL; - FFstrbuf* configDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); - ffStrbufEnsureFixedLengthFree(&data->genConfigPath, configDir->length + strlen("fastfetch/config.jsonc")); - ffStrbufSet(&data->genConfigPath, configDir); - ffStrbufAppendS(&data->genConfigPath, "fastfetch/config.jsonc"); - } - else - { - ffStrbufSetS(&data->genConfigPath, filePath); - } + yyjson_val *const root = yyjson_doc_get_root(data->configDoc); + if (!yyjson_is_obj(root)) + error = "Invalid JSON config format. Root value must be an object"; - if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) - { - fprintf(stderr, "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", data->genConfigPath.chars, fullConfig ? "-full" : ""); - exit(477); + if (error || + (error = ffOptionsParseLogoJsonConfig(&instance.config.logo, root)) || + (error = + ffOptionsParseGeneralJsonConfig(&instance.config.general, root)) || + (error = + ffOptionsParseDisplayJsonConfig(&instance.config.display, root)) || + false) { + fprintf(stderr, "JsonConfig Error: %s\n", error); + exit(477); } + } - data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; - data->resultDoc = yyjson_mut_doc_new(NULL); + return true; } -static void optionParseConfigFile(FFdata* data, const char* key, const char* value) -{ - if (data->configLoaded) - { - fprintf(stderr, "Error: only one config file can be loaded\n"); - exit(413); - } - - data->configLoaded = true; - - if(value == NULL) - { - fprintf(stderr, "Error: usage: %s \n", key); - exit(413); - } - - if (value[0] == '\0' || ffStrEqualsIgnCase(value, "none")) - return; - - if (value[0] == '-' && value[1] == '\0') - { - parseJsoncFile(data, NULL, false); - return; - } - - //Try to load as an absolute path +static void generateConfigFile(FFdata *data, bool force, const char *filePath, + bool fullConfig) { + if (data->resultDoc) { + fprintf( + stderr, + "Error: duplicated `--gen-config` or `--format json` flags found\n"); + exit(477); + } + + if (!filePath) { + if (instance.state.platform.configDirs.length == 0) { + fprintf(stderr, "Error: No config directory found to generate config " + "file in. Use --gen-config to specify a path\n"); + exit(477); + } + + FFstrbuf *configDir = + FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); + ffStrbufEnsureFixedLengthFree(&data->genConfigPath, + configDir->length + + strlen("fastfetch/config.jsonc")); + ffStrbufSet(&data->genConfigPath, configDir); + ffStrbufAppendS(&data->genConfigPath, "fastfetch/config.jsonc"); + } else { + ffStrbufSetS(&data->genConfigPath, filePath); + } + + if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) { + fprintf( + stderr, + "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", + data->genConfigPath.chars, fullConfig ? "-full" : ""); + exit(477); + } + + data->docType = + fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; + data->resultDoc = yyjson_mut_doc_new(NULL); +} - FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(128); - ffStrbufSetS(&absolutePath, value); - bool strictJson = ffStrbufEndsWithIgnCaseS(&absolutePath, ".json"); - bool jsonc = !strictJson && ffStrbufEndsWithIgnCaseS(&absolutePath, ".jsonc"); - bool json5 = !strictJson && !jsonc && ffStrbufEndsWithIgnCaseS(&absolutePath, ".json5"); - bool needExtension = !strictJson && !jsonc && !json5; +static void optionParseConfigFile(FFdata *data, const char *key, + const char *value) { + if (data->configLoaded) { + fprintf(stderr, "Error: only one config file can be loaded\n"); + exit(413); + } + + data->configLoaded = true; + + if (value == NULL) { + fprintf(stderr, "Error: usage: %s \n", key); + exit(413); + } + + if (value[0] == '\0' || ffStrEqualsIgnCase(value, "none")) + return; + + if (value[0] == '-' && value[1] == '\0') { + parseJsoncFile(data, NULL, false); + return; + } + + // Try to load as an absolute path + + FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(128); + ffStrbufSetS(&absolutePath, value); + bool strictJson = ffStrbufEndsWithIgnCaseS(&absolutePath, ".json"); + bool jsonc = !strictJson && ffStrbufEndsWithIgnCaseS(&absolutePath, ".jsonc"); + bool json5 = !strictJson && !jsonc && + ffStrbufEndsWithIgnCaseS(&absolutePath, ".json5"); + bool needExtension = !strictJson && !jsonc && !json5; + if (needExtension) + ffStrbufAppendS(&absolutePath, ".jsonc"); + + yyjson_read_flag flag = + strictJson ? 0 + : jsonc ? YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS + : YYJSON_READ_JSON5; + + if (parseJsoncFile(data, absolutePath.chars, flag)) + return; + + // Try to load as a relative path with the config directory + + FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.configDirs) { + ffStrbufSet(&absolutePath, path); + ffStrbufAppendS(&absolutePath, "fastfetch/"); + ffStrbufAppendS(&absolutePath, value); if (needExtension) - ffStrbufAppendS(&absolutePath, ".jsonc"); - - yyjson_read_flag flag = strictJson - ? 0 - : jsonc - ? YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS - : YYJSON_READ_JSON5; + ffStrbufAppendS(&absolutePath, ".jsonc"); - if (parseJsoncFile(data, absolutePath.chars, flag)) return; + if (parseJsoncFile(data, absolutePath.chars, flag)) + return; + } - //Try to load as a relative path with the config directory + // Try to load as a preset - FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.configDirs) - { - ffStrbufSet(&absolutePath, path); - ffStrbufAppendS(&absolutePath, "fastfetch/"); - ffStrbufAppendS(&absolutePath, value); - if (needExtension) - ffStrbufAppendS(&absolutePath, ".jsonc"); - - if (parseJsoncFile(data, absolutePath.chars, flag)) return; - } + FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { + ffStrbufSet(&absolutePath, path); + ffStrbufAppendS(&absolutePath, "fastfetch/presets/"); + ffStrbufAppendS(&absolutePath, value); + if (needExtension) + ffStrbufAppendS(&absolutePath, ".jsonc"); - //Try to load as a preset + if (parseJsoncFile(data, absolutePath.chars, flag)) + return; + } - FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) - { - ffStrbufSet(&absolutePath, path); - ffStrbufAppendS(&absolutePath, "fastfetch/presets/"); - ffStrbufAppendS(&absolutePath, value); - if (needExtension) - ffStrbufAppendS(&absolutePath, ".jsonc"); + // Try to load as a relative path with the directory of fastfetch binary, for + // Windows support - if (parseJsoncFile(data, absolutePath.chars, flag)) return; - } + if (instance.state.platform.exePath.length) { + uint32_t lastSlash = + ffStrbufLastIndexC(&instance.state.platform.exePath, '/') + 1; + assert(lastSlash < instance.state.platform.exePath.length); - //Try to load as a relative path with the directory of fastfetch binary, for Windows support - - if (instance.state.platform.exePath.length) - { - uint32_t lastSlash = ffStrbufLastIndexC(&instance.state.platform.exePath, '/') + 1; - assert(lastSlash < instance.state.platform.exePath.length); - - // Try {exePath}/ - ffStrbufSetNS(&absolutePath, lastSlash, instance.state.platform.exePath.chars); - ffStrbufAppendS(&absolutePath, value); - if (needExtension) - ffStrbufAppendS(&absolutePath, ".jsonc"); - if (parseJsoncFile(data, absolutePath.chars, flag)) return; - - // Try {exePath}/presets/ - ffStrbufSubstrBefore(&absolutePath, lastSlash); - ffStrbufAppendS(&absolutePath, "presets/"); - ffStrbufAppendS(&absolutePath, value); - if (needExtension) - ffStrbufAppendS(&absolutePath, ".jsonc"); - if (parseJsoncFile(data, absolutePath.chars, flag)) return; - } + // Try {exePath}/ + ffStrbufSetNS(&absolutePath, lastSlash, + instance.state.platform.exePath.chars); + ffStrbufAppendS(&absolutePath, value); + if (needExtension) + ffStrbufAppendS(&absolutePath, ".jsonc"); + if (parseJsoncFile(data, absolutePath.chars, flag)) + return; + + // Try {exePath}/presets/ + ffStrbufSubstrBefore(&absolutePath, lastSlash); + ffStrbufAppendS(&absolutePath, "presets/"); + ffStrbufAppendS(&absolutePath, value); + if (needExtension) + ffStrbufAppendS(&absolutePath, ".jsonc"); + if (parseJsoncFile(data, absolutePath.chars, flag)) + return; + } - //File not found + // File not found - fprintf(stderr, "Error: couldn't find config: %s\n", value); - exit(414); + fprintf(stderr, "Error: couldn't find config: %s\n", value); + exit(414); } -static void printVersion() -{ - FFVersionResult* result = &ffVersionResult; - printf("%s %s%s%s (%s)\n", result->projectName, result->version, result->versionTweak, result->debugMode ? "-debug" : "", result->architecture); +static void printVersion() { + FFVersionResult *result = &ffVersionResult; + printf("%s %s%s%s (%s)\n", result->projectName, result->version, + result->versionTweak, result->debugMode ? "-debug" : "", + result->architecture); } -static void enableJsonOutput(FFdata* data) -{ - if (data->resultDoc) - { - fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); - exit(477); - } - - data->resultDoc = yyjson_mut_doc_new(NULL); - data->docType = FF_RESULT_DOC_TYPE_JSON; - yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc)); +static void enableJsonOutput(FFdata *data) { + if (data->resultDoc) { + fprintf( + stderr, + "Error: duplicated `--gen-config` or `--format json` flags found\n"); + exit(477); + } + + data->resultDoc = yyjson_mut_doc_new(NULL); + data->docType = FF_RESULT_DOC_TYPE_JSON; + yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc)); } -static void parseCommand(FFdata* data, char* key, char* value) -{ - if(ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help")) - { - printCommandHelp(value); - exit(0); - } - if(ffStrEqualsIgnCase(key, "--help-raw")) - { - puts(FASTFETCH_DATATEXT_JSON_HELP); - exit(0); - } - else if(ffStrEqualsIgnCase(key, "-v") || ffStrEqualsIgnCase(key, "--version")) - { - printVersion(); - exit(0); - } - else if(ffStrEqualsIgnCase(key, "--version-raw")) - { - puts(FASTFETCH_PROJECT_VERSION); - exit(0); - } - else if(ffStrStartsWithIgnCase(key, "--print-")) - { - const char* subkey = key + strlen("--print-"); - if(ffStrEndsWithIgnCase(subkey, "structure")) - puts(FASTFETCH_DATATEXT_STRUCTURE); - else if(ffStrEqualsIgnCase(subkey, "logos")) - ffLogoBuiltinPrint(); - else - { - fprintf(stderr, "Error: unsupported print option: %s\n", key); - exit(415); - } - exit(0); - } - else if(ffStrStartsWithIgnCase(key, "--list-")) - { - const char* subkey = key + strlen("--list-"); - if(ffStrEqualsIgnCase(subkey, "modules")) - listModules(!value || !ffStrEqualsIgnCase(value, "autocompletion")); - else if(ffStrEqualsIgnCase(subkey, "presets")) - listAvailablePresets(!value || !ffStrEqualsIgnCase(value, "autocompletion")); - else if(ffStrEqualsIgnCase(subkey, "config-paths")) - listConfigPaths(); - else if(ffStrEqualsIgnCase(subkey, "data-paths")) - listDataPaths(); - else if(ffStrEqualsIgnCase(subkey, "features")) - ffListFeatures(); - else if(ffStrEqualsIgnCase(subkey, "logos")) - { - if (value) - { - if (ffStrEqualsIgnCase(value, "autocompletion")) - ffLogoBuiltinListAutocompletion(); - else if (ffStrEqualsIgnCase(value, "builtin")) - ffLogoBuiltinList(); - else if (ffStrEqualsIgnCase(value, "custom")) - listAvailableLogos(); - else - { - fprintf(stderr, "Error: unsupported logo type: %s\n", value); - exit(415); - } - } - else - { - puts("Builtin logos:"); - ffLogoBuiltinList(); - puts("\nCustom logos:"); - listAvailableLogos(); - } - } - else - { - fprintf(stderr, "Error: unsupported list option: %s\n", key); - exit(415); +static void parseCommand(FFdata *data, char *key, char *value) { + if (ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help")) { + printCommandHelp(value); + exit(0); + } + if (ffStrEqualsIgnCase(key, "--help-raw")) { + puts(FASTFETCH_DATATEXT_JSON_HELP); + exit(0); + } else if (ffStrEqualsIgnCase(key, "-v") || + ffStrEqualsIgnCase(key, "--version")) { + printVersion(); + exit(0); + } else if (ffStrEqualsIgnCase(key, "--version-raw")) { + puts(FASTFETCH_PROJECT_VERSION); + exit(0); + } else if (ffStrStartsWithIgnCase(key, "--print-")) { + const char *subkey = key + strlen("--print-"); + if (ffStrEndsWithIgnCase(subkey, "structure")) + puts(FASTFETCH_DATATEXT_STRUCTURE); + else if (ffStrEqualsIgnCase(subkey, "logos")) + ffLogoBuiltinPrint(); + else { + fprintf(stderr, "Error: unsupported print option: %s\n", key); + exit(415); + } + exit(0); + } else if (ffStrStartsWithIgnCase(key, "--list-")) { + const char *subkey = key + strlen("--list-"); + if (ffStrEqualsIgnCase(subkey, "modules")) + listModules(!value || !ffStrEqualsIgnCase(value, "autocompletion")); + else if (ffStrEqualsIgnCase(subkey, "presets")) + listAvailablePresets(!value || + !ffStrEqualsIgnCase(value, "autocompletion")); + else if (ffStrEqualsIgnCase(subkey, "config-paths")) + listConfigPaths(); + else if (ffStrEqualsIgnCase(subkey, "data-paths")) + listDataPaths(); + else if (ffStrEqualsIgnCase(subkey, "features")) + ffListFeatures(); + else if (ffStrEqualsIgnCase(subkey, "logos")) { + if (value) { + if (ffStrEqualsIgnCase(value, "autocompletion")) + ffLogoBuiltinListAutocompletion(); + else if (ffStrEqualsIgnCase(value, "builtin")) + ffLogoBuiltinList(); + else if (ffStrEqualsIgnCase(value, "custom")) + listAvailableLogos(); + else { + fprintf(stderr, "Error: unsupported logo type: %s\n", value); + exit(415); } - - exit(0); - } - else if(ffStrEqualsIgnCase(key, "--gen-config")) - generateConfigFile(data, false, value, false); - else if(ffStrEqualsIgnCase(key, "--gen-config-force")) - generateConfigFile(data, true, value, false); - else if(ffStrEqualsIgnCase(key, "--gen-config-full")) - generateConfigFile(data, false, value, true); - else if(ffStrEqualsIgnCase(key, "--gen-config-full-force")) - generateConfigFile(data, true, value, true); - else if(ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config")) - optionParseConfigFile(data, key, value); - else if(ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json")) - { - if (ffOptionParseBoolean(value)) enableJsonOutput(data); - } - else if(ffStrEqualsIgnCase(key, "--format")) - { - if (!!ffOptionParseEnum(key, value, (FFKeyValuePair[]) { - { "default", false}, - { "json", true }, - {}, - })) enableJsonOutput(data); - } - else if(ffStrEqualsIgnCase(key, "--dynamic-interval")) - instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds - else - return; - - // Don't parse it again in parseOption. - // This is necessary because parseOption doesn't understand this option and will result in an unknown option error. - key[0] = '\0'; - if (value) value[0] = '\0'; + } else { + puts("Builtin logos:"); + ffLogoBuiltinList(); + puts("\nCustom logos:"); + listAvailableLogos(); + } + } else { + fprintf(stderr, "Error: unsupported list option: %s\n", key); + exit(415); + } + + exit(0); + } else if (ffStrEqualsIgnCase(key, "--gen-config")) + generateConfigFile(data, false, value, false); + else if (ffStrEqualsIgnCase(key, "--gen-config-force")) + generateConfigFile(data, true, value, false); + else if (ffStrEqualsIgnCase(key, "--gen-config-full")) + generateConfigFile(data, false, value, true); + else if (ffStrEqualsIgnCase(key, "--gen-config-full-force")) + generateConfigFile(data, true, value, true); + else if (ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config")) + optionParseConfigFile(data, key, value); + else if (ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json")) { + if (ffOptionParseBoolean(value)) + enableJsonOutput(data); + } else if (ffStrEqualsIgnCase(key, "--format")) { + if (!!ffOptionParseEnum(key, value, + (FFKeyValuePair[]){ + {"default", false}, + {"json", true}, + {}, + })) + enableJsonOutput(data); + } else if (ffStrEqualsIgnCase(key, "--dynamic-interval")) + instance.state.dynamicInterval = + ffOptionParseUInt32(key, value); // seconds to milliseconds + else + return; + + // Don't parse it again in parseOption. + // This is necessary because parseOption doesn't understand this option and + // will result in an unknown option error. + key[0] = '\0'; + if (value) + value[0] = '\0'; } -static void parseOption(FFdata* data, const char* key, const char* value) -{ - if(ffStrEqualsIgnCase(key, "-s") || ffStrEqualsIgnCase(key, "--structure")) - ffOptionParseString(key, value, &data->structure); - - else if( - ffOptionsParseGeneralCommandLine(&instance.config.general, key, value) || - ffOptionsParseLogoCommandLine(&instance.config.logo, key, value) || - ffOptionsParseDisplayCommandLine(&instance.config.display, key, value) || - ffParseModuleOptions(key, value) - ) {} - - else if(ffStrEqualsIgnCase(key, "--structure-disabled")) - ffOptionParseString(key, value, &data->structureDisabled); - - else - { - fprintf(stderr, "Error: unknown option: %s\n", key); - exit(400); - } +static void parseOption(FFdata *data, const char *key, const char *value) { + if (ffStrEqualsIgnCase(key, "-s") || ffStrEqualsIgnCase(key, "--structure")) + ffOptionParseString(key, value, &data->structure); + + else if (ffOptionsParseGeneralCommandLine(&instance.config.general, key, + value) || + ffOptionsParseLogoCommandLine(&instance.config.logo, key, value) || + ffOptionsParseDisplayCommandLine(&instance.config.display, key, + value) || + ffParseModuleOptions(key, value)) { + } + + else if (ffStrEqualsIgnCase(key, "--structure-disabled")) + ffOptionParseString(key, value, &data->structureDisabled); + + else { + fprintf(stderr, "Error: unknown option: %s\n", key); + exit(400); + } } -static void parseConfigFiles(FFdata* data) -{ - if (__builtin_expect(data->genConfigPath.length == 0, true)) - { - FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs) - { - uint32_t dirLength = dir->length; - - ffStrbufAppendS(dir, "fastfetch/config.jsonc"); - bool success = parseJsoncFile(data, dir->chars, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS); - ffStrbufSubstrBefore(dir, dirLength); - if (success) return; - - ffStrbufAppendS(dir, "fastfetch/config.json5"); - success = parseJsoncFile(data, dir->chars, YYJSON_READ_JSON5); - ffStrbufSubstrBefore(dir, dirLength); - if (success) return; - } +static void parseConfigFiles(FFdata *data) { + if (__builtin_expect(data->genConfigPath.length == 0, true)) { + FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs) { + uint32_t dirLength = dir->length; + + ffStrbufAppendS(dir, "fastfetch/config.jsonc"); + bool success = parseJsoncFile(data, dir->chars, + YYJSON_READ_ALLOW_COMMENTS | + YYJSON_READ_ALLOW_TRAILING_COMMAS); + ffStrbufSubstrBefore(dir, dirLength); + if (success) + return; + + ffStrbufAppendS(dir, "fastfetch/config.json5"); + success = parseJsoncFile(data, dir->chars, YYJSON_READ_JSON5); + ffStrbufSubstrBefore(dir, dirLength); + if (success) + return; } + } } -static void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(FFdata* data, char* key, char* value)) -{ - for(int i = 1; i < argc; i++) - { - const char* key = argv[i]; - if(*key == '\0') - continue; // has been handled by parseCommand - - if(*key != '-') - { - fprintf(stderr, "Error: invalid option: %s. An option must start with `-`\n", key); - exit(400); - } - - if(i == argc - 1 || ( - argv[i + 1][0] == '-' && - argv[i + 1][1] != '\0' && // `-` is used as an alias for `/dev/stdin` - !ffStrEqualsIgnCase(argv[i], "--separator-string") // Separator string can start with a - - )) { - parser(data, argv[i], NULL); - } - else - { - parser(data, argv[i], argv[i + 1]); - ++i; - } - } +static void parseArguments(FFdata *data, int argc, char **argv, + void (*parser)(FFdata *data, char *key, + char *value)) { + for (int i = 1; i < argc; i++) { + const char *key = argv[i]; + if (*key == '\0') + continue; // has been handled by parseCommand + + if (*key != '-') { + fprintf(stderr, + "Error: invalid option: %s. An option must start with `-`\n", + key); + exit(400); + } + + if (i == argc - 1 || + (argv[i + 1][0] == '-' && + argv[i + 1][1] != '\0' && // `-` is used as an alias for `/dev/stdin` + !ffStrEqualsIgnCase( + argv[i], + "--separator-string") // Separator string can start with a - + )) { + parser(data, argv[i], NULL); + } else { + parser(data, argv[i], argv[i + 1]); + ++i; + } + } } -static void run(FFdata* data) -{ - const bool useJsonConfig = data->structure.length == 0 && data->configDoc; +static void run(FFdata *data) { + const bool useJsonConfig = data->structure.length == 0 && data->configDoc; + + if (useJsonConfig) + ffPrintJsonConfig(data, true /* prepare */); + else { + // If we don't have a custom structure, use the default one + if (data->structure.length == 0) + ffStrbufAppendS( + &data->structure, + FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here + // because we will modify the string + ffPrepareCommandOption(data); + } + + ffStart(); + + if (!data->resultDoc) + ffLogoPrint(); + +#if defined(_WIN32) + if (!instance.config.display.noBuffer) + fflush(stdout); +#endif + while (true) { if (useJsonConfig) - ffPrintJsonConfig(data, true /* prepare */); + ffPrintJsonConfig(data, false); else - { - //If we don't have a custom structure, use the default one - if(data->structure.length == 0) - ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string - ffPrepareCommandOption(data); - } - - ffStart(); - - if (!data->resultDoc) - ffLogoPrint(); - - #if defined(_WIN32) - if (!instance.config.display.noBuffer) fflush(stdout); - #endif - - while (true) - { - if (useJsonConfig) - ffPrintJsonConfig(data, false); - else - ffPrintCommandOption(data); - - if (instance.state.dynamicInterval > 0) - { - fflush(stdout); - ffTimeSleep(instance.state.dynamicInterval); - fputs("\e[H", stdout); - } - else - break; + ffPrintCommandOption(data); - if (useJsonConfig) - ffPrintJsonConfig(data, true /* prepare */); - else - ffPrepareCommandOption(data); - } + if (instance.state.dynamicInterval > 0) { + fflush(stdout); + ffTimeSleep(instance.state.dynamicInterval); + fputs("\e[H", stdout); + } else + break; - if (data->resultDoc) - yyjson_mut_write_fp(stdout, data->resultDoc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL); + if (useJsonConfig) + ffPrintJsonConfig(data, true /* prepare */); else - { - if (instance.config.logo.printRemaining) - ffLogoPrintRemaining(); - ffFinish(); - } + ffPrepareCommandOption(data); + } + + if (data->resultDoc) + yyjson_mut_write_fp(stdout, data->resultDoc, + YYJSON_WRITE_INF_AND_NAN_AS_NULL | + YYJSON_WRITE_PRETTY_TWO_SPACES | + YYJSON_WRITE_NEWLINE_AT_END, + NULL, NULL); + else { + if (instance.config.logo.printRemaining) + ffLogoPrintRemaining(); + ffFinish(); + } } -static void writeConfigFile(FFdata* data) -{ - const FFstrbuf* filename = &data->genConfigPath; - - yyjson_mut_doc* doc = data->resultDoc; - 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/master/doc/json_schema.json"); - - if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL) - { - ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo); - ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display); - ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general); - } - ffMigrateCommandOptionToJsonc(data); - - if (ffStrbufEqualS(filename, "-")) - yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL); - else - { - size_t len; - FF_AUTO_FREE const char* str = yyjson_mut_write(doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, &len); - if (!str) - { - printf("Error: failed to generate config file\n"); - exit(1); - } - if (ffWriteFileData(filename->chars, len, str)) - { - printf("✓ Configuration file generated: `%s`\n" - "* Tip: Use a JSON schema-aware editor for better editing experience\n" - "* Documentation: https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\n", filename->chars); - } - else - { - printf("Error: failed to write file in `%s`\n", filename->chars); - exit(1); - } - } +static void writeConfigFile(FFdata *data) { + const FFstrbuf *filename = &data->genConfigPath; + + yyjson_mut_doc *doc = data->resultDoc; + 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/" + "master/doc/json_schema.json"); + + if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL) { + ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo); + ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display); + ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general); + } + ffMigrateCommandOptionToJsonc(data); + + if (ffStrbufEqualS(filename, "-")) + yyjson_mut_write_fp(stdout, doc, + YYJSON_WRITE_INF_AND_NAN_AS_NULL | + YYJSON_WRITE_PRETTY_TWO_SPACES | + YYJSON_WRITE_NEWLINE_AT_END, + NULL, NULL); + else { + size_t len; + FF_AUTO_FREE const char *str = yyjson_mut_write( + doc, + YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | + YYJSON_WRITE_NEWLINE_AT_END, + &len); + if (!str) { + printf("Error: failed to generate config file\n"); + exit(1); + } + if (ffWriteFileData(filename->chars, len, str)) { + printf("✓ Configuration file generated: `%s`\n" + "* Tip: Use a JSON schema-aware editor for better editing " + "experience\n" + "* Documentation: " + "https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\n", + filename->chars); + } else { + printf("Error: failed to write file in `%s`\n", filename->chars); + exit(1); + } + } } -int main(int argc, char** argv) -{ - ffInitInstance(); - atexit(ffDestroyInstance); - - //Data stores things only needed for the configuration of fastfetch - FFdata data = { - .structure = ffStrbufCreate(), - .structureDisabled = ffStrbufCreate(), - .genConfigPath = ffStrbufCreate(), - }; - - parseArguments(&data, argc, argv, parseCommand); - if(instance.state.dynamicInterval && data.resultDoc) - { - fprintf(stderr, "Error: --dynamic-interval cannot be used with --json\n"); - exit(400); - } - - if(!data.configLoaded && !getenv("NO_CONFIG")) - parseConfigFiles(&data); - parseArguments(&data, argc, argv, (void*) parseOption); - - if (__builtin_expect(data.genConfigPath.length == 0, true)) - run(&data); - else - writeConfigFile(&data); - - ffStrbufDestroy(&data.structure); - ffStrbufDestroy(&data.structureDisabled); - yyjson_doc_free(data.configDoc); - yyjson_mut_doc_free(data.resultDoc); - ffStrbufDestroy(&data.genConfigPath); +int main(int argc, char **argv) { + ffInitInstance(); + atexit(ffDestroyInstance); + + // Data stores things only needed for the configuration of fastfetch + FFdata data = { + .structure = ffStrbufCreate(), + .structureDisabled = ffStrbufCreate(), + .genConfigPath = ffStrbufCreate(), + }; + + parseArguments(&data, argc, argv, parseCommand); + if (instance.state.dynamicInterval && data.resultDoc) { + fprintf(stderr, "Error: --dynamic-interval cannot be used with --json\n"); + exit(400); + } + + if (!data.configLoaded && !getenv("NO_CONFIG")) + parseConfigFiles(&data); + parseArguments(&data, argc, argv, (void *)parseOption); + + if (__builtin_expect(data.genConfigPath.length == 0, true)) + run(&data); + else + writeConfigFile(&data); + + ffStrbufDestroy(&data.structure); + ffStrbufDestroy(&data.structureDisabled); + yyjson_doc_free(data.configDoc); + yyjson_mut_doc_free(data.resultDoc); + ffStrbufDestroy(&data.genConfigPath); } diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index c9d3d55802..e17c1e6c3a 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -1,158 +1,138 @@ -#include "common/printing.h" +#include "detection/packages/packages.h" #include "common/jsonconfig.h" +#include "common/printing.h" #include "common/stringUtils.h" -#include "detection/packages/packages.h" #include "modules/packages/packages.h" -bool ffPrintPackages(FFPackagesOptions* options) -{ - FFPackagesResult counts = {}; - ffStrbufInit(&counts.pacmanBranch); - - const char* error = ffDetectPackages(&counts, options); - - if(error) - { - ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); - return false; +bool ffPrintPackages(FFPackagesOptions *options) { + FFPackagesResult counts = {}; + ffStrbufInit(&counts.pacmanBranch); + + const char *error = ffDetectPackages(&counts, options); + + if (error) { + ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, + FF_PRINT_TYPE_DEFAULT, "%s", error); + return false; + } + + uint32_t nixAll = counts.nixDefault + counts.nixSystem + counts.nixUser; + uint32_t flatpakAll = counts.flatpakSystem + counts.flatpakUser; + uint32_t brewAll = counts.brew + counts.brewCask; + uint32_t guixAll = counts.guixSystem + counts.guixUser + counts.guixHome; + uint32_t hpkgAll = counts.hpkgSystem + counts.hpkgUser; + uint32_t amAll = counts.amSystem + counts.amUser; + uint32_t scoopAll = counts.scoopUser + counts.scoopGlobal; + + if (options->moduleArgs.outputFormat.length == 0) { + ffPrintLogoAndKey(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, + FF_PRINT_TYPE_DEFAULT); + +#define FF_PRINT_PACKAGE_NAME(var, name) \ + { \ + if (counts.var > 0) { \ + printf("%u (%s)", counts.var, (name)); \ + if ((all -= counts.var) > 0) \ + fputs(", ", stdout); \ + } \ + } + +#define FF_PRINT_PACKAGE(name) FF_PRINT_PACKAGE_NAME(name, #name) + +#define FF_PRINT_PACKAGE_ALL(name) \ + { \ + if (name##All > 0) { \ + printf("%u (%s)", name##All, #name); \ + if ((all -= name##All) > 0) \ + fputs(", ", stdout); \ + } \ + } + + uint32_t all = counts.all; + if (counts.pacman > 0) { + printf("%u (pacman)", counts.pacman); + if (counts.pacmanBranch.length > 0) + printf("[%s]", counts.pacmanBranch.chars); + if ((all -= counts.pacman) > 0) + printf(", "); + }; + FF_PRINT_PACKAGE(dpkg) + FF_PRINT_PACKAGE(rpm) + FF_PRINT_PACKAGE(emerge) + FF_PRINT_PACKAGE(eopkg) + FF_PRINT_PACKAGE(xbps) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(nix); + } else { + FF_PRINT_PACKAGE_NAME(nixSystem, "nix-system") + FF_PRINT_PACKAGE_NAME(nixUser, "nix-user") + FF_PRINT_PACKAGE_NAME(nixDefault, "nix-default") } - - uint32_t nixAll = counts.nixDefault + counts.nixSystem + counts.nixUser; - uint32_t flatpakAll = counts.flatpakSystem + counts.flatpakUser; - uint32_t brewAll = counts.brew + counts.brewCask; - uint32_t guixAll = counts.guixSystem + counts.guixUser + counts.guixHome; - uint32_t hpkgAll = counts.hpkgSystem + counts.hpkgUser; - uint32_t amAll = counts.amSystem + counts.amUser; - uint32_t scoopAll = counts.scoopUser + counts.scoopGlobal; - - if(options->moduleArgs.outputFormat.length == 0) - { - ffPrintLogoAndKey(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); - - #define FF_PRINT_PACKAGE_NAME(var, name) {\ - if(counts.var > 0) \ - { \ - printf("%u (%s)", counts.var, (name)); \ - if((all -= counts.var) > 0) \ - fputs(", ", stdout); \ - } \ - } - - #define FF_PRINT_PACKAGE(name) FF_PRINT_PACKAGE_NAME(name, #name) - - #define FF_PRINT_PACKAGE_ALL(name) {\ - if(name ## All > 0) \ - { \ - printf("%u (%s)", name ## All, #name); \ - if((all -= name ## All) > 0) \ - fputs(", ", stdout); \ - } \ - } - - uint32_t all = counts.all; - if(counts.pacman > 0) - { - printf("%u (pacman)", counts.pacman); - if(counts.pacmanBranch.length > 0) - printf("[%s]", counts.pacmanBranch.chars); - if((all -= counts.pacman) > 0) - printf(", "); - }; - FF_PRINT_PACKAGE(dpkg) - FF_PRINT_PACKAGE(rpm) - FF_PRINT_PACKAGE(emerge) - FF_PRINT_PACKAGE(eopkg) - FF_PRINT_PACKAGE(xbps) - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(nix); - } - else - { - FF_PRINT_PACKAGE_NAME(nixSystem, "nix-system") - FF_PRINT_PACKAGE_NAME(nixUser, "nix-user") - FF_PRINT_PACKAGE_NAME(nixDefault, "nix-default") - } - FF_PRINT_PACKAGE(apk) - FF_PRINT_PACKAGE(pkg) - FF_PRINT_PACKAGE(pkgsrc) - FF_PRINT_PACKAGE(kiss) - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(hpkg) - } - else - { - FF_PRINT_PACKAGE_NAME(hpkgSystem, counts.hpkgUser ? "hpkg-system" : "hpkg") - FF_PRINT_PACKAGE_NAME(hpkgUser, "hpkg-user") - } - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(flatpak); - } - else - { - FF_PRINT_PACKAGE_NAME(flatpakSystem, counts.flatpakUser ? "flatpak-system" : "flatpak") - FF_PRINT_PACKAGE_NAME(flatpakUser, "flatpak-user") - } - FF_PRINT_PACKAGE(snap) - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(brew); - } - else - { - FF_PRINT_PACKAGE_NAME(brew, "brew") - FF_PRINT_PACKAGE_NAME(brewCask, "brew-cask") - } - FF_PRINT_PACKAGE(macports) - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(scoop); - } - else - { - FF_PRINT_PACKAGE_NAME(scoopUser, counts.scoopGlobal ? "scoop-user" : "scoop") - FF_PRINT_PACKAGE_NAME(scoopGlobal, "scoop-global") - } - FF_PRINT_PACKAGE(choco) - FF_PRINT_PACKAGE(pkgtool) - FF_PRINT_PACKAGE(paludis) - FF_PRINT_PACKAGE(winget) - FF_PRINT_PACKAGE(opkg) - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(am); - } - else - { - FF_PRINT_PACKAGE_NAME(amSystem, "am") - FF_PRINT_PACKAGE_NAME(amUser, "appman") - } - FF_PRINT_PACKAGE(sorcery) - FF_PRINT_PACKAGE(lpkg) - FF_PRINT_PACKAGE(lpkgbuild) - if (options->combined) - { - FF_PRINT_PACKAGE_ALL(guix); - } - else - { - FF_PRINT_PACKAGE_NAME(guixSystem, "guix-system") - FF_PRINT_PACKAGE_NAME(guixUser, "guix-user") - FF_PRINT_PACKAGE_NAME(guixHome, "guix-home") - } - FF_PRINT_PACKAGE(linglong) - FF_PRINT_PACKAGE(pacstall) - FF_PRINT_PACKAGE(mport) - FF_PRINT_PACKAGE(pisi) - FF_PRINT_PACKAGE(soar) - - putchar('\n'); + FF_PRINT_PACKAGE(apk) + FF_PRINT_PACKAGE(pkg) + FF_PRINT_PACKAGE(pkgsrc) + FF_PRINT_PACKAGE(kiss) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(hpkg) + } else { + FF_PRINT_PACKAGE_NAME(hpkgSystem, + counts.hpkgUser ? "hpkg-system" : "hpkg") + FF_PRINT_PACKAGE_NAME(hpkgUser, "hpkg-user") + } + if (options->combined) { + FF_PRINT_PACKAGE_ALL(flatpak); + } else { + FF_PRINT_PACKAGE_NAME(flatpakSystem, + counts.flatpakUser ? "flatpak-system" : "flatpak") + FF_PRINT_PACKAGE_NAME(flatpakUser, "flatpak-user") + } + FF_PRINT_PACKAGE(snap) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(brew); + } else { + FF_PRINT_PACKAGE_NAME(brew, "brew") + FF_PRINT_PACKAGE_NAME(brewCask, "brew-cask") } - else - { - FF_PRINT_FORMAT_CHECKED(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ + FF_PRINT_PACKAGE(macports) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(scoop); + } else { + FF_PRINT_PACKAGE_NAME(scoopUser, + counts.scoopGlobal ? "scoop-user" : "scoop") + FF_PRINT_PACKAGE_NAME(scoopGlobal, "scoop-global") + } + FF_PRINT_PACKAGE(choco) + FF_PRINT_PACKAGE(pkgtool) + FF_PRINT_PACKAGE(paludis) + FF_PRINT_PACKAGE(winget) + FF_PRINT_PACKAGE(opkg) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(am); + } else { + FF_PRINT_PACKAGE_NAME(amSystem, "am") + FF_PRINT_PACKAGE_NAME(amUser, "appman") + } + FF_PRINT_PACKAGE(sorcery) + FF_PRINT_PACKAGE(lpkg) + FF_PRINT_PACKAGE(lpkgbuild) + if (options->combined) { + FF_PRINT_PACKAGE_ALL(guix); + } else { + FF_PRINT_PACKAGE_NAME(guixSystem, "guix-system") + FF_PRINT_PACKAGE_NAME(guixUser, "guix-user") + FF_PRINT_PACKAGE_NAME(guixHome, "guix-home") + } + FF_PRINT_PACKAGE(linglong) + FF_PRINT_PACKAGE(pacstall) + FF_PRINT_PACKAGE(mport) + FF_PRINT_PACKAGE(pisi) + FF_PRINT_PACKAGE(soar) + + putchar('\n'); + } else { + FF_PRINT_FORMAT_CHECKED( + FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, + ((FFformatarg[]){ FF_FORMAT_ARG(counts.all, "all"), FF_FORMAT_ARG(counts.pacman, "pacman"), FF_FORMAT_ARG(counts.pacmanBranch, "pacman-branch"), @@ -202,267 +182,304 @@ bool ffPrintPackages(FFPackagesOptions* options) FF_FORMAT_ARG(guixAll, "guix-all"), FF_FORMAT_ARG(hpkgAll, "hpkg-all"), })); - } + } - ffStrbufDestroy(&counts.pacmanBranch); + ffStrbufDestroy(&counts.pacmanBranch); - return true; + return true; } -void ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module) -{ - yyjson_val *key, *val; - size_t idx, max; - yyjson_obj_foreach(module, idx, max, key, val) - { - if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) - continue; - - if (unsafe_yyjson_equals_str(key, "disabled")) - { - if (!yyjson_is_null(val) && !yyjson_is_arr(val)) - { - ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid JSON value for %s", unsafe_yyjson_get_str(key)); - continue; - } - - options->disabled = FF_PACKAGES_FLAG_NONE; - - if (yyjson_is_arr(val)) - { - yyjson_val* flagObj; - size_t flagIdx, flagMax; - yyjson_arr_foreach(val, flagIdx, flagMax, flagObj) - { - if (!yyjson_is_str(flagObj)) - { - ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid JSON value for %s", unsafe_yyjson_get_str(key)); - continue; - } - const char* flag = unsafe_yyjson_get_str(flagObj); - - #define FF_TEST_PACKAGE_NAME(name) else if (ffStrEqualsIgnCase(flag, #name)) { options->disabled |= FF_PACKAGES_FLAG_ ## name ## _BIT; } - switch (toupper(flag[0])) - { - case 'A': if (false); - FF_TEST_PACKAGE_NAME(APK) - FF_TEST_PACKAGE_NAME(AM) - break; - case 'B': if (false); - FF_TEST_PACKAGE_NAME(BREW) - break; - case 'C': if (false); - FF_TEST_PACKAGE_NAME(CHOCO) - break; - case 'D': if (false); - FF_TEST_PACKAGE_NAME(DPKG) - break; - case 'E': if (false); - FF_TEST_PACKAGE_NAME(EMERGE) - FF_TEST_PACKAGE_NAME(EOPKG) - break; - case 'F': if (false); - FF_TEST_PACKAGE_NAME(FLATPAK) - break; - case 'G': if (false); - FF_TEST_PACKAGE_NAME(GUIX) - break; - case 'H': if (false); - FF_TEST_PACKAGE_NAME(HPKG) - break; - case 'K': if (false); - FF_TEST_PACKAGE_NAME(KISS) - break; - case 'L': if (false); - FF_TEST_PACKAGE_NAME(LPKG) - FF_TEST_PACKAGE_NAME(LPKGBUILD) - FF_TEST_PACKAGE_NAME(LINGLONG) - break; - case 'M': if (false); - FF_TEST_PACKAGE_NAME(MACPORTS) - FF_TEST_PACKAGE_NAME(MPORT) - break; - case 'N': if (false); - FF_TEST_PACKAGE_NAME(NIX) - break; - case 'O': if (false); - FF_TEST_PACKAGE_NAME(OPKG) - break; - case 'P': if (false); - FF_TEST_PACKAGE_NAME(PACMAN) - FF_TEST_PACKAGE_NAME(PACSTALL) - FF_TEST_PACKAGE_NAME(PALUDIS) - FF_TEST_PACKAGE_NAME(PISI) - FF_TEST_PACKAGE_NAME(PKG) - FF_TEST_PACKAGE_NAME(PKGTOOL) - FF_TEST_PACKAGE_NAME(PKGSRC) - break; - case 'R': if (false); - FF_TEST_PACKAGE_NAME(RPM) - break; - case 'S': if (false); - FF_TEST_PACKAGE_NAME(SCOOP) - FF_TEST_PACKAGE_NAME(SNAP) - FF_TEST_PACKAGE_NAME(SOAR) - FF_TEST_PACKAGE_NAME(SORCERY) - break; - case 'W': if (false); - FF_TEST_PACKAGE_NAME(WINGET) - break; - case 'X': if (false); - FF_TEST_PACKAGE_NAME(XBPS) - break; - } - #undef FF_TEST_PACKAGE_NAME - } - continue; - } - } - - if (unsafe_yyjson_equals_str(key, "combined")) - { - options->combined = yyjson_get_bool(val); +void ffParsePackagesJsonObject(FFPackagesOptions *options, yyjson_val *module) { + yyjson_val *key, *val; + size_t idx, max; + yyjson_obj_foreach(module, idx, max, key, val) { + if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) + continue; + + if (unsafe_yyjson_equals_str(key, "disabled")) { + if (!yyjson_is_null(val) && !yyjson_is_arr(val)) { + ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, + FF_PRINT_TYPE_DEFAULT, "Invalid JSON value for %s", + unsafe_yyjson_get_str(key)); + continue; + } + + options->disabled = FF_PACKAGES_FLAG_NONE; + + if (yyjson_is_arr(val)) { + yyjson_val *flagObj; + size_t flagIdx, flagMax; + yyjson_arr_foreach(val, flagIdx, flagMax, flagObj) { + if (!yyjson_is_str(flagObj)) { + ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, + FF_PRINT_TYPE_DEFAULT, "Invalid JSON value for %s", + unsafe_yyjson_get_str(key)); continue; + } + const char *flag = unsafe_yyjson_get_str(flagObj); + +#define FF_TEST_PACKAGE_NAME(name) \ + else if (ffStrEqualsIgnCase(flag, #name)) { \ + options->disabled |= FF_PACKAGES_FLAG_##name##_BIT; \ + } + switch (toupper(flag[0])) { + case 'A': + if (false) + ; + FF_TEST_PACKAGE_NAME(APK) + FF_TEST_PACKAGE_NAME(AM) + break; + case 'B': + if (false) + ; + FF_TEST_PACKAGE_NAME(BREW) + break; + case 'C': + if (false) + ; + FF_TEST_PACKAGE_NAME(CHOCO) + break; + case 'D': + if (false) + ; + FF_TEST_PACKAGE_NAME(DPKG) + break; + case 'E': + if (false) + ; + FF_TEST_PACKAGE_NAME(EMERGE) + FF_TEST_PACKAGE_NAME(EOPKG) + break; + case 'F': + if (false) + ; + FF_TEST_PACKAGE_NAME(FLATPAK) + break; + case 'G': + if (false) + ; + FF_TEST_PACKAGE_NAME(GUIX) + break; + case 'H': + if (false) + ; + FF_TEST_PACKAGE_NAME(HPKG) + break; + case 'K': + if (false) + ; + FF_TEST_PACKAGE_NAME(KISS) + break; + case 'L': + if (false) + ; + FF_TEST_PACKAGE_NAME(LPKG) + FF_TEST_PACKAGE_NAME(LPKGBUILD) + FF_TEST_PACKAGE_NAME(LINGLONG) + break; + case 'M': + if (false) + ; + FF_TEST_PACKAGE_NAME(MACPORTS) + FF_TEST_PACKAGE_NAME(MPORT) + break; + case 'N': + if (false) + ; + FF_TEST_PACKAGE_NAME(NIX) + break; + case 'O': + if (false) + ; + FF_TEST_PACKAGE_NAME(OPKG) + break; + case 'P': + if (false) + ; + FF_TEST_PACKAGE_NAME(PACMAN) + FF_TEST_PACKAGE_NAME(PACSTALL) + FF_TEST_PACKAGE_NAME(PALUDIS) + FF_TEST_PACKAGE_NAME(PISI) + FF_TEST_PACKAGE_NAME(PKG) + FF_TEST_PACKAGE_NAME(PKGTOOL) + FF_TEST_PACKAGE_NAME(PKGSRC) + break; + case 'R': + if (false) + ; + FF_TEST_PACKAGE_NAME(RPM) + break; + case 'S': + if (false) + ; + FF_TEST_PACKAGE_NAME(SCOOP) + FF_TEST_PACKAGE_NAME(SNAP) + FF_TEST_PACKAGE_NAME(SOAR) + FF_TEST_PACKAGE_NAME(SORCERY) + break; + case 'W': + if (false) + ; + FF_TEST_PACKAGE_NAME(WINGET) + break; + case 'X': + if (false) + ; + FF_TEST_PACKAGE_NAME(XBPS) + break; + } +#undef FF_TEST_PACKAGE_NAME } - - ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); + continue; + } } -} - -void ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) -{ - ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); - FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); - yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "disabled"); - #define FF_TEST_PACKAGE_NAME(name) else if ((options->disabled & FF_PACKAGES_FLAG_ ## name ## _BIT)) { \ - ffStrbufSetS(&buf, #name); \ - ffStrbufLowerCase(&buf); \ - yyjson_mut_arr_add_strbuf(doc, arr, &buf); \ + if (unsafe_yyjson_equals_str(key, "combined")) { + options->combined = yyjson_get_bool(val); + continue; } - if (false); - FF_TEST_PACKAGE_NAME(AM) - FF_TEST_PACKAGE_NAME(APK) - FF_TEST_PACKAGE_NAME(BREW) - FF_TEST_PACKAGE_NAME(CHOCO) - FF_TEST_PACKAGE_NAME(DPKG) - FF_TEST_PACKAGE_NAME(EMERGE) - FF_TEST_PACKAGE_NAME(EOPKG) - FF_TEST_PACKAGE_NAME(FLATPAK) - FF_TEST_PACKAGE_NAME(GUIX) - FF_TEST_PACKAGE_NAME(HPKG) - FF_TEST_PACKAGE_NAME(KISS) - FF_TEST_PACKAGE_NAME(LINGLONG) - FF_TEST_PACKAGE_NAME(LPKG) - FF_TEST_PACKAGE_NAME(LPKGBUILD) - FF_TEST_PACKAGE_NAME(MACPORTS) - FF_TEST_PACKAGE_NAME(MPORT) - FF_TEST_PACKAGE_NAME(NIX) - FF_TEST_PACKAGE_NAME(OPKG) - FF_TEST_PACKAGE_NAME(PACMAN) - FF_TEST_PACKAGE_NAME(PACSTALL) - FF_TEST_PACKAGE_NAME(PALUDIS) - FF_TEST_PACKAGE_NAME(PISI) - FF_TEST_PACKAGE_NAME(PKG) - FF_TEST_PACKAGE_NAME(PKGTOOL) - FF_TEST_PACKAGE_NAME(PKGSRC) - FF_TEST_PACKAGE_NAME(RPM) - FF_TEST_PACKAGE_NAME(SCOOP) - FF_TEST_PACKAGE_NAME(SNAP) - FF_TEST_PACKAGE_NAME(SOAR) - FF_TEST_PACKAGE_NAME(SORCERY) - FF_TEST_PACKAGE_NAME(WINGET) - FF_TEST_PACKAGE_NAME(XBPS) - #undef FF_TEST_PACKAGE_NAME - - yyjson_mut_obj_add_bool(doc, module, "combined", options->combined); -} - -bool ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) -{ - FFPackagesResult counts = {}; - ffStrbufInit(&counts.pacmanBranch); - const char* error = ffDetectPackages(&counts, options); + ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, + FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", + unsafe_yyjson_get_str(key)); + } +} - if(error) - { - yyjson_mut_obj_add_str(doc, module, "error", error); - return false; - } +void ffGeneratePackagesJsonConfig(FFPackagesOptions *options, + yyjson_mut_doc *doc, yyjson_mut_val *module) { + ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); + + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + yyjson_mut_val *arr = yyjson_mut_obj_add_arr(doc, module, "disabled"); +#define FF_TEST_PACKAGE_NAME(name) \ + else if ((options->disabled & FF_PACKAGES_FLAG_##name##_BIT)) { \ + ffStrbufSetS(&buf, #name); \ + ffStrbufLowerCase(&buf); \ + yyjson_mut_arr_add_strbuf(doc, arr, &buf); \ + } + if (false) + ; + FF_TEST_PACKAGE_NAME(AM) + FF_TEST_PACKAGE_NAME(APK) + FF_TEST_PACKAGE_NAME(BREW) + FF_TEST_PACKAGE_NAME(CHOCO) + FF_TEST_PACKAGE_NAME(DPKG) + FF_TEST_PACKAGE_NAME(EMERGE) + FF_TEST_PACKAGE_NAME(EOPKG) + FF_TEST_PACKAGE_NAME(FLATPAK) + FF_TEST_PACKAGE_NAME(GUIX) + FF_TEST_PACKAGE_NAME(HPKG) + FF_TEST_PACKAGE_NAME(KISS) + FF_TEST_PACKAGE_NAME(LINGLONG) + FF_TEST_PACKAGE_NAME(LPKG) + FF_TEST_PACKAGE_NAME(LPKGBUILD) + FF_TEST_PACKAGE_NAME(MACPORTS) + FF_TEST_PACKAGE_NAME(MPORT) + FF_TEST_PACKAGE_NAME(NIX) + FF_TEST_PACKAGE_NAME(OPKG) + FF_TEST_PACKAGE_NAME(PACMAN) + FF_TEST_PACKAGE_NAME(PACSTALL) + FF_TEST_PACKAGE_NAME(PALUDIS) + FF_TEST_PACKAGE_NAME(PISI) + FF_TEST_PACKAGE_NAME(PKG) + FF_TEST_PACKAGE_NAME(PKGTOOL) + FF_TEST_PACKAGE_NAME(PKGSRC) + FF_TEST_PACKAGE_NAME(RPM) + FF_TEST_PACKAGE_NAME(SCOOP) + FF_TEST_PACKAGE_NAME(SNAP) + FF_TEST_PACKAGE_NAME(SOAR) + FF_TEST_PACKAGE_NAME(SORCERY) + FF_TEST_PACKAGE_NAME(WINGET) + FF_TEST_PACKAGE_NAME(XBPS) +#undef FF_TEST_PACKAGE_NAME + + yyjson_mut_obj_add_bool(doc, module, "combined", options->combined); +} - yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); - - #define FF_APPEND_PACKAGE_COUNT(name) yyjson_mut_obj_add_uint(doc, obj, #name, counts.name); - - FF_APPEND_PACKAGE_COUNT(all) - FF_APPEND_PACKAGE_COUNT(amSystem) - FF_APPEND_PACKAGE_COUNT(amUser) - FF_APPEND_PACKAGE_COUNT(apk) - FF_APPEND_PACKAGE_COUNT(brew) - FF_APPEND_PACKAGE_COUNT(brewCask) - FF_APPEND_PACKAGE_COUNT(choco) - FF_APPEND_PACKAGE_COUNT(dpkg) - FF_APPEND_PACKAGE_COUNT(emerge) - FF_APPEND_PACKAGE_COUNT(eopkg) - FF_APPEND_PACKAGE_COUNT(flatpakSystem) - FF_APPEND_PACKAGE_COUNT(flatpakUser) - FF_APPEND_PACKAGE_COUNT(guixSystem) - FF_APPEND_PACKAGE_COUNT(guixUser) - FF_APPEND_PACKAGE_COUNT(guixHome) - FF_APPEND_PACKAGE_COUNT(hpkgSystem) - FF_APPEND_PACKAGE_COUNT(hpkgUser) - FF_APPEND_PACKAGE_COUNT(linglong) - FF_APPEND_PACKAGE_COUNT(mport) - FF_APPEND_PACKAGE_COUNT(nixDefault) - FF_APPEND_PACKAGE_COUNT(nixSystem) - FF_APPEND_PACKAGE_COUNT(nixUser) - FF_APPEND_PACKAGE_COUNT(opkg) - FF_APPEND_PACKAGE_COUNT(pacman) - FF_APPEND_PACKAGE_COUNT(pacstall) - FF_APPEND_PACKAGE_COUNT(paludis) - FF_APPEND_PACKAGE_COUNT(pisi) - FF_APPEND_PACKAGE_COUNT(pkg) - FF_APPEND_PACKAGE_COUNT(pkgtool) - FF_APPEND_PACKAGE_COUNT(pkgsrc) - FF_APPEND_PACKAGE_COUNT(macports) - FF_APPEND_PACKAGE_COUNT(rpm) - FF_APPEND_PACKAGE_COUNT(scoopUser) - FF_APPEND_PACKAGE_COUNT(scoopGlobal) - FF_APPEND_PACKAGE_COUNT(snap) - FF_APPEND_PACKAGE_COUNT(soar) - FF_APPEND_PACKAGE_COUNT(kiss) - FF_APPEND_PACKAGE_COUNT(sorcery) - FF_APPEND_PACKAGE_COUNT(winget) - FF_APPEND_PACKAGE_COUNT(xbps) - yyjson_mut_obj_add_strbuf(doc, obj, "pacmanBranch", &counts.pacmanBranch); - - return true; +bool ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions *options, + yyjson_mut_doc *doc, yyjson_mut_val *module) { + FFPackagesResult counts = {}; + ffStrbufInit(&counts.pacmanBranch); + + const char *error = ffDetectPackages(&counts, options); + + if (error) { + yyjson_mut_obj_add_str(doc, module, "error", error); + return false; + } + + yyjson_mut_val *obj = yyjson_mut_obj_add_obj(doc, module, "result"); + +#define FF_APPEND_PACKAGE_COUNT(name) \ + yyjson_mut_obj_add_uint(doc, obj, #name, counts.name); + + FF_APPEND_PACKAGE_COUNT(all) + FF_APPEND_PACKAGE_COUNT(amSystem) + FF_APPEND_PACKAGE_COUNT(amUser) + FF_APPEND_PACKAGE_COUNT(apk) + FF_APPEND_PACKAGE_COUNT(brew) + FF_APPEND_PACKAGE_COUNT(brewCask) + FF_APPEND_PACKAGE_COUNT(choco) + FF_APPEND_PACKAGE_COUNT(dpkg) + FF_APPEND_PACKAGE_COUNT(emerge) + FF_APPEND_PACKAGE_COUNT(eopkg) + FF_APPEND_PACKAGE_COUNT(flatpakSystem) + FF_APPEND_PACKAGE_COUNT(flatpakUser) + FF_APPEND_PACKAGE_COUNT(guixSystem) + FF_APPEND_PACKAGE_COUNT(guixUser) + FF_APPEND_PACKAGE_COUNT(guixHome) + FF_APPEND_PACKAGE_COUNT(hpkgSystem) + FF_APPEND_PACKAGE_COUNT(hpkgUser) + FF_APPEND_PACKAGE_COUNT(linglong) + FF_APPEND_PACKAGE_COUNT(mport) + FF_APPEND_PACKAGE_COUNT(nixDefault) + FF_APPEND_PACKAGE_COUNT(nixSystem) + FF_APPEND_PACKAGE_COUNT(nixUser) + FF_APPEND_PACKAGE_COUNT(opkg) + FF_APPEND_PACKAGE_COUNT(pacman) + FF_APPEND_PACKAGE_COUNT(pacstall) + FF_APPEND_PACKAGE_COUNT(paludis) + FF_APPEND_PACKAGE_COUNT(pisi) + FF_APPEND_PACKAGE_COUNT(pkg) + FF_APPEND_PACKAGE_COUNT(pkgtool) + FF_APPEND_PACKAGE_COUNT(pkgsrc) + FF_APPEND_PACKAGE_COUNT(macports) + FF_APPEND_PACKAGE_COUNT(rpm) + FF_APPEND_PACKAGE_COUNT(scoopUser) + FF_APPEND_PACKAGE_COUNT(scoopGlobal) + FF_APPEND_PACKAGE_COUNT(snap) + FF_APPEND_PACKAGE_COUNT(soar) + FF_APPEND_PACKAGE_COUNT(kiss) + FF_APPEND_PACKAGE_COUNT(sorcery) + FF_APPEND_PACKAGE_COUNT(winget) + FF_APPEND_PACKAGE_COUNT(xbps) + yyjson_mut_obj_add_strbuf(doc, obj, "pacmanBranch", &counts.pacmanBranch); + + return true; } -void ffInitPackagesOptions(FFPackagesOptions* options) -{ - ffOptionInitModuleArg(&options->moduleArgs, "󰏖"); +void ffInitPackagesOptions(FFPackagesOptions *options) { + ffOptionInitModuleArg(&options->moduleArgs, "󰏖"); - options->disabled = FF_PACKAGES_DISABLE_LIST; - options->combined = false; + options->disabled = FF_PACKAGES_DISABLE_LIST; + options->combined = false; } -void ffDestroyPackagesOptions(FFPackagesOptions* options) -{ - ffOptionDestroyModuleArg(&options->moduleArgs); +void ffDestroyPackagesOptions(FFPackagesOptions *options) { + ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffPackagesModuleInfo = { .name = FF_PACKAGES_MODULE_NAME, - .description = "List installed package managers and count of installed packages", - .initOptions = (void*) ffInitPackagesOptions, - .destroyOptions = (void*) ffDestroyPackagesOptions, - .parseJsonObject = (void*) ffParsePackagesJsonObject, - .printModule = (void*) ffPrintPackages, - .generateJsonResult = (void*) ffGeneratePackagesJsonResult, - .generateJsonConfig = (void*) ffGeneratePackagesJsonConfig, - .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { + .description = + "List installed package managers and count of installed packages", + .initOptions = (void *)ffInitPackagesOptions, + .destroyOptions = (void *)ffDestroyPackagesOptions, + .parseJsonObject = (void *)ffParsePackagesJsonObject, + .printModule = (void *)ffPrintPackages, + .generateJsonResult = (void *)ffGeneratePackagesJsonResult, + .generateJsonConfig = (void *)ffGeneratePackagesJsonConfig, + .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){ {"Number of all packages", "all"}, {"Number of pacman packages", "pacman"}, {"Pacman branch on manjaro", "pacman-branch"}, @@ -511,5 +528,4 @@ FFModuleBaseInfo ffPackagesModuleInfo = { {"Total number of all brew packages", "brew-all"}, {"Total number of all guix packages", "guix-all"}, {"Total number of all hpkg packages", "hpkg-all"}, - })) -}; + }))};