forked from breck7/pldb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PLDB.js
executable file
·2013 lines (1659 loc) · 64.6 KB
/
PLDB.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env node
/*
To investigate slowdowns:
code
node --cpu-prof --cpu-prof-name=test.cpuprofile ./PLDB.js testPerf
Then:
- open a new Chrome tab
- open devtools
- click Performance
- click "Load Profile..."
- select your test.cpuprofile
*/
const path = require("path")
const numeral = require("numeral")
const lodash = require("lodash")
const dayjs = require("dayjs")
const { TreeNode } = require("jtree/products/TreeNode.js")
const { Utils } = require("jtree/products/Utils.js")
const { shiftRight, removeReturnChars } = Utils
const { Disk } = require("jtree/products/Disk.node.js")
const { TrueBaseFolder, TrueBaseFile } = require("truebase/server/TrueBase.js")
const { TrueBaseServer } = require("truebase/server/TrueBaseServer.js")
const baseFolder = path.join(__dirname)
const ignoreFolder = path.join(baseFolder, "ignore")
const siteFolder = path.join(baseFolder, "site")
const nodeModulesFolder = path.join(baseFolder, "node_modules")
const pagesDir = path.join(siteFolder, "pages")
const listsFolder = path.join(siteFolder, "lists")
const conceptsFolderName = "/concepts/"
const linkManyAftertext = links =>
links.map((link, index) => `${index + 1}.`).join(" ") + // notice the dot is part of the link. a hack to make it more unique for aftertext matching.
links.map((link, index) => `\n ${link} ${index + 1}.`).join("")
const cleanAndRightShift = str => Utils.shiftRight(Utils.removeReturnChars(str), 1)
const makePrettyUrlLink = url => `<a href="${url}">${new URL(url).hostname}</a>`
const delimiter = `|_$^`
const quickTree = (rows, header) => `table ${delimiter}
${new TreeNode(rows).toDelimited(delimiter, header, false).replace(/\n/g, "\n ")}`
const toCommaList = (arr, conjunction = "and") => {
if (arr.length === 1) return arr[0]
let last = arr.pop()
return arr.join(", ") + ` ${conjunction} ${last}`
}
const currentYear = new Date().getFullYear()
const getJoined = (node, keywords) => {
const words = keywords
.map(word => node.get(word) || "")
.filter(i => i)
.join(" ")
.split(" ")
return lodash.uniq(words).join(" ")
}
// todo: move to grammar
const isLanguage = type => {
const nonLanguages = {
vm: true,
linter: true,
library: true,
webApi: true,
characterEncoding: true,
cloud: true,
editor: true,
filesystem: true,
feature: true,
packageManager: true,
os: true,
application: true,
framework: true,
standard: true,
hashFunction: true,
compiler: true,
decompiler: true,
binaryExecutable: true,
binaryDataFormat: true,
equation: true,
interpreter: true,
computingMachine: true,
dataStructure: true
}
return nonLanguages[type] ? false : true
}
// Todo: move to Grammar with an enum concept?
const typeNames = new TreeNode(`application
assembly assembly language
binaryDataFormat
binaryExecutable binary executable format
bytecode bytecode format
characterEncoding
cloud cloud service
compiler
editor
esolang esoteric programming language
filesystem
framework
grammarLanguage
idl interface design language
interpreter
ir intermediate representation language
isa instruction set architecture
jsonFormat
library
linter
metalanguage
notation
os operating system
packageManager
feature language feature
pl programming language
plzoo minilanguage
protocol
queryLanguage
schema
standard
stylesheetLanguage
template template language
textData text data format
textMarkup text markup language
visual visual programming language
vm virtual machine
webApi
xmlFormat`).toObject()
class PLDBFile extends TrueBaseFile {
get filePath() {
return this._getFilePath()
}
get domainName() {
return this.get("domainName")
}
get subredditId() {
return this.get("subreddit")?.split("/").pop()
}
get getSource() {
const { repo } = this
return repo ? `git clone ${repo}` : ""
}
get bookCount() {
if (this.quickCache.bookCount !== undefined) return this.quickCache.bookCount
const gr = this.getNode(`goodreads`)?.length
const isbndb = this.getNode(`isbndb`)?.length
let count = 0
if (gr) count += gr - 1
if (isbndb) count += isbndb - 1
this.quickCache.bookCount = count
return count
}
get paperCount() {
if (this.quickCache.paperCount !== undefined) return this.quickCache.paperCount
const ss = this.getNode(`semanticScholar`)?.length
let count = 0
if (ss) count += ss - 1
this.quickCache.paperCount = count
return count
}
get hoplId() {
return this.get("hopl")?.replace("https://hopl.info/showlanguage.prx?exp=", "")
}
get helpfulResearchLinks() {
const id = this.id
const title = this.get("title")
const references = this.findNodes("reference")
.map(node => "Reference: " + node.content)
.join("\n")
const links = ["website", "githubRepo", "wikipedia"]
.filter(key => this.has(key))
.map(key => `${Utils.capitalizeFirstLetter(key)}: ${this.get(key)}`)
.join("\n")
const searchEngines = `Google: https://www.google.com/search?q=${title}+programming+language
Google w/time: https://www.google.com/search?q=${title}+programming+language&tbs=cdr%3A1%2Ccd_min%3A1%2F1%2F1980%2Ccd_max%3A12%2F31%2F1995&tbm=
Google Scholar: https://scholar.google.com/scholar?q=${title}
Google Groups: https://groups.google.com/forum/#!search/${title}
Google Trends: https://trends.google.com/trends/explore?date=all&q=${title}
DDG: https://duckduckgo.com/?q=${title}
Wayback Machine: https://web.archive.org/web/20220000000000*/${title}`
return (
`<br><h3>Links for researching ${title}</h3>` +
(links + references + "\n" + searchEngines)
.split("\n")
.map(line => {
const parts = line.split(": ")
return `<a href="${parts[1]}">${parts[0]}</a>`
})
.join("<br>")
)
}
get names() {
return [
this.id,
this.title,
this.get("standsFor"),
this.get("githubLanguage"),
this.wikipediaTitle,
...this.getAll("aka")
].filter(i => i)
}
get fileExtension() {
return this.extensions.split(" ")[0]
}
get keywords() {
const kw = this.get("keywords")
return kw ? kw.split(" ") : []
}
get repo() {
return this.getOneOf("gitRepo githubRepo gitlabRepo sourcehutRepo".split(" "))
}
get repl() {
return this.getOneOf("webRepl rijuRepl tryItOnline replit".split(" "))
}
get lineCommentToken() {
return this.get("lineCommentToken")
}
get langRankDebug() {
const obj = this.parent.getLanguageRankExplanation(this)
return `TotalRank: ${obj.totalRank} Jobs: ${obj.jobsRank} Users: ${obj.usersRank} Facts: ${obj.factsRank} Links: ${obj.pageRankLinksRank}`
}
get previousRanked() {
return this.isLanguage
? this.parent.getFileAtLanguageRank(this.languageRank - 1)
: this.parent.getFileAtRank(this.rank - 1)
}
get nextRanked() {
return this.isLanguage
? this.parent.getFileAtLanguageRank(this.languageRank + 1)
: this.parent.getFileAtRank(this.rank + 1)
}
get exampleCount() {
return this.allExamples.length + this.featuresWithExamples.length
}
// Todo: make this a general TrueBase feature
// Support inheritance in the dataset. Entities can extend from other entities and override
// only the column values where they are different.
get extended() {
if (this.quickCache.extended) return this.quickCache.extended
const extendedFile = this.getRelationshipFile("supersetOf") || this.getRelationshipFile("implementationOf")
this.quickCache.extended = extendedFile ? extendedFile.patch(this) : this
return this.quickCache.extended
}
get features() {
const featuresMap = this.parent.featuresMap
return this.extended.filter(node => featuresMap.has(node.getWord(0)))
}
get featuresWithExamples() {
return this.features.filter(node => node.length)
}
get originCommunity() {
const originCommunity = this.get("originCommunity")
return originCommunity ? originCommunity.split(" && ") : []
}
get creators() {
return this.get("creators")?.split(" and ") ?? []
}
get hasBooleansPrediction() {
const { keywords } = this
const pairs = ["true false", "TRUE FALSE", "True False"]
let hit
pairs.forEach(pair => {
const parts = pair.split(" ")
if (keywords.includes(parts[0]) && keywords.includes(parts[1]))
hit = {
value: true,
token: pair
}
})
if (hit) return hit
const examples = this.allExamples.map(code => code.code)
pairs.forEach(pair => {
const parts = pair.split(" ")
const hasTrue = examples.some(code => code.includes(parts[0]))
const hasFalse = examples.some(code => code.includes(parts[1]))
if (hasTrue && hasFalse)
hit = {
value: true,
token: pair
}
})
return hit
}
get hasImportsPrediction() {
const { keywords } = this
const words = ["import", "include", "require"]
for (let word of words) {
if (keywords.includes(word)) {
const example = this.allExamples.find(code => code.code.includes(word))
if (example) {
const exampleLine = example.code.split("\n").filter(line => line.includes(word))[0]
return {
value: true,
token: word,
example: exampleLine
}
} else {
console.log(`No example found for ${this.id}`)
}
}
}
}
makeSimpleKeywordPrediction(theWord) {
const { keywords } = this
if (keywords.includes(theWord))
return {
value: true
}
}
get hasWhileLoopsPrediction() {
return this.makeSimpleKeywordPrediction("while")
}
get hasClassesPrediction() {
return this.makeSimpleKeywordPrediction("class")
}
get hasConstantsPrediction() {
return this.makeSimpleKeywordPrediction("const")
}
get hasExceptionsPrediction() {
return this.makeSimpleKeywordPrediction("throw")
}
get hasSwitchPrediction() {
return this.makeSimpleKeywordPrediction("switch")
}
get hasAccessModifiersPrediction() {
return this.makeSimpleKeywordPrediction("public")
}
get hasInheritancePrediction() {
return this.makeSimpleKeywordPrediction("extends")
}
get hasAsyncAwaitPrediction() {
return this.makeSimpleKeywordPrediction("async")
}
get hasConditionalsPrediction() {
return this.makeSimpleKeywordPrediction("if")
}
get hasFunctionsPrediction() {
return (
this.makeSimpleKeywordPrediction("function") ||
this.makeSimpleKeywordPrediction("fun") ||
this.makeSimpleKeywordPrediction("def")
)
}
get allExamples() {
const examples = []
this.findNodes("example").forEach(node => {
examples.push({
code: node.childrenToString(),
source: "the web",
link: ""
})
})
this.findNodes("compilerExplorer example").forEach(node => {
examples.push({
code: node.childrenToString(),
source: "Compiler Explorer",
link: `https://godbolt.org/`
})
})
this.findNodes("rijuRepl example").forEach(node => {
examples.push({
code: node.childrenToString(),
source: "Riju",
link: this.get("rijuRepl")
})
})
this.findNodes("leachim6").forEach(node => {
examples.push({
code: node.getNode("example").childrenToString(),
source: "hello-world",
link: `https://github.com/leachim6/hello-world/blob/main/` + node.get("filepath")
})
})
this.findNodes("helloWorldCollection").forEach(node => {
examples.push({
code: node.childrenToString(),
source: "the Hello World Collection",
link: `http://helloworldcollection.de/#` + node.getWord(1)
})
})
const linguist_url = this.get("linguistGrammarRepo")
this.findNodes("linguistGrammarRepo example").forEach(node => {
examples.push({
code: node.childrenToString(),
source: "Linguist",
link: linguist_url
})
})
this.findNodes("wikipedia example").forEach(node => {
examples.push({
code: node.childrenToString(),
source: "Wikipedia",
link: this.get("wikipedia")
})
})
return examples
}
getMostRecentInt(pathToSet) {
let set = this.getNode(pathToSet)
if (!set) return 0
set = set.toObject()
const key = Math.max(...Object.keys(set).map(year => parseInt(year)))
return parseInt(set[key])
}
get appeared() {
const appeared = this.get("appeared")
return appeared === undefined ? 0 : parseInt(appeared)
}
get website() {
return this.get("website")
}
get type() {
return this.get("type")
}
get isLanguage() {
return isLanguage(this.get("type"))
}
get otherReferences() {
return this.findNodes("reference").map(line => line.content)
}
get wikipediaTitle() {
const wp = this.get("wikipedia")
return wp ? wp.replace("https://en.wikipedia.org/wiki/", "").trim() : ""
}
get numberOfUsersEstimate() {
if (this.quickCache.numberOfUsersEstimate === undefined)
this.quickCache.numberOfUsersEstimate = this.parent.predictnumberOfUsersEstimate(this)
return this.quickCache.numberOfUsersEstimate
}
get numberOfJobsEstimate() {
if (this.quickCache.numberOfJobsEstimate === undefined)
this.quickCache.numberOfJobsEstimate = this.parent.predictnumberOfJobsEstimate(this)
return this.quickCache.numberOfJobsEstimate
}
get percentile() {
return this.parent.predictPercentile(this)
}
getRelationshipFile(relationshipType) {
const hit = this.get(relationshipType)
return hit ? this.parent.getFile(hit) : undefined
}
get languageRank() {
return this.isLanguage ? this.parent.getLanguageRank(this) : undefined
}
get rank() {
if (this.quickCache.rank === undefined) this.quickCache.rank = this.parent.getRank(this)
return this.quickCache.rank
}
get extensions() {
return getJoined(this, [
"fileExtensions",
"githubLanguage fileExtensions",
"pygmentsHighlighter fileExtensions",
"wikipedia fileExtensions"
])
}
get typeName() {
let { type } = this
type = typeNames[type] || type
return lodash.startCase(type).toLowerCase()
}
get lastActivity() {
return lodash.max(this.parsed.findAllWordsWithCellType("yearCell").map(word => parseInt(word.word)))
}
makeATag(id) {
const file = this.parent.getFile(id)
return `<a href="${file.permalink}">${file.title}</a>`
}
get trendingRepos() {
const { title } = this
const count = this.get(`$githubLanguage trendingProjectsCount`)
if (parseInt(count) > 0) {
const table = this.getNode("githubLanguage trendingProjects")
const githubId = this.get("githubLanguage")
if (!table) {
console.log(`Error with ${this.id}`)
return ""
}
const tree = TreeNode.fromSsv(table.childrenToString())
tree.forEach(child => {
child.set("repo", child.get("name"))
child.set("repoLink", child.get("url"))
})
return `## Trending <a href="https://github.com/trending/${githubId}?since=monthly">${title} repos</a> on GitHub
commaTable
${cleanAndRightShift(tree.toDelimited(",", ["repo", "repoLink", "stars", "description"]))}
`
}
return ""
}
get semanticScholar() {
const { title } = this
const items = this.getNode(`semanticScholar`)
if (!items) return ""
if (items.content === "0") return ""
const tree = TreeNode.fromDelimited(items.childrenToString(), "|")
tree.forEach(child => {
child.set("titleLink", `https://www.semanticscholar.org/paper/${child.get("paperId")}`)
})
return `## Publications about ${title} from Semantic Scholar
pipeTable
${cleanAndRightShift(
tree.toDelimited("|", ["title", "titleLink", "authors", "year", "citations", "influentialCitations"])
)}
`
}
get isbndb() {
const { title } = this
const isbndb = this.getNode(`isbndb`)
if (!isbndb) return ""
if (isbndb.content === "0") return ""
const tree = TreeNode.fromDelimited(isbndb.childrenToString(), "|")
tree.forEach(child => {
child.set("titleLink", `https://isbndb.com/book/${child.get("isbn13")}`)
})
return `## Books about ${title} from ISBNdb
pipeTable
${cleanAndRightShift(tree.toDelimited("|", ["title", "titleLink", "authors", "year", "publisher"]))}
`
}
get goodreads() {
const { title } = this
const goodreads = this.getNode(`goodreads`) // todo: the goodreadsIds we have are wrong.
if (!goodreads) return ""
const tree = TreeNode.fromDelimited(goodreads.childrenToString(), "|")
tree.forEach(child => {
child.set("titleLink", `https://www.goodreads.com/search?q=${child.get("title") + " " + child.get("author")}`)
})
return `## Books about ${title} on goodreads
pipeTable
${cleanAndRightShift(tree.toDelimited("|", ["title", "titleLink", "author", "year", "reviews", "ratings", "rating"]))}
`
}
get publications() {
const { title } = this
const dblp = this.getNode(`dblp`)
if (dblp && dblp.get("hits") !== "0") {
const tree = TreeNode.fromDelimited(dblp.getNode("publications").childrenToString(), "|")
tree.forEach(child => {
child.set("titleLink", child.get("doi") ? `https://doi.org/` + child.get("doi") : child.get("url"))
})
return `## ${dblp.get("hits")} publications about ${title} on <a href="${this.get("dblp")}">DBLP</a>
pipeTable
${cleanAndRightShift(tree.toDelimited("|", ["title", "titleLink", "year"]))}
`
}
return ""
}
get featuresTable() {
const { features, id } = this
if (!features.length) return ""
const { featuresMap } = this.parent
const table = new TreeNode()
features.forEach(node => {
const feature = featuresMap.get(node.getWord(0))
if (!feature) {
console.log(`warning: we need a features page for feature '${node.getWord(0)}' found in '${id}'`)
return
}
const tokenPath = feature.token
const supported = node.content === "true"
table
.appendLineAndChildren(
`row`,
`Feature ${feature.title}
FeatureLink ${feature.titleLink}
Supported ${supported ? `<span class="hasFeature">✓</span>` : `<span class="doesNotHaveFeature">X</span>`}
Token ${supported && tokenPath ? this.get(tokenPath) ?? "" : ""}
Example`
)
.touchNode("Example")
.setChildren(node.childrenToString())
})
return `## Language <a href="../lists/features.html">features</a>
treeTable
${table.sortBy(["Supported", "Example"]).reverse().toString().replace(/\n/g, "\n ")}`
}
get hackerNewsTable() {
const hnTable = this.getNode(`hackerNewsDiscussions`)?.childrenToString()
if (!hnTable) return ""
const table = TreeNode.fromDelimited(hnTable, "|")
table.forEach(row => {
row.set("titleLink", `https://news.ycombinator.com/item?id=${row.get("id")}`)
row.set("date", dayjs(row.get("time")).format("MM/DD/YYYY"))
})
const delimited = table
.toDelimited("|", ["title", "titleLink", "date", "score", "comments"])
.replace(/\n/g, "\n ")
.trim()
return `## HackerNews discussions of ${this.title}
pipeTable
${delimited}`
}
get sourceUrl() {
return `https://github.com/breck7/pldb/blob/main/concepts/${this.id}.pldb`
}
toScroll() {
const { typeName, title, id, getSource } = this
if (title.includes("%20")) throw new Error("bad space in title: " + title)
const sourceCode = getSource
? `codeWithHeader Source code:
${getSource}`
: ""
return `import ../header.scroll
baseUrl https://pldb.com/concepts/
title ${title}
title ${title} - ${lodash.upperFirst(typeName)}
hidden
html
<a class="trueBaseThemePreviousItem" href="${this.prevPage}"><</a>
<a class="trueBaseThemeNextItem" href="${this.nextPage}">></a>
viewSourceUrl ${this.sourceUrl}
startColumns 4
<div class="trueBaseThemeQuickLinks">${this.quickLinks}</div>
${this.oneLiner}
${sourceCode}
${this.kpiBar}
${this.tryNowRepls}
${this.monacoEditor}
${this.image}
${this.descriptionSection}
${this.factsSection}
<br>
${this.exampleSection}
${this.funFactSection}
${this.keywordsSection}
endColumns
${this.featuresTable}
${this.trendingRepos}
${this.publications}
${this.hackerNewsTable}
keyboardNav ${this.prevPage} ${this.nextPage}
import ../footer.scroll
`.replace(/\n\n\n+/g, "\n\n")
}
get image() {
const { title } = this
let image = this.get("screenshot")
let caption = `A screenshot of the visual language ${title}.
link /search.html?q=select+type%0D%0Awhere+type+%3D+visual visual language`
if (!image) {
image = this.get("photo")
caption = `A photo of ${title}.`
}
if (!image) return ""
return `openGraphImage ${image}
image ${image.replace("https://pldb.com/", "../")}
caption ${caption}`
}
get monacoEditor() {
const monaco = this.get("monaco")
if (!monaco) return ""
const exampleToUse = this.allExamples.find(example => !example.code.includes("`")) // our monaco code struggles with backticks for some reason.
const example = exampleToUse ? exampleToUse.code.replace(/\n/g, "\n ") : ""
return `monacoEditor ${monaco}
${example}`
}
get prevPage() {
return this.previousRanked.permalink
}
get nextPage() {
return this.nextRanked.permalink
}
get quickLinks() {
// Sigh. After learning Material Designs realized
// it's partially broken on purpose:
// https://github.com/google/material-design-icons/issues/166
const SVGS = {
twitter: `<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>`,
reddit: `<svg role="img" width="32px" height="32px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M 18.65625 4 C 16.558594 4 15 5.707031 15 7.65625 L 15 11.03125 C 12.242188 11.175781 9.742188 11.90625 7.71875 13.0625 C 6.945313 12.316406 5.914063 12 4.90625 12 C 3.816406 12 2.707031 12.355469 1.9375 13.21875 L 1.9375 13.25 L 1.90625 13.28125 C 1.167969 14.203125 0.867188 15.433594 1.0625 16.65625 C 1.242188 17.777344 1.898438 18.917969 3.03125 19.65625 C 3.023438 19.769531 3 19.882813 3 20 C 3 22.605469 4.574219 24.886719 6.9375 26.46875 C 9.300781 28.050781 12.488281 29 16 29 C 19.511719 29 22.699219 28.050781 25.0625 26.46875 C 27.425781 24.886719 29 22.605469 29 20 C 29 19.882813 28.976563 19.769531 28.96875 19.65625 C 30.101563 18.917969 30.757813 17.777344 30.9375 16.65625 C 31.132813 15.433594 30.832031 14.203125 30.09375 13.28125 L 30.0625 13.25 C 29.292969 12.386719 28.183594 12 27.09375 12 C 26.085938 12 25.054688 12.316406 24.28125 13.0625 C 22.257813 11.90625 19.757813 11.175781 17 11.03125 L 17 7.65625 C 17 6.675781 17.558594 6 18.65625 6 C 19.175781 6 19.820313 6.246094 20.8125 6.59375 C 21.65625 6.890625 22.75 7.21875 24.15625 7.3125 C 24.496094 8.289063 25.414063 9 26.5 9 C 27.875 9 29 7.875 29 6.5 C 29 5.125 27.875 4 26.5 4 C 25.554688 4 24.738281 4.535156 24.3125 5.3125 C 23.113281 5.242188 22.246094 4.992188 21.46875 4.71875 C 20.566406 4.402344 19.734375 4 18.65625 4 Z M 16 13 C 19.152344 13 21.964844 13.867188 23.9375 15.1875 C 25.910156 16.507813 27 18.203125 27 20 C 27 21.796875 25.910156 23.492188 23.9375 24.8125 C 21.964844 26.132813 19.152344 27 16 27 C 12.847656 27 10.035156 26.132813 8.0625 24.8125 C 6.089844 23.492188 5 21.796875 5 20 C 5 18.203125 6.089844 16.507813 8.0625 15.1875 C 10.035156 13.867188 12.847656 13 16 13 Z M 4.90625 14 C 5.285156 14 5.660156 14.09375 5.96875 14.25 C 4.882813 15.160156 4.039063 16.242188 3.53125 17.4375 C 3.277344 17.117188 3.125 16.734375 3.0625 16.34375 C 2.953125 15.671875 3.148438 14.976563 3.46875 14.5625 C 3.472656 14.554688 3.464844 14.539063 3.46875 14.53125 C 3.773438 14.210938 4.3125 14 4.90625 14 Z M 27.09375 14 C 27.6875 14 28.226563 14.210938 28.53125 14.53125 C 28.535156 14.535156 28.527344 14.558594 28.53125 14.5625 C 28.851563 14.976563 29.046875 15.671875 28.9375 16.34375 C 28.875 16.734375 28.722656 17.117188 28.46875 17.4375 C 27.960938 16.242188 27.117188 15.160156 26.03125 14.25 C 26.339844 14.09375 26.714844 14 27.09375 14 Z M 11 16 C 9.894531 16 9 16.894531 9 18 C 9 19.105469 9.894531 20 11 20 C 12.105469 20 13 19.105469 13 18 C 13 16.894531 12.105469 16 11 16 Z M 21 16 C 19.894531 16 19 16.894531 19 18 C 19 19.105469 19.894531 20 21 20 C 22.105469 20 23 19.105469 23 18 C 23 16.894531 22.105469 16 21 16 Z M 21.25 21.53125 C 20.101563 22.597656 18.171875 23.28125 16 23.28125 C 13.828125 23.28125 11.898438 22.589844 10.75 21.65625 C 11.390625 23.390625 13.445313 25 16 25 C 18.554688 25 20.609375 23.398438 21.25 21.53125 Z"/></svg>`,
wikipedia: `<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="98.05px" height="98.05px" viewBox="0 0 98.05 98.05" style="enable-background:new 0 0 98.05 98.05;" xml:space="preserve"><path d="M98.023,17.465l-19.584-0.056c-0.004,0.711-0.006,1.563-0.017,2.121c1.664,0.039,5.922,0.822,7.257,4.327L66.92,67.155 c-0.919-2.149-9.643-21.528-10.639-24.02l9.072-18.818c1.873-2.863,5.455-4.709,8.918-4.843l-0.01-1.968L55.42,17.489 c-0.045,0.499,0.001,1.548-0.068,2.069c5.315,0.144,7.215,1.334,5.941,4.508c-2.102,4.776-6.51,13.824-7.372,15.475 c-2.696-5.635-4.41-9.972-7.345-16.064c-1.266-2.823,1.529-3.922,4.485-4.004v-1.981l-21.82-0.067 c0.016,0.93-0.021,1.451-0.021,2.131c3.041,0.046,6.988,0.371,8.562,3.019c2.087,4.063,9.044,20.194,11.149,24.514 c-2.685,5.153-9.207,17.341-11.544,21.913c-3.348-7.43-15.732-36.689-19.232-44.241c-1.304-3.218,3.732-5.077,6.646-5.213 l0.019-2.148L0,17.398c0.005,0.646,0.027,1.71,0.029,2.187c4.025-0.037,9.908,6.573,11.588,10.683 c7.244,16.811,14.719,33.524,21.928,50.349c0.002,0.029,2.256,0.059,2.281,0.008c4.717-9.653,10.229-19.797,15.206-29.56 L63.588,80.64c0.005,0.004,2.082,0.016,2.093,0.007c7.962-18.196,19.892-46.118,23.794-54.933c1.588-3.767,4.245-6.064,8.543-6.194 l0.032-1.956L98.023,17.465z"/></svg>`
}
const links = {
home: this.website,
terminal: this.repl,
code: this.repo,
menu_book: this.get("documentation"),
mail: this.get("emailList"),
wikipedia: this.get(`wikipedia`),
reddit: this.get("subreddit"),
twitter: this.get("twitter"),
edit: `/edit.html?id=${this.id}`
}
return Object.keys(links)
.filter(key => links[key])
.map(key =>
SVGS[key]
? `<a href="${links[key]}">${SVGS[key]}</a>`
: `<a href="${links[key]}" class="material-symbols-outlined">${key}</a>`
)
.join(" ")
}
get factsSection() {
return this.facts.map(fact => `- ${fact}`).join("\n")
}
get sourceStatus() {
const value = this.get("isOpenSource")
if (value === undefined) return ""
return value ? " open source" : " closed source"
}
get oneLiner() {
const { typeName, title, creators, appeared, sourceStatus } = this
const standsFor = this.get("standsFor")
let akaMessage = standsFor ? `, aka ${standsFor},` : ""
let creatorsStr = ""
let creatorsLinks = ""
if (creators.length) {
creatorsStr = ` by ` + creators.join(" and ")
creatorsLinks = creators.map(name => ` link ../lists/creators.html#${lodash.camelCase(name)} ${name}`).join("\n")
}
return `* ${title}${akaMessage} is ${Utils.getIndefiniteArticle(sourceStatus || typeName)}${sourceStatus} ${
this.typeLink
}${appeared ? ` created in ${appeared}` : ""}${creatorsStr}.
link /search.html?q=select+type+appeared%0D%0Awhere+appeared+%3D+${appeared} ${appeared}
${creatorsLinks}
`
}
get typeLink() {
return `<a href="/search.html?q=select+type%0D%0Awhere+type+%3D+${this.type}">${this.typeName}</a>`
}
get descriptionSection() {
let description = ""
const authoredDescription = this.get("description")
const wikipediaSummary = this.get("wikipedia summary")
const ghDescription = this.get("githubRepo description")
const wpLink = this.get(`wikipedia`)
if (wikipediaSummary)
description =
wikipediaSummary.split(". ").slice(0, 3).join(". ") +
`. Read more on Wikipedia...\n ${wpLink} Read more on Wikipedia...`
else if (authoredDescription) description = authoredDescription
else if (ghDescription) description = ghDescription
return `* ${description}`
}
get facts() {
const { title, website } = this
const facts = []
if (website) facts.push(`${title} website\n ${website}`)
const downloadPageUrl = this.get("downloadPageUrl")
if (downloadPageUrl) facts.push(`${title} downloads page\n ${downloadPageUrl}`)
const wikipediaLink = this.get("wikipedia")
const wikiLink = wikipediaLink ? wikipediaLink : ""
if (wikiLink) facts.push(`${title} Wikipedia page\n ${wikiLink}`)
const githubRepo = this.getNode("githubRepo")
if (githubRepo) {
const stars = githubRepo.get("stars")
const starMessage = stars ? ` and has ${numeral(stars).format("0,0")} stars` : ""
facts.push(`${title} is developed on <a href="${githubRepo.getWord(1)}">GitHub</a>${starMessage}`)
}
const gource = this.get("gource")
if (gource) facts.push(`Watch the history of <a href="${gource}">the ${title} repo visualized with Gource</a>`)
const gitlabRepo = this.get("gitlabRepo")
if (gitlabRepo) facts.push(`${title} on GitLab\n ${gitlabRepo}`)
const documentationLinks = this.getAll("documentation")
if (documentationLinks.length === 1) facts.push(`${title} docs\n ${documentationLinks[0]}`)
else if (documentationLinks.length > 1)
facts.push(
`PLDB has ${documentationLinks.length} documentation sites for ${title}: ${documentationLinks
.map(makePrettyUrlLink)
.join(", ")}`
)
const specLinks = this.getAll("spec")
if (specLinks.length === 1) facts.push(`${title} specs\n ${specLinks[0]}`)
else if (specLinks.length > 1)
facts.push(
`PLDB has ${specLinks.length} specification sites for ${title}: ${specLinks.map(makePrettyUrlLink).join(", ")}`
)
const emailListLinks = this.getAll("emailList")
if (emailListLinks.length === 1) facts.push(`${title} mailing list\n ${emailListLinks[0]}`)
else if (emailListLinks.length > 1)
facts.push(
`PLDB has ${emailListLinks.length} mailing list sites for ${title}: ${emailListLinks
.map(makePrettyUrlLink)
.join(", ")}`
)
const demoVideo = this.get("demoVideo")
if (demoVideo) facts.push(`Video demo of ${title}\n ${demoVideo}`)
const githubRepoCount = this.get("githubLanguage repos")
if (githubRepoCount) {
const url = `https://github.com/search?q=language:${this.get("githubLanguage")}`
const repoCount = numeral(githubRepoCount).format("0,0")
facts.push(`There are at least ${repoCount} ${title} repos on <a href="${url}">GitHub</a>`)
}
const supersetOf = this.getRelationshipFile("supersetOf")
if (supersetOf) facts.push(`${title} is a superset of ${supersetOf.link}`)
const implementationOf = this.getRelationshipFile("implementationOf")
if (implementationOf) facts.push(`${title} is an implementation of ${implementationOf.link}`)
const { originCommunity } = this
let originCommunityStr = ""
if (originCommunity.length) {
originCommunityStr = originCommunity
.map(name => `<a href="../lists/originCommunities.html#${lodash.camelCase(name)}">${name}</a>`)
.join(" and ")
facts.push(`${title} first developed in ${originCommunityStr}`)
}
const { numberOfJobsEstimate } = this
const jobs = numberOfJobsEstimate > 10 ? numeral(numberOfJobsEstimate).format("0a") : ""
if (jobs) facts.push(`PLDB estimates there are currently ${jobs} job openings for ${title} programmers.`)
const { extensions } = this
if (extensions) facts.push(`file extensions for ${title} include ${toCommaList(extensions.split(" "))}`)
const compilesTo = this.get("compilesTo")
if (compilesTo)
facts.push(
`${title} compiles to ${compilesTo
.split(" ")
.map(link => this.makeATag(link))
.join(" or ")}`
)
const writtenIn = this.get("writtenIn")
if (writtenIn)
facts.push(
`${title} is written in ${writtenIn
.split(" ")
.map(link => this.makeATag(link))
.join(" & ")}`
)
const twitter = this.get("twitter")
if (twitter) facts.push(`${title} on Twitter\n ${twitter}`)
const conferences = this.getNodesByGlobPath("conference")
if (conferences.length) {
facts.push(
`Recurring conference about ${title}: ${conferences.map(
tree => `<a href="${tree.getWord(1)}">${tree.getWordsFrom(2)}</a>`
)}`