sheepy

build system (sheepy) and package manager (spm) for C
git clone https://spartatek.se/git/sheepy.git
Log | Files | Refs | README | LICENSE

spm.c (57272B)


      1 #! /usr/bin/env sheepy
      2 // MIT License
      3 //
      4 // Copyright (c) 2026 Remy Noulin
      5 //
      6 // Permission is hereby granted, free of charge, to any person obtaining a copy
      7 // of this software and associated documentation files (the "Software"), to deal
      8 // in the Software without restriction, including without limitation the rights
      9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     10 // copies of the Software, and to permit persons to whom the Software is
     11 // furnished to do so, subject to the following conditions:
     12 //
     13 // The above copyright notice and this permission notice shall be included in all
     14 // copies or substantial portions of the Software.
     15 //
     16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     22 // SOFTWARE.
     23 //
     24 //
     25 // sheepy package manager
     26 // the published packages have the package information and the source
     27 // install downloads and compiles the packages as static and dynamic libraries
     28 //
     29 // dependencies: tar curl pigz sheepy
     30 //
     31 // commands:
     32 //   publish:           upload pacakge to spmRegistry
     33 //   install pkg:       install latest package in current folder
     34 //   install -g pkg:    install latest package globally (system wide or specified folder in ~/.sheepy/config.yml)
     35 //   install pkg@1.0.0: install specific version
     36 //
     37 // To be able to publish, the registry key has to be set in ~/.sheepy/config.yml
     38 //
     39 // TODO
     40 // add command to set registry key
     41 
     42 #include "libsheepyObject.h"
     43 #include "spm.h"
     44 #include "common.h"
     45 #include "sha256/sha256.h"
     46 
     47 #define internal static
     48 
     49 #include <stdio.h>
     50 #include <dirent.h>
     51 #include <libgen.h>
     52 #include <stdbool.h>
     53 #include <string.h>
     54 #include <sys/wait.h>
     55 #include <stdlib.h>
     56 #include <unistd.h>
     57 
     58 #ifndef unitTest
     59 #endif
     60 int MAIN(int ARGC, char** ARGV);
     61 void publish(smallDictt *cfgD, bool pigz);
     62 void installPackages(bool isGlobal, smallDictt *cfgD, smallArrayt *options, bool pigz);
     63 void childInstall(void *Args);
     64 void installAPackage(bool isGlobal, smallDictt *cfgD, smallStringt *o, bool pigz);
     65 void compilePackage(char *name, bool isGlobal, smallDictt *cfgD, char *currentVersion);
     66 smallDictt *loadYML(char *path);
     67 bool installDependencies(bool isGlobal, smallDictt *cfgD, smallDictt *d, const char *name);
     68 int testPackage(smallDictt *cfgD, smallDictt *packageyml);
     69 void memcheckPackage(smallDictt *cfgD, smallDictt *packageyml);
     70 void asanPackage(smallDictt *cfgD, smallDictt *packageyml);
     71 void listAllPackages(smallDictt *cfgD, smallArrayt *options);
     72 void showPackage(smallDictt *cfgD, smallStringt *o);
     73 void showPackageInfo(smallStringt *path);
     74 void updatePackages(bool isGlobal, smallDictt *cfgD, smallArrayt *options, bool pigz);
     75 void uninstallPackages(bool isGlobal, smallDictt *cfgD, smallArrayt *options);
     76 void uninstallDependencies(smallDictt *cfgD, smallDictt *d);
     77 void spawnProcDeps(cbFt cb, void *arg);
     78 void waitForJobsDeps(void);
     79 void findPrint(smallDictt *d);
     80 char **requestRegistry(smallDictt *cfgD, smallJsont *json);
     81 void showPackageListFromRegistry(char **result, smallJsont *json);
     82 void whoamiResult(char **result, smallJsont *json);
     83 void profileResult(char **result, smallJsont *json);
     84 void unpublishResult(char **result, smallJsont *json);
     85 void apikeyResult(char **result, smallJsont *json);
     86 void addUserResult(char **result, smallJsont *json);
     87 
     88 int argc; char **argv;
     89 
     90 #define LOCAL         false
     91 #define GLOBAL        true
     92 #define CURRENT_YML   NULL
     93 
     94 jobst jobsDeps;
     95 
     96 char *localPackageDir;
     97 
     98 #ifndef unitTest
     99 // Remove main when running the unit tests
    100 #define MAIN   main
    101 #endif
    102 int MAIN(int ARGC, char** ARGV) {
    103   bool bum;
    104   char *dum = NULL;
    105   char *homedir = NULL;
    106   bool pigz = false;;
    107 
    108   argc = ARGC; argv = ARGV;;// steps
    109   // load config.yml
    110   // check arguments
    111   // set parameters
    112   // check if pigz or gzip are installed
    113   // publish package to registry
    114     // get registry user key
    115     // load package information
    116     // get package information
    117     // upload package
    118   // install package
    119     // choose install destination
    120     // create package folder
    121     // package name and version
    122     // download package
    123     // extract package in package folder
    124     // compile package
    125     // build with sheepy
    126     // if global then link command to bin from package folder
    127     // install dependencies
    128 
    129   // load config.yml
    130   smallJsont *cfgJ = loadSheepyConfig(&homedir);
    131   smallDictt *cfgD = getTopG(cfgJ, rtSmallDictt);
    132   //putsG(cfgJ);
    133 
    134   // check arguments
    135   // need at least 1 argument
    136   if (argc < 2) {
    137     puts("Nothing to do. "BLD GRN"spm help"RST" for help\n"); {
    138     goto help;
    139   }
    140     }
    141 
    142   // find current local package dir
    143   localPackageDir = findLocalPackageDir(cfgD);
    144 
    145   //logVarG(localPackageDir);
    146 
    147   if (eqS(argv[1], "-h") || eqS(argv[1], "--help") || eqS(argv[1], "help")) {
    148     help:
    149     putsG(BLD GRN "Sheepy Package Manager Help" RST);
    150     putsG("  " UDL YLW "Environment" RST);
    151     putsG("  The configuration is located in " UDL HOME_DIR CONFIG_NAME RST "\n");
    152     char *dum = expandHome(HOME_DIR CONFIG_NAME);;
    153     if (!fileExists(dum)) {
    154       printf("  %s missing.", dum);
    155     }
    156     printf("  The build directory is located in " UDL "%sbuild/" RST "\n", HOME_DIR);
    157     printf("  The local package repository is located in " UDL "%s" RST "\n", localPackageDir);
    158     if (dum) {
    159       var tmp = catS(getG(cfgD, rtChar, "system"), "/", PACKAGE_DIR);
    160       pErrorNULL(bUniqSlash(tmp));
    161       printf("  The global package repository is located in " UDL "%s" RST "\n", tmp);
    162       free(tmp);
    163       tmp     = catS(getG(cfgD, rtChar, "system"), "/bin");
    164       pErrorNULL(bUniqSlash(tmp));
    165       printf("  Commands from packages are added in " UDL "%s" RST "\n", tmp);
    166       free(tmp);
    167     }
    168     if (hasG(cfgD, "parallelSpm") && !getG(cfgD, rtI32, "parallelSpm")) {
    169       puts(BLD RED "  Parallel package processing is disabled." RST);
    170     }
    171     if (hasG(cfgD, "parallelSheepy") && !getG(cfgD, rtI32, "parallelSheepy")) {
    172       puts(BLD RED "  Parallel compilation is disabled." RST);
    173     }
    174     free(dum);
    175     put
    176     putsG("For sheepy help, run: sheepy -h");
    177     put
    178     putsG(BLD CYN"  new (PKG1) (PKG2)"RST"             - create a new empty package, without parameters print default package.yml");
    179     putsG(BLD CYN"  add"RST"                           - add packages in "PACKAGE_DIR" folder to dependencies list in package.yml");
    180     putsG(BLD CYN"  publish"RST"                       - upload package to spm registry");
    181     putsG(BLD CYN"  install (PKG1) (PKG2)"RST"         - install package from spm registry, without parameters install packages listed in package.yml");
    182     putsG(BLD CYN"  -g install PKG1 (PKG2)"RST"        - install package globally in the system");
    183     putsG(BLD CYN"  uninstall PKG1 (PKG2)"RST"         - uninstall package");
    184     putsG(BLD CYN"  -g uninstall PKG1 (PKG2)"RST"      - uninstall package globally");
    185     putsG(BLD CYN"  test"RST"                          - compile testBin and run");
    186     putsG(BLD CYN"  memcheck"RST"                      - compile memcheckBin and run with the memcheck command");
    187     putsG(BLD CYN"  showmc"RST"                        - show the memcheck command, either in " HOME_DIR CONFIG_NAME " or in package.yml");
    188     putsG(BLD CYN"  doc"RST"                           - generate the documentation by running the documentation command from package.yml");
    189     putsG(BLD CYN"  show (PKG1 PKG2)"RST"              - show list of package information in the system");
    190     putsG(BLD CYN"  -g show (PKG1 PKG2)"RST"           - show list of package information installed globally in the system");
    191     putsG(BLD CYN"  update|upgrade (PKG1 PKG2)"RST"    - update package");
    192     putsG(BLD CYN"  -g update|upgrade (PKG1 PKG2)"RST" - update packages globally");
    193     putsG(BLD CYN"  find PKG1 (PKG2)"RST"              - find package in the registry");
    194     putsG(BLD CYN"  top"RST"                           - top packages in the registry");
    195     putsG(BLD CYN"  hot"RST"                           - hot packages in the registry");
    196     putsG(BLD CYN"  latest"RST"                        - latest packages in the registry");
    197     putsG(BLD CYN"  whoami"RST"                        - user associated with the configured api key");
    198     putsG(BLD CYN"  profile USERNAME"RST"              - show user profile");
    199     putsG(BLD CYN"  unpublish PKG1 (PKG2)"RST"         - unpublish pacakges in the registry");
    200     putsG(BLD CYN"  info PKG1 (PKG2)"RST"              - show package information");
    201     putsG(BLD CYN"  adduser"RST"                       - create an account in the registry");
    202     putsG(BLD CYN"  apikey"RST"                        - get api key for a user, to use the key edit the key field in " HOME_DIR CONFIG_NAME); {
    203     XSUCCESS
    204   }
    205     }
    206 
    207   // set parameters
    208   // global is an option associated with install to install a package in the system
    209 
    210   createAllocateSmallArray(options);
    211 
    212   // bum is set to true when a command is found
    213   bum = false;
    214   rangeFrom(i, 1, argc)
    215     if (eqG(argv[i], "publish")) {
    216       params.publish += 1;
    217       bum = true;
    218     }
    219     else if (eqG(argv[i], "new")) {
    220       params.new += 1;
    221       bum = true;
    222     }
    223     else if (eqG(argv[i], "add")) {
    224       params.add += 1;
    225       bum = true;
    226     }
    227     else if (eqG(argv[i], "install")) {
    228       params.install += 1;
    229       bum = true;
    230     }
    231     else if (eqG(argv[i], "uninstall")) {
    232       params.uninstall += 1;
    233       bum = true;
    234     }
    235     else if (eqG(argv[i], "test")) {
    236       params.test += 1;
    237       bum = true;
    238     }
    239     else if (eqG(argv[i], "memcheck")) {
    240       params.memcheck += 1;
    241       bum = true;
    242     }
    243     else if (eqG(argv[i], "showmc")) {
    244       params.showmcheck += 1;
    245       bum = true;
    246     }
    247     else if (eqG(argv[i], "asan")) {
    248       params.asan += 1;
    249       bum = true;
    250     }
    251     else if (eqG(argv[i], "showasan")) {
    252       params.showasan += 1;
    253       bum = true;
    254     }
    255     else if (eqG(argv[i], "doc")) {
    256       params.doc += 1;
    257       bum = true;
    258     }
    259     else if (eqG(argv[i], "-g")) {
    260       params.global += 1;
    261     }
    262     else if (eqG(argv[i], "show")) {
    263       params.show += 1;
    264       bum = true;
    265     }
    266     else if (eqG(argv[i], "update") || eqG(argv[i], "upgrade")) {
    267       params.update += 1;
    268       bum = true;
    269     }
    270     else if (eqG(argv[i], "find")) {
    271       params.find += 1;
    272       bum = true;
    273     }
    274     else if (eqG(argv[i], "top")) {
    275       params.top += 1;
    276       bum = true;
    277     }
    278     else if (eqG(argv[i], "hot")) {
    279       params.hot += 1;
    280       bum = true;
    281     }
    282     else if (eqG(argv[i], "latest")) {
    283       params.latest += 1;
    284       bum = true;
    285     }
    286     else if (eqG(argv[i], "whoami")) {
    287       params.whoami += 1;
    288       bum = true;
    289     }
    290     else if (eqG(argv[i], "profile")) {
    291       params.profile += 1;
    292       bum = true;
    293     }
    294     else if (eqG(argv[i], "unpublish")) {
    295       params.unpublish += 1;
    296       bum = true;
    297     }
    298     else if (eqG(argv[i], "info")) {
    299       params.info += 1;
    300       bum = true;
    301     }
    302     else if (eqG(argv[i], "adduser")) {
    303       params.adduser += 1;
    304       bum = true;
    305     }
    306     else if (eqG(argv[i], "apikey")) {
    307       params.apikey += 1;
    308       bum = true;
    309     }
    310     else if (params.new || params.install || params.show || params.update || params.uninstall || params.find || params.profile || params.unpublish || params.info) {
    311       pushG(options, argv[i]);
    312     }
    313 
    314   if (!bum) {
    315     putsG(BLD RED "Command not found." RST);
    316     XFAILURE
    317   }
    318 
    319   int paramsSum = params.install + params.publish + params.show + params.new + params.update + params.uninstall + params.find + params.top + params.hot + params.latest + params.whoami + params.profile + params.unpublish + params.info + params.adduser + params.apikey + params.test + params.memcheck + params.showmcheck + params.asan + params.showasan + params.doc + params.add;
    320   if (paramsSum > 1) {
    321     putsG(BLD RED "Conflicting commands." RST);
    322     XFAILURE
    323   }
    324 
    325   if (params.adduser) {
    326 
    327     newUser:
    328     printf("Username: ");
    329     char *username = readS();
    330 
    331     if (isBlankG(username)) {
    332       free(username);
    333       puts(BLD RED "Incorrect input. Enter new username." RST);
    334       goto newUser;
    335     }
    336 
    337     newName:
    338     printf("Name: ");
    339     char *name = readS();
    340 
    341     if (isBlankG(name)) {
    342       free(name);
    343       puts(BLD RED "Incorrect input. Enter new name." RST);
    344       goto newName;
    345     }
    346 
    347     newPassword:
    348     printf("Password: ");
    349     char *password = readPasswordS();;
    350 
    351     printf("Confirm password: ");
    352     char *cpassword = readPasswordS();
    353 
    354     if (isBlankG(password) || !eqG(password, cpassword)) {
    355       freeManyS(password, cpassword);
    356       puts(BLD RED "Incorrect input, enter new password." RST);
    357       goto newPassword;
    358     }
    359     if (lenG(password) < 8) {
    360       freeManyS(password, cpassword);
    361       puts(BLD RED "Too short, minimum 8 random characters, enter new password." RST);
    362       goto newPassword;
    363     }
    364     // clear password from memory
    365     pError0(zeroS(cpassword));
    366     free(cpassword);
    367 
    368     newEmail:
    369     printf("Public Email: ");
    370     char *email = readS();;
    371 
    372     if (lenG(email) < 2 || countG(email, '@') != 1 || startsWithG(email, "@") || endsWithG(email, "@")) {
    373       free(email);
    374       puts(BLD RED "Incorrect input, enter another email address." RST);
    375       goto newEmail;
    376     }
    377 
    378     //logVarG(username);
    379     //logVarG(name);
    380     //logVarG(password);
    381     //logVarG(email);
    382 
    383     char *hash = sha256S(password);;
    384     // clear password from memory
    385     pError0(zeroS(password));
    386     free(password);
    387 
    388     createAllocateSmallJson(json);
    389     pErrorNULL(setG(json, "method", "adduser"));
    390     createSmallArray(a);
    391     pErrorNULL(pushG(&a, username));
    392     pErrorNULL(pushG(&a, hash));
    393     pErrorNULL(pushG(&a, email));
    394     pErrorNULL(pushG(&a, name));
    395     pErrorNULL(setG(json, "params", &a));
    396 
    397     freeManyS(username, hash, email, name);
    398 
    399     //logVarG(json);
    400     char **result = requestRegistry(cfgD, json);
    401     // Success: result=["Ls28ZCj_i19JsLXxP5o9OcbSN0pj9KTEBNH,Y,IaiyI",""]
    402     // Error:   result=["","User USERNAME already exists!"]
    403 
    404     addUserResult(result, json);
    405 
    406     terminateG(json);
    407     XSUCCESS
    408   }
    409 
    410   if (params.apikey) {
    411 
    412     printf("Username: ");
    413     char *username = readS();
    414 
    415     printf("Password: ");
    416     char *password = readPasswordS();
    417 
    418     //logVarG(username);
    419     //logVarG(password);
    420 
    421     char *hash = sha256S(password);;
    422     // clear password from memory
    423     pError0(zeroS(password));
    424     free(password);
    425 
    426     createAllocateSmallJson(json);
    427     pErrorNULL(setG(json, "method", "apikey"));
    428     createSmallArray(ar);
    429     pErrorNULL(pushG(&ar, username));
    430     pErrorNULL(pushG(&ar, hash));
    431     pErrorNULL(setG(json, "params", &ar));
    432 
    433     freeManyS(username, hash);
    434 
    435     char **result = requestRegistry(cfgD, json);
    436     apikeyResult(result, json);
    437 
    438     terminateG(json);
    439     XSUCCESS
    440   }
    441 
    442   if (params.whoami) {
    443     char *key = getG(cfgD, rtChar, "key");
    444     if (!key || eqG(key, "uninitialized")) {
    445       puts(BLD RED "user key missing in " HOME_DIR CONFIG_NAME "!" RST);
    446       // TODO free stuff?
    447       XFAILURE
    448     }
    449 
    450     createAllocateSmallJson(json);
    451     pErrorNULL(setG(json, "method", "whoami"));
    452     pErrorNULL(setG(json, "key", key));
    453 
    454     char **result = requestRegistry(cfgD, json);
    455     // result=[{"username":"remy","name":"Remy Noulin","publicemail":"eqw@wdqd"}]
    456     whoamiResult(result, json);
    457 
    458     terminateG(json);
    459     XSUCCESS
    460   }
    461 
    462   if (params.profile) {
    463     if (!lenG(options)) {
    464       puts(BLD RED "Missing parameters!" RST);
    465       XFAILURE
    466     }
    467 
    468     createAllocateSmallJson(json);
    469     pErrorNULL(setG(json, "method", "profile"));
    470     pErrorNULL(setNFreeG(json, "params", dupG(options)));
    471 
    472     char **result = requestRegistry(cfgD, json);
    473     //result=[{"username":"remy","name":"Remy Noulin","publicemail":"eqw@wdqd","packages":["aCmd","cfp","cpy","here","dmce","forest","inotify","mkparents","mve","normalize","easydoneitCTui","searchReplace","sheepyExamples","md4c","preprocessor","md","files","dog","pp","ini","liveserver"]}]
    474     profileResult(result, json);
    475 
    476     terminateG(json);
    477     XSUCCESS
    478   }
    479 
    480   if (params.unpublish) {
    481     if (!lenG(options)) {
    482       puts(BLD RED "Missing parameters!" RST);
    483       XFAILURE
    484     }
    485     char *key = getG(cfgD, rtChar, "key");
    486     if (!key || eqG(key, "uninitialized")) {
    487       puts(BLD RED "user key missing in " HOME_DIR CONFIG_NAME "!" RST);
    488       // TODO free stuff?
    489       XFAILURE
    490     }
    491 
    492     createAllocateSmallJson(json);
    493     pErrorNULL(setG(json, "method", "unpublish"));
    494     pErrorNULL(setNFreeG(json, "params", dupG(options)));
    495     pErrorNULL(setG(json, "key", key));
    496 
    497     char **result = requestRegistry(cfgD, json);
    498     // result=["TT","simpleTemplates"]
    499     unpublishResult(result, json);
    500 
    501     terminateG(json);
    502     XSUCCESS
    503   }
    504 
    505   if (params.info) {
    506     if (!lenG(options)) {
    507       puts(BLD RED "Missing parameters!" RST);
    508       XFAILURE
    509     }
    510 
    511     createAllocateSmallJson(json);
    512     pErrorNULL(setG(json, "method", "info"));
    513     pErrorNULL(setNFreeG(json, "params", dupG(options)));
    514 
    515     char **result = requestRegistry(cfgD, json);
    516     showPackageListFromRegistry(result, json);
    517 
    518     terminateG(json);
    519     XSUCCESS
    520   }
    521 
    522   if (params.top) {
    523     createAllocateSmallJson(json);
    524     pErrorNULL(setG(json, "method", "top"));
    525 
    526     char **result = requestRegistry(cfgD, json);
    527     showPackageListFromRegistry(result, json);
    528 
    529     terminateG(json);
    530     XSUCCESS
    531   }
    532 
    533   if (params.hot) {
    534     createAllocateSmallJson(json);
    535     pErrorNULL(setG(json, "method", "hot"));
    536 
    537     char **result = requestRegistry(cfgD, json);
    538     showPackageListFromRegistry(result, json);
    539 
    540     terminateG(json);
    541     XSUCCESS
    542   }
    543 
    544   if (params.latest) {
    545     createAllocateSmallJson(json);
    546     pErrorNULL(setG(json, "method", "latest"));
    547 
    548     char **result = requestRegistry(cfgD, json);
    549     showPackageListFromRegistry(result, json);
    550 
    551     terminateG(json);
    552     XSUCCESS
    553   }
    554 
    555   if (params.find) {
    556     if (!lenG(options)) {
    557       puts(BLD RED "Find needs at least one parameter!" RST);
    558       XFAILURE
    559     }
    560     createAllocateSmallJson(json);
    561     pErrorNULL(setG(json, "method", "find"));
    562 
    563     // put options in a string in an array
    564     createAllocateSmallArray(a);
    565     pErrorNULL(pushNFreeG(a, joinG(options, ' ')));
    566     pErrorNULL(setNFreeG(json, "params", a));
    567     //logVarG(json);
    568 
    569     char **result = requestRegistry(cfgD, json);
    570     showPackageListFromRegistry(result, json);
    571 
    572     terminateG(json);
    573     XSUCCESS
    574   }
    575 
    576   if (params.new) {
    577     if (!lenG(options)) {
    578       // show package.yml template
    579       putsG(PKG_TEMPLATE);
    580       XSUCCESS
    581     }
    582 
    583      iter(options, O) {
    584       castS(o, O);
    585       pError0(mkdirParentsG(o));
    586       pError0(chDirG(o));
    587       sourceFromTemplate(ssGet(o));
    588       pkgFromTemplate(ssGet(o));
    589       pError0(chDirG(".."));
    590      }
    591     XSUCCESS
    592   }
    593 
    594   if (params.show) {
    595     if (argc < 3) {
    596       // show dependencies when package.yml is in the current directory
    597       if (fileExists(PACKAGE)) {
    598         smallDictt *d = loadYML(PACKAGE);
    599         smallDictt *deps = getG(d, rtSmallDictt, "dependencies");
    600         if (deps) {
    601           char **depsL = keysG(deps);
    602           if (fileExists(PACKAGE_DIR)) {
    603             // list packages install PACKAGE_DIR dir and not listed in dependencies
    604             var packagesInDir = readDirDir(PACKAGE_DIR);
    605             forEachS(packagesInDir, dir) {
    606               if ((!hasG(depsL, dir))) {
    607                 printf(BLD RED "%s"RST" installed and not in dependencies\n\n", dir);
    608             }
    609               }
    610             freeG(packagesInDir);
    611           }
    612           fromArrayNFreeG(options, depsL, 0);
    613         }
    614         terminateG(d);
    615         if (lenG(options)) {
    616           params.generatedPackageList = 1;
    617       }
    618         }
    619       if (!params.generatedPackageList && fileExists(PACKAGE_DIR)) {
    620         // no dependencies in package.yml, check if there is shpPackages folder
    621         char **shpp = readDirDir(PACKAGE_DIR);
    622         fromArrayNFreeG(options, shpp, 0);
    623         if (lenG(options)) {
    624           params.generatedPackageList = 1;
    625     }
    626       }
    627         }
    628 
    629     if ((!params.generatedPackageList && (argc < 3)) || ((argc == 3) && (params.global))) {
    630       // no package is specified and package.yml not found, list all global packages
    631       params.global = 1;
    632       listAllPackages(cfgD, options);
    633     }
    634 
    635     sortG(options);
    636     // show packages in parameters or all packages
    637     iter(options, O) {
    638       castS(o, O);
    639       showPackage(cfgD, o);
    640     }
    641     XSUCCESS
    642   }
    643 
    644   // check if pigz or gzip are installed
    645   createAllocateSmallArray(out);
    646   createAllocateSmallArray(er);
    647   execO("which pigz", out, er);
    648   #if (__sun__)
    649   if (not startsWithG(getG(out, rtChar, 0) , "no pigz")) {
    650     pigz = true;
    651   }
    652   else {
    653     emptyG(out);
    654     execO("which gzip", out, er);
    655     if (startsWithG(getG(out, rtChar, 0) , "no gzip")) {
    656       puts(BLD RED "Neither pigz nor gunzip are not found." RST);
    657       XFAILURE
    658   }
    659     }
    660   #else // #if (__sun__)
    661   if (lenG(out)) {
    662     pigz = true;
    663   }
    664   else {
    665     execO("which gzip", out, er);
    666     if (!lenG(out)) {
    667       puts(BLD RED "Neither pigz nor gunzip are not found." RST);
    668       XFAILURE
    669   }
    670     }
    671   #endif
    672   // #if __sun__
    673   terminateManyO(out, er);
    674 
    675   if (params.update) {
    676     if (argc < 3) {
    677       // update/install dependencies when package.yml is in the current directory
    678       if (fileExists(PACKAGE)) {
    679         smallDictt *d = loadYML(PACKAGE);
    680         smallDictt *deps = getG(d, rtSmallDictt, "dependencies");
    681         if (deps) {
    682           char **depsL = keysG(deps);
    683           if (fileExists(PACKAGE_DIR)) {
    684             // list packages install PACKAGE_DIR dir and not listed in dependencies
    685             var packagesInDir = readDirDir(PACKAGE_DIR);
    686             forEachS(packagesInDir, dir) {
    687               if ((!hasG(depsL, dir))) {
    688                 printf(BLD RED "%s"RST" installed and not in dependencies, to update this package run: "BLD"spm update %s"RST"\n\n", dir, dir);
    689             }
    690               }
    691             freeG(packagesInDir);
    692           }
    693           fromArrayNFreeG(options, depsL, 0);
    694         }
    695         terminateG(d);
    696         if (lenG(options)) {
    697           params.generatedPackageList = 1;
    698       }
    699         }
    700       if (!params.generatedPackageList && fileExists(PACKAGE_DIR)) {
    701         // no dependencies in package.yml, check if there is shpPackages folder
    702         char **shpp = readDirDir(PACKAGE_DIR);
    703         fromArrayNFreeG(options, shpp, 0);
    704         if (lenG(options)) {
    705           params.generatedPackageList = 1;
    706     }
    707       }
    708         }
    709 
    710     if ((!params.generatedPackageList && (argc < 3)) || ((argc == 3) && (params.global))) {
    711       // no package is specified and package.yml not found, list all global packages
    712       params.global = 1;
    713       listAllPackages(cfgD, options);
    714       if (lenG(options)) {
    715         params.generatedPackageList = 1;
    716     }
    717       }
    718 
    719     sortG(options);
    720     // update packages in parameters or all packages
    721     updatePackages(params.global, cfgD, options, pigz);
    722     XSUCCESS
    723   }
    724 
    725   if (params.add) {
    726     if (not isDir(PACKAGE_DIR)) {
    727       puts("'"PACKAGE_DIR"' folder not found in current path. Nothing to do.");
    728     }
    729     else {
    730       if (!fileExists(PACKAGE)) {
    731         // command line is spm install and there is no package.yml
    732         puts(BLD RED "'package.yml' in current path!" RST);
    733         XFAILURE
    734       }
    735       char **installedPackages = readDirDir(PACKAGE_DIR);
    736       if (not isEmptyG(installedPackages)) {
    737         createSmallJson(pkgInfo);
    738         readFileG(&pkgInfo, PACKAGE);
    739         smallDictt *pkgDeps = getG(&pkgInfo, rtSmallDictt, "dependencies");
    740         if (not pkgDeps) {
    741           // there are no dependencies in package.yml
    742           initiateG(&pkgDeps);
    743           forEachS(installedPackages, pkg) {
    744             // add pkg to dependency list in package.yml
    745             pErrorNULL(setG(pkgDeps, pkg, ""));
    746             printf("Added %s to dependencies", pkg);
    747             printf("\n");
    748           }
    749           pErrorNULL(setNFreeG(&pkgInfo, "dependencies", pkgDeps));
    750         }
    751         else {
    752           forEachS(installedPackages, pkg) {
    753             if (not hasG(pkgDeps, pkg)) {
    754               // add pkg to dependency list in package.yml
    755               pErrorNULL(setG(pkgDeps, pkg, ""));
    756               printf("Added %s to dependencies", pkg);
    757               printf("\n");
    758           }
    759             }
    760           pErrorNULL(setNFreePG(&pkgInfo, "dependencies", pkgDeps));
    761         }
    762         pError0(writeFileG(&pkgInfo, PACKAGE));
    763         freeG(&pkgInfo);
    764       }
    765       listFreeS(installedPackages);
    766     }
    767     XSUCCESS
    768   }
    769 
    770   // TODO check that package name is valid: not -g, publish, install
    771   // publish package to registry
    772   if (params.publish) {
    773     publish(cfgD, pigz);
    774   }
    775 
    776   if (params.install) {
    777     if (argc < 3) {
    778       // install dependencies when package.yml is in the current directory
    779       if (!fileExists(PACKAGE)) {
    780         // command line is spm install and there is no package.yml
    781         puts(BLD RED "Package name missing!" RST);
    782         XFAILURE
    783       }
    784       smallDictt *d = loadYML(PACKAGE);
    785       // install dependencies locally
    786       installDependencies(LOCAL, cfgD, d, CURRENT_YML);
    787       terminateG(d);
    788       XSUCCESS
    789     }
    790 
    791     installPackages(params.global, cfgD, options, pigz);
    792   }
    793 
    794   if (params.uninstall) {
    795     if (argc < 3) {
    796       // install dependencies when package.yml is in the current directory
    797       if (!fileExists(PACKAGE)) {
    798         // command line is spm install and there is no package.yml
    799         puts(BLD RED "Package name missing!" RST);
    800         XFAILURE
    801       }
    802       smallDictt *d = loadYML(PACKAGE);
    803       // install dependencies locally
    804       uninstallDependencies(cfgD, d);
    805       terminateG(d);
    806       XSUCCESS
    807     }
    808 
    809     uninstallPackages(params.global, cfgD, options);
    810   }
    811 
    812   if (params.test) {
    813     if (!fileExists(PACKAGE)) {
    814       // command line is spm test and there is no package.yml
    815       puts(BLD RED "Package name missing!" RST);
    816       XFAILURE
    817     }
    818     smallDictt *d = loadYML(PACKAGE);
    819     int exitCode = testPackage(cfgD, d);;
    820     terminateG(d);
    821     exit(exitCode);
    822   }
    823 
    824   if (params.memcheck) {
    825     if (!fileExists(PACKAGE)) {
    826       // command line is spm memcheck and there is no package.yml
    827       puts(BLD RED "Package name missing!" RST);
    828       XFAILURE
    829     }
    830     smallDictt *d = loadYML(PACKAGE);
    831     memcheckPackage(cfgD, d);
    832     terminateG(d);
    833   }
    834 
    835   if (params.showmcheck) {
    836     smallDictt *packageyml = NULL;
    837     if (fileExists(PACKAGE)) {
    838       packageyml = loadYML(PACKAGE);
    839     }
    840     const char *mcheckCmd  = NULL;
    841     if (packageyml and hasG(packageyml, "memcheckCmd")) {
    842       mcheckCmd = getG(packageyml, rtChar, "memcheckCmd");
    843     }
    844     else if (hasG(cfgD, "memcheckCmd")) {
    845       mcheckCmd = getG(cfgD, rtChar, "memcheckCmd");
    846     }
    847     if (mcheckCmd) {
    848       printf("Current memcheck: " BLD GRN "%s" RST "\n", mcheckCmd);
    849     }
    850     else {
    851       puts(BLD RED "No memcheck command (memcheckCmd) found, searched " HOME_DIR CONFIG_NAME " and package.yml" RST);
    852     }
    853     terminateG(packageyml);
    854   }
    855 
    856   if (params.asan) {
    857     if (!fileExists(PACKAGE)) {
    858       // command line is spm memcheck and there is no package.yml
    859       puts(BLD RED "Package name missing!" RST);
    860       XFAILURE
    861     }
    862     smallDictt *d = loadYML(PACKAGE);
    863     asanPackage(cfgD, d);
    864     terminateG(d);
    865   }
    866 
    867   if (params.showasan) {
    868     smallDictt *packageyml = NULL;
    869     if (fileExists(PACKAGE)) {
    870       packageyml = loadYML(PACKAGE);
    871     }
    872     const char *asanCmd  = NULL;
    873     if (packageyml and hasG(packageyml, "asanCmd")) {
    874       asanCmd = getG(packageyml, rtChar, "asanCmd");
    875     }
    876     else if (hasG(cfgD, "asanCmd")) {
    877       asanCmd = getG(cfgD, rtChar, "asanCmd");
    878     }
    879     if (asanCmd) {
    880       printf("Current asan command: " BLD GRN "%s" RST "\n", asanCmd);
    881     }
    882     else {
    883       puts(BLD RED "No asan command (asanCmd) found, searched " HOME_DIR CONFIG_NAME " and package.yml" RST);
    884     }
    885     terminateG(packageyml);
    886   }
    887 
    888   if (params.doc) {
    889     smallDictt *d = NULL;
    890     if (fileExists(PACKAGE)) {
    891       d = loadYML(PACKAGE);
    892     }
    893     if (d and hasG(d, "documentationCmd")) {
    894       if (getG(cfgD, bum, "print_compile_commands")) {
    895         printf("%s\n", getG(d, rtChar, "documentationCmd"));
    896       }
    897       pErrorNot0(system(getG(d, rtChar, "documentationCmd")));
    898     }
    899     else {
    900       puts(BLD RED "No documentation command (documentationCmd) found in package.yml" RST);
    901     }
    902     terminateG(d);
    903   }
    904 
    905 
    906   terminateManyG(options, cfgJ);
    907   finishG(cfgD);
    908   free(homedir);
    909   finalizeLibsheepy();
    910   XSUCCESS
    911 }
    912 
    913 void publish(smallDictt *cfgD, bool pigz) {
    914   char *cmd = NULL;
    915   bool bum;
    916 
    917   // get registry user key
    918   char *key = getG(cfgD, rtChar, "key");
    919   if (!key) {
    920     puts(BLD RED "user key missing in " HOME_DIR CONFIG_NAME "!" RST);
    921     // TODO free stuff?
    922     XFAILURE
    923   }
    924 
    925   // load package information
    926   if (!fileExists(PACKAGE)) {
    927     puts(BLD RED "'package.yml' missing!" RST);
    928     // TODO free stuff?
    929     XFAILURE
    930   }
    931 
    932   smallDictt *d = loadYML(PACKAGE);
    933   // TODO check yml validity
    934 
    935   // get package information
    936   char *name = getG(d, rtChar, "name");
    937   if (!name) {
    938     puts(BLD RED "Package name missing in package.yml!" RST);
    939     // TODO free stuff?
    940     XFAILURE
    941   }
    942   if (eqG(name, "sheepy") || eqG(name, "spm")) {
    943     puts(BLD RED "Package name not allowed! (sheepy and spm)" RST);
    944     // TODO free stuff?
    945     XFAILURE
    946   }
    947 
    948   char *version = getG(d, rtChar, "version");
    949   if (!version) {
    950     puts(BLD RED "Version missing in package.yml!" RST);
    951     // TODO free stuff?
    952     XFAILURE
    953   }
    954 
    955   // upload package
    956   if (pigz) {
    957     cmd = replaceManyS(UPLOAD_PKG_ZIP, "$NAME$", name, "$VERSION$", version, "$KEY$", key);
    958   }
    959   else {
    960     cmd = replaceManyS(UPLOAD_PKG_ZIP, "pigz -9", "gzip -9", "$NAME$", name, "$VERSION$", version, "$KEY$", key);
    961   }
    962   if (getG(cfgD, bum, "print_compile_commands")) {
    963     printf("%s\n", cmd);
    964   }
    965   pErrorNot0(systemNFreeG(cmd));
    966 
    967   terminateG(d);
    968 }
    969 
    970 void installPackages(bool isGlobal, smallDictt *cfgD, smallArrayt *options, bool pigz) {
    971 
    972   // choose install destination
    973   char *packageDir;
    974   if (!isGlobal) {
    975     // install package in current folder
    976     packageDir = strdup(localPackageDir);
    977   }
    978   else {
    979     // install package in system
    980     packageDir = catS(getG(cfgD, rtChar, "system") , "/", PACKAGE_DIR);
    981   }
    982 
    983   pErrorNULL(iExpandHome(&packageDir));
    984 
    985   // create package folder
    986   if (!fileExists(packageDir)) {
    987     pError0(mkdirParents(packageDir));
    988   }
    989 
    990   char *cwd = getCwd();
    991   pError0(chDir(packageDir));
    992 
    993   //logVarG(options);
    994 
    995   // initialize child process list
    996   staticArrayInit(jobs);
    997 
    998   char *cwdPackage = appendS(cwd, "/" PACKAGE);
    999   if (!lenG(options) && isGlobal && fileExists(cwdPackage)) {
   1000     printf("installing current package %s\n", cwd);
   1001     char *pkgName = strdup(basename(cwd));
   1002     if (isDir(pkgName)) {
   1003       pError0(rmAll(pkgName));
   1004     }
   1005     pError0(copy(cwd, packageDir));
   1006     compilePackage(pkgName, isGlobal, cfgD, "version");
   1007     free(pkgName);
   1008   }
   1009   freeManyS(packageDir, cwdPackage);
   1010 
   1011   iter(options, O) {
   1012     castS(o, O);
   1013     installArgst args;
   1014     args.isGlobal = isGlobal;
   1015     args.cfgD     = cfgD;
   1016     args.o        = o;
   1017     args.pigz     = pigz;
   1018 
   1019     // parallel has to be defined and 0 to disable parallel spm
   1020     if (hasG(cfgD, "parallelSpm") && !getG(cfgD, rtI32, "parallelSpm")) {
   1021       childInstall(&args);
   1022     }
   1023     else {
   1024       spawnProc(childInstall, &args);
   1025     }
   1026   }
   1027 
   1028   pError0(chDir(cwd));
   1029   free(cwd);
   1030 
   1031   // wait for the jobs to finish
   1032   waitForJobs();
   1033 }
   1034 
   1035 void childInstall(void *Args) {
   1036 
   1037   cast(installArgst *, args, Args);
   1038   installAPackage(args->isGlobal, args->cfgD, args->o, args->pigz);
   1039   if (!hasG(args->cfgD, "parallelSpm") || getG(args->cfgD, rtI32, "parallelSpm")) {
   1040     XSUCCESS
   1041 }
   1042   }
   1043 
   1044 void installAPackage(bool isGlobal, smallDictt *cfgD, smallStringt *o, bool pigz) {
   1045   char *cmd = NULL;
   1046 
   1047   char *cwd = getCwd();
   1048   if (!isWritable(cwd)) {
   1049     logC(BLD RED"%s is not writable. Stop."RST, cwd);
   1050     free(cwd);
   1051     XFAILURE
   1052   }
   1053   free(cwd);
   1054 
   1055   // package name and version
   1056   char *name;
   1057   char *version = NULL;
   1058 
   1059   smallArrayt *spl = splitG(o, "@");;
   1060   if (lenG(spl) == 1) {
   1061     name = ssGet(o);
   1062     cmd  = replaceS_max(DOWNLOAD_PKG, "$$", name);
   1063   }
   1064   else {
   1065     name    = getG(spl, rtChar, 0);
   1066     version = getG(spl, rtChar, 1);
   1067     cmd     = replaceManyS(DOWNLOAD_PKG_VERSION, "$NAME$", name, "$VERSION$", version);
   1068   }
   1069 
   1070   // current version is checked against package version in compilePackage to eventually update the dependencies
   1071   char *currentVersion = NULL;
   1072   char *path           = catS(name, "/", PACKAGE);
   1073   if (fileExists(path)) {
   1074     smallDictt *d   = loadYML(path);
   1075     currentVersion  = getNDupG(d, rtChar, "version");
   1076     terminateG(d);
   1077   }
   1078 
   1079   free(path);
   1080 
   1081   // download package
   1082   // TODO save as pkg@version
   1083   if (getG(cfgD, rtBool, "print_compile_commands")) {
   1084     printf("%s\n", cmd);
   1085   }
   1086   pErrorNot0(systemNFree(cmd));
   1087 
   1088   // detect download errors
   1089   char *s = catS(name, ".tar.gz");;
   1090   if (!fileExists(s)) {
   1091     printf(BLD RED "Download error: %s not found" RST "\n", name);
   1092     free(s);
   1093     XFAILURE
   1094   }
   1095   char errorBuf[17];
   1096   pErrorNULL(bLReadFileToS(s, errorBuf, sizeof(errorBuf)));
   1097   if (startsWithG(errorBuf, "No version found") or startsWithG(errorBuf, "Internal Error.") or startsWithG(errorBuf, "package does not")) {
   1098     printf(BLD RED "Download error: %s not found" RST "\n", name);
   1099     free(s);
   1100     XFAILURE
   1101   }
   1102   free(s);
   1103 
   1104   // extract package in package folder
   1105   if (pigz) {
   1106     cmd = replaceS_max(EXTRACT_PKG, "$$", name);
   1107   }
   1108   else {
   1109     cmd = replaceManyS(EXTRACT_PKG,"unpigz ", "gunzip ", "$$", name);
   1110   }
   1111   if (getG(cfgD, rtBool, "print_compile_commands")) {
   1112     printf("%s\n", cmd);
   1113   }
   1114   pErrorNot0(systemNFreeG(cmd));
   1115 
   1116   // compile package
   1117   compilePackage(name, isGlobal, cfgD, currentVersion);
   1118   terminateG(spl);
   1119   free(currentVersion);
   1120 }
   1121 
   1122 void compilePackage(char *name, bool isGlobal, smallDictt *cfgD, char *currentVersion) {
   1123   char *cmd = NULL;
   1124   char *version = NULL;
   1125 
   1126   cmd = catS(name, "/", PACKAGE);
   1127   smallDictt *d = loadYML(cmd);
   1128   free(cmd);
   1129 
   1130   version         = getG(d, rtChar, "version");
   1131   char *bin       = getG(d, rtChar, "bin");
   1132   bool isCompiled = false;
   1133 
   1134   if (eqG(currentVersion, version)) {
   1135       if (bin) {
   1136         // check if the package is already compiled
   1137         char *tbin      = dupG(bin);
   1138         // remove extension
   1139         if (getS(tbin, -1) == 'c') {
   1140           // c extension
   1141           pError0(setG(tbin, -2, 0));
   1142         }
   1143         else if (getS(tbin, -2) == 'c' && getS(tbin, -1) == 'p') {
   1144           // cp extension
   1145           pError0(setG(tbin, -3, 0));
   1146         }
   1147         // is execautable or library: detect h file
   1148         char *h         = catS(name, "/", tbin, ".h");
   1149         bool isLib      = fileExists(h);
   1150         free(h);
   1151         if (isLib) {
   1152           // check .a and .so
   1153           char *libname = catS(name, "/", tbin, ".a");
   1154           bool a        = fileExists(libname);
   1155           free(libname);
   1156           libname       = catS(name, "/", tbin, ".so");
   1157           bool so       = fileExists(libname);
   1158           free(libname);
   1159           isCompiled    = a && so;
   1160         }
   1161         else {
   1162           // check executable
   1163           char *exe  = catS(name, "/", tbin);
   1164           isCompiled = fileExists(exe);
   1165           free(exe);
   1166         }
   1167         free(tbin);
   1168         if (isCompiled) {
   1169           goto checkDependencies;
   1170       }
   1171         }
   1172       else {
   1173         checkDependencies:
   1174         printf("%s@%s is already installed. Checking dependencies...", name, version);
   1175         printf("\n");
   1176   }
   1177       }
   1178 
   1179     // install dependencies
   1180   // the goal with hasDependencies is to detect when dependencies are updated/compiled and recompile the package
   1181   // even when isCompiled is true
   1182   // TODO convert sheepy and spm to library and return the status (installed, updated, failed) to decide if this
   1183   // package needs to be recompiled
   1184   bool hasDependencies = false;
   1185   if (bin) {
   1186     // local, even when the package is installed globally
   1187     hasDependencies = installDependencies(LOCAL, cfgD, d, name);
   1188   }
   1189   else {
   1190     // metapackage
   1191     // install dependencies locally or globally
   1192     if (!isGlobal || !params.update || !params.generatedPackageList) {
   1193       // dont update dependencies in global metapackage
   1194       hasDependencies = installDependencies(isGlobal, cfgD, d, name);
   1195   }
   1196     }
   1197 
   1198   if (isCompiled && hasDependencies) {
   1199     cmd = replaceManyS(DELETE_PKG, "N", name, "B", bin);
   1200     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1201       printf("%s\n", cmd);
   1202     }
   1203     if (systemNFreeG(cmd)) {
   1204       printf(BLD RED "Error deleting: %s" RST "\n", name);
   1205   }
   1206     }
   1207 
   1208 
   1209   if ((bin && !isCompiled) || (bin && isCompiled && hasDependencies)) {
   1210     // this package in a bin line, it is not a metapackage
   1211 
   1212     // build with sheepy
   1213     // compile lib when there is 'bin'.h in the package
   1214     // the executable is stored in the package folder
   1215     cmd = catS(name, "/", bin);
   1216     pErrorNULL(setS(cmd, -1, 'h'));
   1217     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1218       printf("%s\n", cmd);
   1219     }
   1220 
   1221     char *build;
   1222     if (fileExists(cmd)) {
   1223       build = BUILD_PKG;
   1224     }
   1225     else {
   1226       build = BUILD_EXEONLY;
   1227     }
   1228 
   1229     free(cmd);
   1230     cmd = replaceManyS(build, "N", name, "B", bin);
   1231     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1232       printf("%s\n", cmd);
   1233     }
   1234     if (systemNFreeG(cmd)) {
   1235       // compile error, show compile help text
   1236       printf(BLD RED "Error compiling: %s" RST "\n", name);
   1237       if (getG(d, rtChar, "compileHelp")) {
   1238         printf(BLD WHT "%s" RST "\n", getG(d, rtChar, "compileHelp"));
   1239     }
   1240       }
   1241 
   1242     if (isGlobal) {
   1243       // link command to bin from package folder
   1244       char *pkgBin = strdup(basename(bin));
   1245       char **rootBin_l = split(pkgBin, ".");
   1246       char *rootBin    = rootBin_l[0];
   1247       // path to bin/cmd to check if it already exists
   1248       char *bin        = catS("../bin/", rootBin);
   1249       cmd = replaceManyS(INSTALL_COMMAND, "$PACKAGE_DIR$", PACKAGE_DIR, "$name$", name, "$bin$", rootBin);
   1250       free(pkgBin);
   1251       listFreeS(rootBin_l);
   1252 
   1253       if (fileExists(bin)) {
   1254         if (!isLink(bin)) {
   1255           printf(BLD RED "%s is not a link, spm can't install this package completely, run: %s\n" RST, bin, cmd);
   1256         }
   1257         free(cmd);
   1258         goto skipLink;
   1259       }
   1260 
   1261       pErrorNULL(iUniqSlash(cmd));
   1262 
   1263       if (getG(cfgD, rtBool, "print_compile_commands")) {
   1264         printf("%s\n", cmd);
   1265       }
   1266       pErrorNot0(systemNFreeG(cmd));
   1267       skipLink:
   1268       free(bin);
   1269   }
   1270     }
   1271 
   1272   terminateG(d);
   1273 }
   1274 
   1275 smallDictt *loadYML(char *path) {
   1276 
   1277   createAllocateSmallJson(pkgInfo);
   1278   readFileG(pkgInfo, path);
   1279   //logVarG(pkgInfo);
   1280 
   1281   smallDictt *d = getTopG(pkgInfo, rtSmallDictt);
   1282   finishG(pkgInfo);
   1283   return(d);
   1284 }
   1285 
   1286 bool installDependencies(bool isGlobal, smallDictt *cfgD, smallDictt *d, const char *name) {
   1287   char **cmd = NULL;
   1288   bool r = false;;
   1289 
   1290   smallDictt *deps = getG(d, rtSmallDictt, "dependencies");
   1291   if (deps) {
   1292     r = true;
   1293     // initialize child process list
   1294     staticArrayInit(jobsDeps);
   1295 
   1296     char **depsL = keysG(deps);
   1297 
   1298     char *cwd = getCwd();
   1299     char *global;
   1300     if (isGlobal) {
   1301       global = "-g";
   1302     }
   1303     else {
   1304       // install dependencies inside current package
   1305       // when name is NULL, the spm is run in current package, there is no need to change dir
   1306       if (name) {
   1307         pError0(chDir(name));
   1308       }
   1309       // create PACKAGE_DIR shpPackages to install locally
   1310       pError0(mkdirParents(PACKAGE_DIR));
   1311       global = "";
   1312     }
   1313 
   1314     forEachCharP(depsL, dep) {
   1315       cmd = listCreateS(argv[0], "install", global, *dep);
   1316       pErrorNULL(iListCompactS(&cmd));
   1317       if (getG(cfgD, rtBool, "print_compile_commands")) {
   1318         logNFree(join(cmd, " "));
   1319       }
   1320 
   1321       // parallel has to be defined and 0 to disable parallel spm
   1322       if (hasG(cfgD, "parallelSpm") && !getG(cfgD, rtI32, "parallelSpm")) {
   1323         pErrorNot0(systemNFreeG(join(cmd, " ")));
   1324       }
   1325       else {
   1326         spawnProcDeps(childProc, cmd);
   1327       }
   1328       listFreeS(cmd);
   1329     }
   1330 
   1331     pError0(chDir(cwd));
   1332     free(cwd);
   1333     listFreeS(depsL);
   1334     finishG(deps);
   1335 
   1336     // wait for the dependencies to install
   1337     waitForJobsDeps();
   1338   }
   1339   return(r);
   1340 }
   1341 
   1342 int testPackage(smallDictt *cfgD, smallDictt *packageyml) {
   1343   int exitCode = 1;;
   1344 
   1345   if (hasG(packageyml, "testBin")) {
   1346     char *bin = getNDupG(packageyml, rtChar, "testBin");
   1347 
   1348     // dont need to delete intermediary files because the test is compiled in a seperate folder
   1349     // (delete intermediary files)
   1350 
   1351     // compile test
   1352     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1353       printf("sheepy -t %s\n", bin);
   1354     }
   1355     if (commandf("sheepy -t %s", bin)) {
   1356       XFAILURE
   1357     }
   1358 
   1359     // remove extension from testBin
   1360     if ((getS(bin, -2) == '.') and (getS(bin, -1) == 'c')) {
   1361       pErrorNULL(setS(bin, -2, 0));
   1362     }
   1363 
   1364     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1365       printf("%s\n", bin);
   1366     }
   1367     exitCode = command(bin);;
   1368 
   1369     if (getG(cfgD, rtBool, "clean_exe")) {
   1370       if (getG(cfgD, rtBool, "print_compile_commands")) {
   1371         printf("rm %s", bin);
   1372         printf("\n");
   1373       }
   1374       pError0(rmAll(bin));
   1375   }
   1376     }
   1377 
   1378   return(exitCode);
   1379 }
   1380 
   1381 void memcheckPackage(smallDictt *cfgD, smallDictt *packageyml) {
   1382 
   1383   if (!hasG(packageyml, "memcheckBin")) {
   1384     putsG(BLD RED "memcheckBin not set in " PACKAGE RST);
   1385   }
   1386   else {
   1387     const char *mcheckCmd;
   1388     if (hasG(packageyml, "memcheckCmd")) {
   1389       mcheckCmd = getG(packageyml, rtChar, "memcheckCmd");
   1390     }
   1391     else if (hasG(cfgD, "memcheckCmd")) {
   1392       mcheckCmd = getG(cfgD, rtChar, "memcheckCmd");
   1393     }
   1394     else {
   1395       // memcheck command not found
   1396       putsG(BLD RED "memcheck command not found. Searched " PACKAGE " and " HOME_DIR CONFIG_NAME RST);
   1397       return;
   1398     }
   1399 
   1400     char *bin = getNDupG(packageyml, rtChar, "memcheckBin");
   1401 
   1402     if (!bin) {
   1403       putsG(BLD RED "memcheckBin is not set in " PACKAGE RST);
   1404       return;
   1405     }
   1406 
   1407     if (!fileExists(bin)) {
   1408       printf(BLD RED "memcheckBin %s not found" RST, bin);
   1409       return;
   1410     }
   1411 
   1412     // dont need to delete intermediary files because the test is compiled in a seperate folder
   1413     // (delete intermediary files)
   1414 
   1415     // compile test
   1416     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1417       printf("sheepy -m %s\n", bin);
   1418     }
   1419     if (commandf("sheepy -m %s", bin)) {
   1420       XFAILURE
   1421     }
   1422 
   1423     // remove extension from memcheckBin
   1424     if ((getS(bin, -2) == '.') and (getS(bin, -1) == 'c')) {
   1425       pErrorNULL(setS(bin, -2, 0));
   1426     }
   1427 
   1428     // TODO due to a weird bug in debian stretch 180324, I run memcheckBin before running it again with valgrind
   1429     // TODO it should not be necessary: "%s %s", mcheckCmd, bin
   1430     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1431       printf("%s ; %s %s\n", bin, mcheckCmd, bin);
   1432     }
   1433     int r;
   1434     spmAssign(r, commandf("%s ; %s %s\n", bin, mcheckCmd, bin));
   1435 
   1436 
   1437     if (getG(cfgD, rtBool, "clean_exe")) {
   1438       if (getG(cfgD, rtBool, "print_compile_commands")) {
   1439         printf("rm %s", bin);
   1440         printf("\n");
   1441       }
   1442       pError0(rmAll(bin));
   1443     }
   1444 
   1445     exit(r);
   1446 }
   1447   }
   1448 
   1449 
   1450 void asanPackage(smallDictt *cfgD, smallDictt *packageyml) {
   1451 
   1452   if (!hasG(packageyml, "asanBin")) {
   1453     putsG(BLD RED "asanBin not set in " PACKAGE RST);
   1454   }
   1455   else {
   1456     const char *asanCmd;
   1457     if (hasG(packageyml, "asanCmd")) {
   1458       asanCmd = getG(packageyml, rtChar, "asanCmd");
   1459     }
   1460     else if (hasG(cfgD, "asanCmd")) {
   1461       asanCmd = getG(cfgD, rtChar, "asanCmd");
   1462     }
   1463     else {
   1464       // asan command not found
   1465       putsG(BLD RED "asan command not found. Searched " PACKAGE " and " HOME_DIR CONFIG_NAME RST);
   1466       return;
   1467     }
   1468 
   1469     char *bin = getNDupG(packageyml, rtChar, "asanBin");
   1470 
   1471     if (!bin) {
   1472       putsG(BLD RED "asanBin is not set in " PACKAGE RST);
   1473       return;
   1474     }
   1475 
   1476     if (!fileExists(bin)) {
   1477       printf(BLD RED "asanBin %s not found" RST, bin);
   1478       return;
   1479     }
   1480 
   1481     // dont need to delete intermediary files because the test is compiled in a seperate folder
   1482     // (delete intermediary files)
   1483 
   1484     // compile test
   1485     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1486       printf("sheepy -a %s\n", bin);
   1487     }
   1488     if (commandf("sheepy -a %s", bin)) {
   1489       XFAILURE
   1490     }
   1491 
   1492     // remove extension from asanBin
   1493     if ((getS(bin, -2) == '.') and (getS(bin, -1) == 'c')) {
   1494       pErrorNULL(setS(bin, -2, 0));
   1495     }
   1496 
   1497     if (getG(cfgD, rtBool, "print_compile_commands")) {
   1498       printf("%s %s\n", asanCmd, bin);
   1499     }
   1500     int r;
   1501     spmAssign(r, commandf("%s %s\n", asanCmd, bin));
   1502 
   1503 
   1504     if (getG(cfgD, rtBool, "clean_exe")) {
   1505       if (getG(cfgD, rtBool, "print_compile_commands")) {
   1506         printf("rm %s", bin);
   1507         printf("\n");
   1508       }
   1509       pError0(rmAll(bin));
   1510     }
   1511 
   1512     exit(r);
   1513 }
   1514   }
   1515 
   1516 
   1517 void listAllPackages(smallDictt *cfgD, smallArrayt *options) {
   1518 
   1519   if (isDirG(localPackageDir)) {
   1520     appendNSmashG(options, readDirDirG(rtSmallArrayt, localPackageDir));
   1521   }
   1522   smallStringt *path = createS(getG(cfgD, rtChar, "system") , "/", PACKAGE_DIR);
   1523   normalizePathG(path);
   1524   pErrorNULL(expandHomeG(path));
   1525   if (isDirG(path)) {
   1526     appendNSmashG(options, readDirDirG(rtSmallArrayt, ssGet(path)));
   1527   }
   1528   terminateG(path);
   1529   pErrorNULL(uniqG(options, unusedV));
   1530 }
   1531 
   1532 void showPackage(smallDictt *cfgD, smallStringt *o) {
   1533   smallStringt *path = NULL;
   1534 
   1535   // local package
   1536   path = createS(localPackageDir, "/", ssGet(o), "/", PACKAGE);
   1537 
   1538   if (fileExistsG(path)) {
   1539     showPackageInfo(path);
   1540   }
   1541 
   1542   terminateG(path);
   1543 
   1544   // global package
   1545   path = createS(getG(cfgD, rtChar, "system") , "/", PACKAGE_DIR, "/", ssGet(o), "/", PACKAGE);
   1546 
   1547   normalizePathG(path);
   1548   pErrorNULL(expandHomeG(path));
   1549   if (fileExistsG(path)) {
   1550     puts(BLD WHT "GLOBAL" RST);
   1551     showPackageInfo(path);
   1552   }
   1553 
   1554   put
   1555   terminateG(path);
   1556 }
   1557 
   1558 void showPackageInfo(smallStringt *path) {
   1559 
   1560   smallDictt *d = loadYML(ssGet(path));
   1561 
   1562   smallStringt *s = getNDupG(d, rtSmallStringt, "name");
   1563   colorG(s, BLD YLW);
   1564   catSG(s, ": ");
   1565 
   1566   smallStringt *s2 = getG(d, rtSmallStringt, "version");
   1567   catG(s, s2);
   1568   putsG(s);
   1569 
   1570   terminateO(s);
   1571   finishG(s2);
   1572 
   1573   colorG(path, BLD GRN);
   1574   putsG(path);
   1575 
   1576   s = getG(d, rtSmallStringt, "bin");
   1577   if (s) {
   1578     printf(BLD GRN "bin:" RST " %s\n", ssGet(s));
   1579     finishG(s);
   1580   }
   1581 
   1582   s = getG(d, rtSmallStringt, "description");
   1583   putsG(s);
   1584   finishG(s);
   1585 }
   1586 
   1587 void updatePackages(bool isGlobal, smallDictt *cfgD, smallArrayt *options, bool pigz) {
   1588   smallStringt *path = NULL;
   1589 
   1590   createAllocateSmallArray(packagesToUpdate);
   1591 
   1592   // local package
   1593   if (!isGlobal) {
   1594     iter(options, O) {
   1595       castS(o, O);
   1596       path = createS(localPackageDir, "/", ssGet(o), "/", PACKAGE);
   1597 
   1598       // update all packages even those that are not yet installed
   1599       pushG(packagesToUpdate, o);
   1600 
   1601       terminateG(path);
   1602     }
   1603   }
   1604   else {
   1605     // global package
   1606     iter(options, O) {
   1607       castS(o, O);
   1608       path = createS(getG(cfgD, rtChar, "system") , "/", PACKAGE_DIR, "/", ssGet(o), "/", PACKAGE);
   1609 
   1610       normalizePathG(path);
   1611       pErrorNULL(expandHomeG(path));
   1612       // update all packages even those that are not yet installed
   1613       pushG(packagesToUpdate, o);
   1614 
   1615       terminateG(path);
   1616     }
   1617   }
   1618 
   1619   installPackages(isGlobal, cfgD, packagesToUpdate, pigz);
   1620   smashG(packagesToUpdate);
   1621 }
   1622 
   1623 
   1624 void uninstallPackages(bool isGlobal, smallDictt *cfgD, smallArrayt *options) {
   1625   char *cwd = NULL;
   1626 
   1627   // choose install destination
   1628   char *packageDir;
   1629   if (!isGlobal) {
   1630     // install package in current folder
   1631     packageDir = strdup(localPackageDir);
   1632   }
   1633   else {
   1634     // install package in system
   1635     packageDir = catS(getG(cfgD, rtChar, "system") , "/", PACKAGE_DIR);
   1636   }
   1637 
   1638   pErrorNULL(iExpandHome(&packageDir));
   1639 
   1640   // skip when package folder is not found
   1641   if (!fileExists(packageDir)) {
   1642     return;
   1643   }
   1644 
   1645   cwd = getCwd();
   1646   pError0(chDir(packageDir));
   1647 
   1648   //logVarG(options);
   1649 
   1650   iter(options, O) {
   1651     castS(o, O);
   1652 
   1653     // package name
   1654     char *name;
   1655 
   1656     smallArrayt *spl = splitG(o, "@");;
   1657     if (lenG(spl) == 1) {
   1658       name = ssGet(o);
   1659     }
   1660     else {
   1661       name    = getG(spl, rtChar, 0);
   1662     }
   1663 
   1664     if (!isDir(name)) {
   1665       goto end;
   1666     }
   1667 
   1668     char *path    = catS(name, "/", PACKAGE);
   1669     smallDictt *d = loadYML(path);
   1670     char *bin     = getG(d, rtChar, "bin");
   1671 
   1672     // delete build files
   1673     if (bin) {
   1674       char *cmd = catS("sheepy -d ", bin);
   1675       if (getG(cfgD, rtBool, "print_compile_commands")) {
   1676         putsG(cmd);
   1677       }
   1678       pError0(chDir(name));
   1679       pErrorNot0(systemNFreeG(cmd));
   1680       pError0(chDir(".."));
   1681 
   1682       // uninstall dependencies
   1683     }
   1684     // metapackage
   1685     // uninstall dependencies globally
   1686     if (!bin && isGlobal) {
   1687       // install dependencies in global metapackage
   1688       uninstallDependencies(cfgD, d);
   1689     }
   1690 
   1691     if (bin && isGlobal) {
   1692       // unlink command in bin from package folder
   1693       char *pkgBin = strdup(basename(bin));
   1694       char **rootBin_l = split(pkgBin, ".");
   1695       char *rootBin    = rootBin_l[0];
   1696       char *path       = catS("../bin/", rootBin);;
   1697       pError0(rmAll(path));
   1698       free(path);
   1699       free(pkgBin);
   1700       listFreeS(rootBin_l);
   1701     }
   1702 
   1703     pError0(rmAll(name));
   1704 
   1705     terminateG(d);
   1706     free(path);
   1707 
   1708     end:
   1709     terminateG(spl);
   1710   }
   1711   pError0(chDir(cwd));
   1712   free(cwd);
   1713   smallArrayt *arr = readDirDirO(packageDir);;
   1714   if (!lenG(arr) && !isGlobal) {
   1715     // remove empty shpPackages
   1716     pError0(rmAll(packageDir));
   1717   }
   1718   terminateG(arr);
   1719   free(packageDir);
   1720 }
   1721 
   1722 void uninstallDependencies(smallDictt *cfgD, smallDictt *d) {
   1723   char **cmd = NULL;
   1724 
   1725   smallDictt *deps = getG(d, rtSmallDictt, "dependencies");
   1726   if (deps) {
   1727 
   1728     char **depsL = keysG(deps);
   1729 
   1730     createAllocateSmallArray(depA);
   1731     fromArrayNFreeG(depA, depsL, 0);
   1732 
   1733     uninstallPackages(params.global, cfgD, depA);
   1734     terminateG(depA);
   1735     finishG(deps);
   1736 }
   1737   }
   1738 
   1739 
   1740 
   1741 
   1742 /**
   1743  * spawn a child process and add to the list
   1744  */
   1745 void spawnProcDeps(cbFt cb, void *arg) {
   1746   pid_t pid;
   1747   int status;
   1748 
   1749   staticArrayPush(jobsDeps)
   1750   staticArrayLast(jobsDeps).pid = fork();
   1751   switch (staticArrayLast(jobsDeps).pid) {
   1752     case -1:
   1753       perror("spawnProc error: ");
   1754       XFAILURE
   1755     case 0:
   1756       cb(arg);
   1757       perror("spawnProc error: ");
   1758       XFAILURE
   1759     default:
   1760       break;
   1761   }
   1762 
   1763   return;
   1764 }
   1765 
   1766 /**
   1767  * wait for all child processes to finish
   1768  */
   1769 void waitForJobsDeps(void) {
   1770 
   1771   range(i, staticArrayCount(jobsDeps)) {
   1772     if (waitpid(staticArrayGet(jobsDeps,i).pid, &(staticArrayGet(jobsDeps,i).status), 0) == -1) {
   1773       printf("couldn't wait for child %d\n", jobsDeps.list[i].pid); {
   1774       XFAILURE
   1775     }
   1776       }
   1777 
   1778     #ifdef __HAIKU__
   1779     if (WEXITSTATUS(staticArrayGet(jobsDeps,i).status) || WTERMSIG(staticArrayGet(jobsDeps,i).status)) {
   1780       puts("subprocess trouble, check output");
   1781       XFAILURE
   1782     }
   1783     #else
   1784     if (WEXITSTATUS(staticArrayGet(jobsDeps,i).status) || WTERMSIG(staticArrayGet(jobsDeps,i).status) || WCOREDUMP(staticArrayGet(jobsDeps,i).status)) {
   1785       puts("subprocess trouble, check output");
   1786       XFAILURE
   1787     }
   1788     #endif
   1789   }
   1790 }
   1791 
   1792 void findPrint(smallDictt *d) {
   1793 
   1794   smallStringt *s = getNDupG(d, rtSmallStringt, "name");
   1795   colorG(s, BLD YLW);
   1796   catSG(s, ": ");
   1797 
   1798   smallStringt *s2 = getG(d, rtSmallStringt, "version");
   1799   catG(s, s2);
   1800   putsG(s);
   1801 
   1802   terminateO(s);
   1803   finishG(s2);
   1804 
   1805   printf(BLD GRN "Author:   " RST "%s\n", getG(d, rtChar, "user"));
   1806   printf(BLD GRN "Homepage: " RST "%s\n", getG(d, rtChar, "homepage"));
   1807   printf(BLD GRN "repo:     " RST "%s\n", getG(d, rtChar, "repo"));
   1808   smallArrayt *a = getG(d, rtSmallArrayt, "keywords");
   1809   s              = joinG(a, ", ");
   1810   if (lenG(a) == 1) {
   1811     printf(BLD GRN "Keyword:  " RST "%s\n", ssGet(s));
   1812   }
   1813   else if (lenG(a) > 1) {
   1814     printf(BLD GRN "Keywords: " RST "%s\n", ssGet(s));
   1815   }
   1816   finishG(s);
   1817   finishG(a);
   1818 
   1819   s = getG(d, rtSmallStringt, "description");
   1820   put
   1821   putsG(s);
   1822   finishG(s);
   1823 }
   1824 
   1825 
   1826 char **requestRegistry(smallDictt *cfgD, smallJsont *json) {
   1827 
   1828   //TODO send the json without using the file system
   1829   char *s       = toStringG(json);
   1830   char *reqFile = expandHomeG("~/.sheepy/api.json");
   1831 
   1832   writeFileG(s, reqFile);
   1833   free(s);
   1834 
   1835   char *cmd = replaceG(API_REQUEST, "$JSONFILE$", reqFile, 1);
   1836   if (getG(cfgD, rtBool, "print_compile_commands")) {
   1837     printf("%s\n", cmd);
   1838   }
   1839   char **result = execOut(cmd);;
   1840   free(cmd);
   1841   pError0(rmAll(reqFile));
   1842   free(reqFile);
   1843 
   1844   return(result);
   1845 }
   1846 
   1847 void showPackageListFromRegistry(char **result, smallJsont *json) {
   1848 
   1849   //logVarG(result);
   1850   if (!result) {
   1851     puts(BLD RED "Bad response from spm registry" RST);
   1852     goto end;
   1853   }
   1854 
   1855   char *r = result[0];
   1856 
   1857   emptyG(json);
   1858   parseG(json, r);
   1859   listFreeS(result);
   1860   //logVarG(json);
   1861   //char *tmp = stringifyG(json, 4)
   1862   //logVarG(tmp);
   1863   //free tmp
   1864 
   1865   if (!eqG(getTopTypeG(json), "array")) {
   1866     puts(BLD RED "Bad response from spm registry" RST);
   1867     goto end;
   1868   }
   1869 
   1870   // show package information
   1871   smallArrayt *a = getTopG(json, rtSmallArrayt);
   1872 
   1873   if (!lenG(a)) {
   1874     puts("Empty results.");
   1875   }
   1876 
   1877   iter(a, D) {
   1878     cast(smallDictt *, d, D);
   1879     if (!isOTypeG(d, "smallDict")) {
   1880       puts(BLD RED "Bad response from spm registry" RST);
   1881       break;
   1882     }
   1883     findPrint(d);
   1884 
   1885     // print -- between packages
   1886     if (iterIndexG(a) != (lenG(a)-1)) {
   1887       put
   1888       puts("-------------");
   1889     }
   1890   }
   1891 
   1892   finishG(a);
   1893 
   1894   end:
   1895   return;
   1896 }
   1897 
   1898 void whoamiResult(char **result, smallJsont *json) {
   1899 
   1900   //logVarG(result);
   1901 
   1902   if (!result) {
   1903     puts(BLD RED "Bad response from spm registry" RST);
   1904     goto end;
   1905   }
   1906 
   1907   char *r = result[0];
   1908   emptyG(json);
   1909   parseG(json, r);
   1910   listFreeS(result);
   1911 
   1912   if (!eqG(getTopTypeG(json), "array")) {
   1913     puts(BLD RED "Bad response from spm registry" RST);
   1914     goto end;
   1915   }
   1916 
   1917   smallArrayt *a = getTopG(json, rtSmallArrayt);
   1918 
   1919   if (!lenG(a)) {
   1920     puts("User not found.");
   1921   }
   1922   else {
   1923     smallDictt *d   = getG(a, rtSmallDictt, 0);;
   1924     printf(BLD GRN "Username:     " RST "%s\n", getG(d, rtChar, "username"));
   1925     printf(BLD GRN "Name:         " RST "%s\n", getG(d, rtChar, "name"));
   1926     printf(BLD GRN "Public email: " RST "%s\n", getG(d, rtChar, "publicemail"));
   1927     finishG(d);
   1928   }
   1929 
   1930   finishG(a);
   1931 
   1932   end:
   1933   return;
   1934 }
   1935 
   1936 void profileResult(char **result, smallJsont *json) {
   1937 
   1938   //logVarG(result);
   1939 
   1940   if (!result) {
   1941     puts(BLD RED "Bad response from spm registry" RST);
   1942     goto end;
   1943   }
   1944 
   1945   char *r = result[0];
   1946   emptyG(json);
   1947   parseG(json, r);
   1948   listFreeS(result);
   1949 
   1950   if (!eqG(getTopTypeG(json), "array")) {
   1951     puts(BLD RED "Bad response from spm registry" RST);
   1952     goto end;
   1953   }
   1954 
   1955   smallArrayt *a = getTopG(json, rtSmallArrayt);
   1956 
   1957   if (!lenG(a)) {
   1958     puts("No user found.");
   1959   }
   1960   else {
   1961   iter(a, D) {
   1962     cast(smallDictt*, d, D);
   1963     printf(BLD GRN "Username:     " RST "%s\n", getG(d, rtChar, "username"));
   1964     printf(BLD GRN "Name:         " RST "%s\n", getG(d, rtChar, "name"));
   1965     printf(BLD GRN "Public email: " RST "%s\n", getG(d, rtChar, "publicemail"));
   1966 
   1967     smallArrayt *pkgs = getG(d, rtSmallArrayt, "packages");
   1968 
   1969     if (!pkgs) {
   1970       goto nullpkgs;
   1971     }
   1972 
   1973     if (!lenG(pkgs)) {
   1974       goto nopkgs;
   1975     }
   1976 
   1977     sortG(pkgs);
   1978     char *s           = joinSG(pkgs, " ");;
   1979 
   1980     if (lenG(pkgs) == 1) {
   1981       printf(BLD GRN "Package:      " RST "%s\n", s);
   1982     }
   1983     else {
   1984       printf(BLD GRN "Packages:     " RST "%s\n", s);
   1985     }
   1986 
   1987     free(s);
   1988 
   1989     nopkgs:
   1990     finishG(pkgs);
   1991 
   1992     nullpkgs:
   1993     // print -- between packages
   1994     if (iterIndexG(a) != (lenG(a)-1)) {
   1995       put
   1996       puts("-------------");
   1997     }
   1998   }
   1999 
   2000   finishG(a);
   2001 
   2002   end:
   2003   return;
   2004 }
   2005   }
   2006 
   2007 void unpublishResult(char **result, smallJsont *json) {
   2008 
   2009   //logVarG(result);
   2010 
   2011   if (!result) {
   2012     puts(BLD RED "Bad response from spm registry" RST);
   2013     goto end;
   2014   }
   2015 
   2016   char *r = result[0];
   2017   emptyG(json);
   2018   parseG(json, r);
   2019   listFreeS(result);
   2020 
   2021   if (!eqG(getTopTypeG(json), "array")) {
   2022     puts(BLD RED "Bad response from spm registry" RST);
   2023     goto end;
   2024   }
   2025 
   2026   smallArrayt *a = getTopG(json, rtSmallArrayt);
   2027 
   2028   if (!lenG(a)) {
   2029     puts("Nothing unpublished.");
   2030   }
   2031   else {
   2032     char *unpub = joinSG(a, ", ");
   2033     printf("Unpublished: %s\n", unpub);
   2034     free(unpub);
   2035   }
   2036 
   2037   finishG(a);
   2038 
   2039   end:
   2040   return;
   2041 }
   2042 
   2043 void apikeyResult(char **result, smallJsont *json) {
   2044 
   2045   //logVarG(result);
   2046 
   2047   if (!result) {
   2048     puts(BLD RED "Bad response from spm registry" RST);
   2049     goto end;
   2050   }
   2051 
   2052   char *r = result[0];
   2053   emptyG(json);
   2054   parseG(json, r);
   2055   listFreeS(result);
   2056 
   2057   if (!eqG(getTopTypeG(json), "array")) {
   2058     puts(BLD RED "Bad response from spm registry" RST);
   2059     goto end;
   2060   }
   2061 
   2062   smallArrayt *a = getTopG(json, rtSmallArrayt);
   2063 
   2064   if (!lenG(a)) {
   2065     puts("Error: API key not found.");
   2066   }
   2067   else {
   2068     printf("API Key: %s\n", getG(a, rtChar, 0));
   2069   }
   2070 
   2071   finishG(a);
   2072   end:
   2073   return;
   2074 }
   2075 
   2076 void addUserResult(char **result, smallJsont *json) {
   2077 
   2078   //logVarG(result);
   2079 
   2080   if (!result) {
   2081     puts(BLD RED "Bad response from spm registry" RST);
   2082     goto end;
   2083   }
   2084 
   2085   char *r = result[0];
   2086   emptyG(json);
   2087   parseG(json, r);
   2088   listFreeS(result);
   2089 
   2090   if (!eqG(getTopTypeG(json), "array")) {
   2091     puts(BLD RED "Bad response from spm registry" RST);
   2092     goto end;
   2093   }
   2094 
   2095   smallArrayt *a = getTopG(json, rtSmallArrayt);
   2096 
   2097   if (!lenG(a)) {
   2098     puts(BLD RED "Bad response from spm registry" RST);
   2099     goto finisha;
   2100   }
   2101 
   2102   if (isBlankG(getG(a, rtChar, 0))) {
   2103     puts(BLD RED "Error: User not created." RST);
   2104     printf(BLD RED "%s" RST, getG(a, rtChar, 1));
   2105   }
   2106   else {
   2107     puts(BLD GRN "New user created successfully." RST);
   2108   }
   2109 
   2110   finisha:
   2111   finishG(a);
   2112 
   2113   end:
   2114   return;
   2115 }
   2116 
   2117 // vim: set expandtab ts=2 sw=2: