1:- module(
    2  ppm,
    3  [
    4    ppm_help/0,
    5    ppm_install/2, % +User, +Repo
    6    ppm_list/0,
    7    ppm_remove/2,  % +User, +Repo
    8    ppm_run/2,     % +User, +Repo
    9    ppm_sync/0,
   10    ppm_update/0,
   11    ppm_update/2,  % +User, +Repo
   12    ppm_updates/0
   13  ]
   14).

Prolog Package Manager (PPM)

A simple package manager for SWI-Prolog.


author
- Wouter Beek
version
- 2017-2018 */
   26:- use_module(library(aggregate)).   27:- use_module(library(apply)).   28:- use_module(library(filesex)).   29:- use_module(library(git)).   30:- use_module(library(http/http_json), []).   31:- use_module(library(prolog_pack), []).   32
   33:- use_module(library(ppm_generic)).   34:- use_module(library(ppm_git)).   35:- use_module(library(ppm_github)).   36
   37:- initialization
   38   init_ppm.
 ppm_current_update(?User:atom, ?Repo:atom, -CurrentVersion:compound, -LatestVersion:compound) is nondet
   47ppm_current_update(User, Repo, CurrentVersion, LatestVersion) :-
   48  repository_directory(User, Repo, Dir),
   49  git_fetch(Dir),
   50  git_current_version(Dir, CurrentVersion),
   51  git_version_latest(Dir, LatestVersion),
   52  CurrentVersion \== LatestVersion.
 ppm_help is det
   58ppm_help :-
   59  ansi_format([fg(green)], "Welcome "),
   60  ansi_format([fg(red)], "to "),
   61  ansi_format([fg(blue)], "Prolog "),
   62  ansi_format([fg(yellow)], "Package "),
   63  ansi_format([fg(magenta)], "Manager"),
   64  format("!"),
   65  nl,
   66  format("We are so happy that you're here :-)"),
   67  nl,
   68  nl.
 ppm_install(+User:atom, +Repo:atom) is semidet
Installs a package. The latests version is chosen in case none is specified.
   77ppm_install(User, Repo) :-
   78  ppm_install(User, Repo, package).
   79
   80ppm_install(User, Repo, Kind) :-
   81  repository_directory(User, Repo, _), !,
   82  ppm_update(User, Repo, Kind).
   83ppm_install(User, Repo, Kind) :-
   84  user_directory(User, UserDir),
   85  (   github_version_latest(User, Repo, LatestVersion)
   86  ->  github_uri(User, Repo, Uri),
   87      git_clone(UserDir, Uri),
   88      directory_file_path(UserDir, Repo, RepoDir),
   89      git_checkout(RepoDir, version(LatestVersion)),
   90      ppm_dependencies(RepoDir, Dependencies),
   91      maplist(ppm_install_dependency, Dependencies),
   92      phrase(version(LatestVersion), Codes),
   93      ansi_format(
   94        [fg(green)],
   95        "Successfully installed ~a ‘~a’ (~s)\n",
   96        [Kind,Repo,Codes]
   97      ),
   98      ppm_sync
   99  ;   ansi_format(
  100        [fg(red)],
  101        "Could not find a version tag in ~a's ~a ‘~a’.",
  102        [User,Kind,Repo]
  103      ),
  104      nl,
  105      fail
  106  ).
  107
  108ppm_install_dependency(Dependency) :-
  109  _{user: User, repo: Repo} :< Dependency,
  110  ppm_install(User, Repo, dependency).
 ppm_list is det
Display all currently installed PPMs.
  118ppm_list :-
  119  aggregate_all(
  120    set(package(User,Repo,Version,Dependencies)),
  121    (
  122      repository_directory(User, Repo, Dir),
  123      git_current_version(Dir, Version),
  124      ppm_dependencies(Dir, Dependencies)
  125    ),
  126    Packages
  127  ),
  128  (   Packages == []
  129  ->  format("No packages are currently installed.\n")
  130  ;   maplist(ppm_list_row, Packages)
  131  ).
  132
  133ppm_list_row(package(User,Repo,Version,Dependencies)) :-
  134  phrase(version(Version), Codes),
  135  format("~a/~a (~s)\n", [User,Repo,Codes]),
  136  maplist(ppm_list_dep_row, Dependencies).
  137
  138ppm_list_dep_row(Dependency) :-
  139  _{user: User, repo: Repo} :< Dependency,
  140  format("  ⤷ ~a/~a\n", [User,Repo]).
 ppm_remove(+User:atom, +Repo:atom) is det
Removes a package.

TBD: Support for removing otherwise unused dependencies.

  150ppm_remove(User, Repo) :-
  151  repository_directory(User, Repo, Dir),
  152  git_current_version(Dir, Version),
  153  delete_directory_and_contents(Dir),
  154  phrase(version(Version), Codes),
  155  format("Deleted package ‘~a/~a’ (~s).", [User,Repo,Codes]).
 ppm_run(+User:atom, +Repo:atom) is semidet
  161ppm_run(User, Repo) :-
  162  repository_directory(User, Repo, Dir),
  163  (   file_by_name(Dir, 'run.pl', File)
  164  ->  consult(File)
  165  ;   ansi_format([fg(red)], "Package ‘~a/~a’ is currently not installed.\n", [User,Repo])
  166  ).
 ppm_sync is det
Synchronizes the packages the current Prolog session has access to the to packages stored in `~/.ppm'.
  175ppm_sync :-
  176  root_directory(Root),
  177  ppm_sync_(Root).
  178
  179ppm_sync_(Root) :-
  180  assertz(user:file_search_path(ppm, Root)),
  181  current_prolog_flag(arch, Arch),
  182  forall(
  183    repository_directory(User, Repo, _),
  184    (
  185      (   sync_directory(Root, User, Repo, [prolog], Dir)
  186      ->  assertz(user:file_search_path(library, ppm(Dir)))
  187      ;   true
  188      ),
  189      (   sync_directory(Root, User, Repo, [lib,Arch], Dir)
  190      ->  assertz(user:file_search_path(foreign, ppm(Dir)))
  191      ;   true
  192      )
  193    )
  194  ).
  195
  196sync_directory(Root, User, Repo, T, Dir) :-
  197  atomic_list_concat([User,Repo|T], /, Dir),
  198  directory_by_name(Root, Dir).
 ppm_update is semidet
 ppm_update(+User, +Repo:atom) is semidet
Updates an exisiting package and all of its dependencies.
  207ppm_update :-
  208  ppm_updates_(Updates),
  209  forall(
  210    member(update(User,Repo,_,_), Updates),
  211    ppm_update(User, Repo)
  212  ).
  213
  214
  215ppm_update(User, Repo) :-
  216  ppm_update(User, Repo, package).
  217
  218
  219ppm_update(User, Repo, Kind) :-
  220  repository_directory(User, Repo, Dir),
  221  git_fetch(Dir),
  222  git_current_version(Dir, CurrentVersion),
  223  git_version_latest(Dir, LatestVersion),
  224  (   compare_version(<, CurrentVersion, LatestVersion)
  225  ->  git_checkout(Dir, version(LatestVersion)),
  226      % informational
  227      phrase(version(CurrentVersion), Codes1),
  228      phrase(version(LatestVersion), Codes2),
  229      format("Updated ‘~a/~a’: ~s → ~s\n", [User,Repo,Codes1,Codes2])
  230  ;   % informational
  231      (   Kind == package
  232      ->  format("No need to update ~a ‘~a/~a’.\n", [Kind,User,Repo])
  233      ;   true
  234      )
  235  ),
  236  % Update the dependencies after updating the main package.
  237  ppm_dependencies(Dir, Dependencies),
  238  maplist(ppm_install_dependency, Dependencies),
  239  ppm_sync.
  240
  241ppm_update_dependency(Dependency) :-
  242  _{user: User, repo: Repo} :< Dependency,
  243  ppm_update(User, Repo, dependency).
 ppm_updates is det
Shows packages, if any, that can be updated using ppm_update/1.
  251ppm_updates :-
  252  format("Checking for updates…\n\n"),
  253  ppm_updates_(Updates),
  254  pp_available_updates(Updates),
  255  maplist(ppm_updates_row, Updates).
  256
  257ppm_updates_(Updates) :-
  258  aggregate_all(
  259    set(update(User,Repo,CurrentVersion,LatestVersion)),
  260    ppm_current_update(User, Repo, CurrentVersion, LatestVersion),
  261    Updates
  262  ).
  263
  264pp_available_updates([]) :- !,
  265  format("No updates available.\n").
  266pp_available_updates([_]) :- !,
  267  format("1 update available:\n").
  268pp_available_updates(Updates) :-
  269  length(Updates, N),
  270  format("~D updates available:\n", [N]).
  271
  272ppm_updates_row(update(User,Repo,CurrentVersion,LatestVersion)) :-
  273  format("  • ~a/~a\t", [User,Repo]),
  274  compare_version(Order, CurrentVersion, LatestVersion),
  275  order_colors(Order, Color1, Color2),
  276  phrase(version(CurrentVersion), CurrentCodes),
  277  ansi_format([fg(Color1)], "~s", [CurrentCodes]),
  278  format(" → "),
  279  phrase(version(LatestVersion), LatestCodes),
  280  ansi_format([fg(Color2)], "~s\n", [LatestCodes]).
  281
  282order_colors(<, red, green).
  283order_colors(>, green, red).
  284
  285
  286
  287
  288
  289% INITIALIZATION %
  290
  291init_ppm :-
  292  root_directory(Root),
  293  ensure_directory_exists(Root),
  294  ppm_sync_(Root)