1 module pkm.search;
2 
3 import std.process: execute, environment, executeShell, Config, spawnProcess, wait;
4 import std.file: tempDir, remove, readText;
5 // import std.file;
6 import std.stdio;
7 import std.regex;
8 import std.path: buildNormalizedPath, absolutePath;
9 import std.array: split;
10 import std.conv: to;
11 import std.range: repeat;
12 import std.algorithm: canFind, sort;
13 import std.numeric: gapWeightedSimilarityNormalized;
14 
15 import core.sys.posix.sys.ioctl;
16 
17 import pkm.pkg;
18 import sily.bashfmt;
19 
20 // yay regex:
21 // (.*?)\/(.*?)\s(.*?)\s\((.*?)\)(?:\s\[(.*)\])?(?:\s\((Orphaned)\))?(?:\s\(Out-of-date:\s(.*?)\))?(?:\s\((Installed)(?:\:\s(.*?))?\))?(?:\s{6}|\s{5})(.*)(?:\r|\n|\z)
22 // 1    2    3        4                5        6          7           8           9             10   
23 // repo/name version (size|aur-votes) [group]? (orphaned) (outofdate) (installed: (version)) \n (description)
24 private auto reg = regex(
25         r"(.*?)\/(.*?)\s(.*?)\s\((.*?)\)(?:\s\[(.*)\])?" ~ 
26         r"(?:\s\((Orphaned)\))?(?:\s\(Out-of-date:\s(.*?)\))?" ~ 
27         r"(?:\s\((Installed)(?:\:\s(.*?))?\))?(?:\s{6}|\s{5})(.*)(?:\r|\n|\z)", "gm");
28 
29 int search(string yay, string[] terms, bool color) {
30     // string yay = "/usr/bin/yay";
31     string tmpFile = tempDir ~ "/" ~ "pkm-yay-search-output.txt";
32     tmpFile = tmpFile.buildNormalizedPath.absolutePath;
33 
34     auto processOut = File(tmpFile, "w+");
35 
36     auto pidErr = wait(spawnProcess([yay, "-Ss"] ~ terms, std.stdio.stdin, processOut));
37 
38     processOut.close();
39 
40     if (pidErr) {
41         remove(tmpFile);
42         writefln("yay exited with code \"%d\"", pidErr);
43         return pidErr;
44     }
45 
46     printPackages(tmpFile, terms, color);
47 
48     remove(tmpFile);
49     
50     return 0;
51 }
52 
53 
54 // 1    2    3        4                5        6          
55 // repo/name version (size|aur-votes) [group]? (orphaned) 
56 //  7           8           9             10   
57 // (outofdate) (installed: (version)) \n (description)
58 void printPackages(string tmpFile, string[] searchTerms, bool color) {
59     string contents = readText(tmpFile);
60 
61     Pkg[] pkgs = [];
62 
63     auto packages = matchAll(contents, reg);
64 
65     foreach (pkg; packages) {
66         string pkgsize;
67         string inssize;
68 
69         if (pkg[1] == "aur") {
70             string[] _size = pkg[4].split(' ');
71             pkgsize = _size[0];
72             inssize = _size[1];
73         } else {
74             string[] _size = pkg[4].split(' ');
75             pkgsize = _size[0] ~ " " ~ _size[1];
76             inssize = _size[2] ~ " " ~ _size[3];
77         }
78 
79         pkgs ~= Pkg(
80             pkg[1],  // repo
81             pkg[2], // name
82             pkg[3], // version
83             pkgsize, // package size / aur votes
84             inssize, // installation size / aur popularity
85             pkg[5], // group
86             pkg[6] != "" ? true : false, // is orphaned
87             pkg[7] != "" ? true : false, // is out of date
88             pkg[7], // out of date date
89             pkg[8] != "" ? true : false, // is installed
90             pkg[9], // installed version
91             pkg[10] // description
92         );
93     }
94 
95     sort!((a,b) {
96         return gapWeightedSimilarityNormalized(a.name.split("-"), searchTerms, 0) < 
97                gapWeightedSimilarityNormalized(b.name.split("-"), searchTerms, 0);
98     })(pkgs);
99 
100     foreach (pkg; pkgs) {
101         printPackage(pkg, color);
102     }
103 }
104 
105 void printPackage(Pkg pkg, bool color) {
106     // if (!(pkg.isOrphaned || pkg.isInstalled || pkg.isOutdated)) return;
107     winsize w;
108     ioctl(0, TIOCGWINSZ, &w);
109     int terminalWidth = w.ws_col;
110 
111     string installstr = color || pkg.isInstalled ? "[i]" : "[ ]";
112     string orphanedstr = color || pkg.isOrphaned ? "[a]" : "[ ]";
113     string outdatedstr = color || pkg.isOutdated ? "[o]" : "[ ]";
114     ulong flagsLength = installstr.length + orphanedstr.length + outdatedstr.length + 2;
115     
116     ulong installPos = 1;
117     if (pkg.name.length + flagsLength < terminalWidth / 2) {
118         installPos = (terminalWidth / 2) - pkg.name.length - flagsLength;
119     }
120     write(pkg.name);
121 
122     write(' '.repeat(installPos));
123 
124     if (pkg.isOrphaned) {
125         writecol(FG.ltred, color,orphanedstr);
126     } else {
127         // write(' '.repeat(orphanedstr.length));
128         writecol(FG.dkgray, color, orphanedstr);
129     }
130 
131     write(' ');
132 
133     if (pkg.isOutdated) {
134         writecol(FG.ltred, color, outdatedstr);
135     } else {
136         // write(' '.repeat(outdatedstr.length));
137         writecol(FG.dkgray, color, outdatedstr);
138     }
139 
140     write(' ');
141 
142     if (pkg.isInstalled) {
143         writecol(FG.ltgreen, color, installstr);
144     } else {
145         // write(' '.repeat(installstr.length));
146         writecol(FG.dkgray, color, installstr);
147     }
148 
149     write(' ');
150 
151     ulong verlen = pkg.installedVersion != "" ? pkg.installedVersion.length + (color ? 0 : 1) : pkg.ver.length;
152     if (pkg.installedVersion != "") {
153         writecol(FG.ltmagenta, color, (color ? "" : "@") ~ pkg.installedVersion);
154     } else {
155         writecol(FG.cyan, color, pkg.ver);
156     }
157 
158     ulong reposize = "[aur]".length + 1;
159     ulong sizelen = pkg.pkgsize.length + pkg.inssize.length + 1;
160 
161     ulong wantedlen = verlen + sizelen + 1 + reposize;
162     ulong rside = 1;
163     if (wantedlen < terminalWidth / 2) {
164         rside = (terminalWidth / 2) - wantedlen;
165     }
166 
167     write(' '.repeat(rside));
168 
169     if (pkg.repo == "aur") {
170         float votes = pkg.aurvotes.to!float;
171         FG colvot = FG.dkgray;
172         if (votes >= 100.0) colvot = FG.ltred;
173         if (votes >= 250.0) colvot = FG.ltyellow;
174         if (votes >= 500.0) colvot = FG.ltcyan;
175         if (votes >= 750.0) colvot = FG.ltgreen;
176         if (votes >= 1000.0) colvot = FG.ltblue;
177 
178         float popul = pkg.aurpopul.to!float;
179         FG colpop = FG.dkgray;
180         if (popul >= 1.0) colpop = FG.ltred;
181         if (popul >= 10.0) colpop = FG.ltyellow;
182         if (popul >= 20.0) colpop = FG.ltcyan;
183         if (popul >= 30.0) colpop = FG.ltgreen;
184         if (popul >= 40.0) colpop = FG.ltblue;
185         writecol(colvot, color, pkg.aurvotes);
186         write(' ');
187         writecol(colpop, color, pkg.aurpopul);
188     } else {
189         writecol(FG.reset, color, pkg.pkgsize);
190         write(' ');
191         writecol(FG.reset, color, pkg.inssize);
192     }
193     write(' ');
194 
195     FG repocol = FG.reset;
196     switch (pkg.repo) {
197         case "aur": repocol = FG.ltblue; break;
198         case "core": repocol = FG.ltyellow; break;
199         case "extra": repocol = FG.ltgreen; break;
200         case "community": repocol = FG.ltmagenta; break;
201         default: 
202             if (pkg.repo.canFind("testing")) {
203                 repocol = FG.ltred;
204             } else {
205                 repocol = FG.ltcyan;
206             }
207         break;
208     }
209 
210     writecol(repocol, color, "[" ~ pkg.repo[0..3] ~ "]");
211     writeln();
212     writeln("    " ~ pkg.description);
213 
214     // writeln();
215 }
216 
217 void writecol(FG col, bool enabled, string args) {
218     if (enabled) {
219         write(col ~ args ~ FG.reset);
220     } else {
221         write(args);
222     }
223 }
224 
225 void writelncol(FG col, bool enabled, string args) {
226     writecol(col, enabled, args ~ "\n");
227 }