fork(1) download
  1. using fandoc
  2. using compilerDoc
  3.  
  4. class Main {
  5. Void main() {
  6. tellMeAbout := "maps"
  7.  
  8. index := IndexBuilder().indexAll.buildIndex
  9. index.tellMeAbout(tellMeAbout).join("\n\n" + "".padl(80, '-')) { it.toPlainText(120) } { echo(it) }
  10. }
  11. }
  12.  
  13. const class Index {
  14. internal const Str:Section[] sections
  15.  
  16. new make(|This| f) { f(this) }
  17.  
  18. Section[] tellMeAbout(Str keyword) {
  19. if (keyword.contains(" "))
  20. throw ArgErr("Keywords can not contain whitespace! $keyword")
  21.  
  22. secs := (Section[]) (sections[keyword] ?: Section[,]).rw
  23. stemmed := SectionBuilder.stem(keyword)
  24. if (stemmed != keyword)
  25. secs.addAll(sections[stemmed] ?: Section#.emptyList)
  26.  
  27. sortScore := |Section s->Int| { (s.parents.size * 2) + s.keywords.size + (s.isApi ? 10 : 0) }
  28. secs = secs.rw.sort |s1, s2| { sortScore(s1) <=> sortScore(s2) }
  29. return secs
  30. }
  31. }
  32.  
  33. class IndexBuilder {
  34. private DocEnv docEnv := DefaultDocEnv()
  35. Section[] sections := Section[,]
  36.  
  37. Index buildIndex() {
  38. keywords := sections.map { it.keywords }.flatten.unique.sort
  39.  
  40. sections := Str:Section[][:]
  41. keywords.each |keyword| {
  42. sections[keyword] = this.sections.findAll { it.containsKeyword(keyword) }
  43. }
  44. return Index {
  45. it.sections = sections
  46. }
  47. }
  48.  
  49. This indexAll() {
  50. Env.cur.findAllPodNames.each { indexPod(it) }
  51. return this
  52. }
  53.  
  54. This indexPod(Str podName) {
  55. podFile := Env.cur.findPodFile(podName)
  56. docPod := DocPod.load(docEnv, podFile)
  57. podSec := SectionBuilder(docPod)
  58. sections.add(podSec.toSection)
  59.  
  60. indexDocs(podName, podSec)
  61. indexTypes(docPod, podSec)
  62. return this
  63. }
  64.  
  65. This indexFandoc(Str pod, Str type, InStream in) {
  66. doIndexFandoc(pod, type, in, null).map { it.toSection }
  67. return this
  68. }
  69.  
  70. private Void indexTypes(DocPod docPod, SectionBuilder podSec) {
  71. docPod.types.each |DocType type| {
  72. typeSec := SectionBuilder.makeType(type) { it.parents.push(podSec) }
  73.  
  74. secs := (SectionBuilder[]) type.slots.map {
  75. SectionBuilder.makeSlot(it)
  76. }
  77. secs.each { it.parents.push(typeSec).push(podSec) }
  78.  
  79. sections.add(typeSec.toSection)
  80. sections.addAll(secs.map { it.toSection })
  81. }
  82. }
  83.  
  84. private Void indexDocs(Str podName, SectionBuilder podSec) {
  85. podFile := Env.cur.findPodFile(podName)
  86. Zip.open(podFile).contents.findAll |file, uri| { uri.ext == "fandoc" && uri.path[0] == "doc" }.each |File fandocFile| {
  87. typeSec := SectionBuilder.makeChapter(podName, fandocFile.basename) { it.parents.push(podSec) }
  88.  
  89. secs := doIndexFandoc(podName, fandocFile.basename, fandocFile.in, typeSec)
  90. secs.each { it.parents.push(typeSec).push(podSec) }
  91.  
  92. sections.add(typeSec.toSection)
  93. sections.addAll(secs.map { it.toSection })
  94. }
  95. }
  96.  
  97. private SectionBuilder[] doIndexFandoc(Str pod, Str type, InStream in, SectionBuilder? parent) {
  98. doc := FandocParser().parse("${pod}::${type}", in, true)
  99.  
  100. overview := false
  101. bobs := SectionBuilder[,]
  102. // for now, ignore headings that are buried in lists
  103. doc.children.each |elem| {
  104. if (elem is Heading) {
  105. if (parent != null && bobs.isEmpty && (elem as Heading).title == "Overview")
  106. overview = true
  107. else
  108. bobs.add(SectionBuilder.makeDoc(pod, type, elem, bobs, overview))
  109.  
  110. } else {
  111. // ? 'cos not all fandocs start with a heading!
  112. if (bobs.isEmpty)
  113. parent?.addContent(elem)
  114. else
  115. bobs.last.addContent(elem)
  116. }
  117. }
  118. return bobs
  119. }
  120. }
  121.  
  122. const class Section {
  123. const Str what
  124. const Str pod
  125. const Str? type
  126. const Str title
  127. const Str content
  128. const Bool isApi
  129. const Bool isDoc
  130. const Str[] keywords
  131. const Str fanUrl
  132. const Uri webUrl
  133. const Section[] parents
  134.  
  135. new make(|This| f) {
  136. f(this)
  137.  
  138. // TODO add acronyms
  139. keywords = keywords.map { it.lower }
  140. }
  141.  
  142. internal Bool containsKeyword(Str keyword) {
  143. keywords.contains(keyword)
  144. }
  145.  
  146. Str toPlainText(Int maxWidth := 80) {
  147. lev := 0
  148. text := "\n\n(${what})\n${webUrl}\n\n"
  149. parents.dup.insert(0, this).eachr {
  150. text += "".justl(lev * 2)
  151. text += "${it.title}\n"; lev++
  152. }
  153. text += "\n" + content
  154. return "\n\n" + TextWrapper { normaliseWhitespace = false }.wrap(text, maxWidth)
  155. }
  156.  
  157. @NoDoc
  158. override Str toStr() { fanUrl }
  159. }
  160.  
  161. class SectionBuilder {
  162. static const Uri webBaseUrl := `http://f...content-available-to-author-only...m.org/doc/`
  163. Str fanUrl
  164. Uri webUrl
  165. Str what
  166. Str pod
  167. Str? type
  168. Str title
  169. Str? fandoc
  170. Bool isApi
  171. Str[] keywords
  172. Heading? heading
  173. DocNode[]? content
  174. SectionBuilder[] parents := SectionBuilder[,]
  175. Section? section
  176.  
  177. new makePod(DocPod pod) {
  178. this.what = "Pod"
  179. this.pod = pod.name
  180. this.title = pod.name
  181. this.fandoc = pod.summary
  182. this.fanUrl = "${pod.name}::index"
  183. this.webUrl = webBaseUrl + `${pod.name}/index`
  184. this.keywords = [pod.name]
  185. }
  186.  
  187. new makeType(DocType type) {
  188. this.what = "Type"
  189. this.pod = type.pod.name
  190. this.type = type.name
  191. this.title = type.name
  192. this.fanUrl = "${this.pod}::${this.type}"
  193. this.webUrl = webBaseUrl + `${this.pod}/${this.type}`
  194. this.fandoc = type.doc.text
  195. this.keywords = [type.name]
  196. this.isApi = true
  197. }
  198.  
  199. new makeSlot(DocSlot slot) {
  200. this.what = "Slot"
  201. this.pod = slot.parent.pod
  202. this.type = slot.parent.name
  203. this.title = slot.name
  204. this.fanUrl = "${this.pod}::${this.type}.${slot.name}"
  205. this.webUrl = webBaseUrl + `${this.pod}/${this.type}#${slot.name}`
  206. this.fandoc = slot.doc.text
  207. this.keywords = [slot.name]
  208. this.isApi = true
  209.  
  210. field := slot as DocField
  211. if (field != null) {
  212. this.what = "Field"
  213. if (field.init != null)
  214. title += " := ${field.init}"
  215. }
  216.  
  217. method := slot as DocMethod
  218. if (method != null) {
  219. this.what = "Method"
  220. title += "(" + method.params.join(", ") { it.toStr } + ")"
  221. }
  222. }
  223.  
  224. new makeChapter(Str pod, Str type) {
  225. this.what = "Documentation"
  226. if (type == "pod") {
  227. this.pod = pod
  228. this.type = "pod-doc"
  229. this.title = "pod-doc"
  230. this.fanUrl = "${pod}::index"
  231. this.webUrl = webBaseUrl + `${pod}/index`
  232. this.content = DocNode[,]
  233. this.keywords = [pod]
  234.  
  235. } else {
  236. this.pod = pod
  237. this.type = type
  238. this.title = type
  239. this.fanUrl = "${pod}::${type}"
  240. this.webUrl = webBaseUrl + `${pod}/${type}`
  241. this.content = DocNode[,]
  242. this.keywords = type.toDisplayName.split.map { stem(it) }
  243. }
  244. }
  245.  
  246. new makeDoc(Str pod, Str type, Heading heading, SectionBuilder[] bobs, Bool overview) {
  247. this.what = "Documentation"
  248. this.pod = pod
  249. this.type = type
  250. this.heading = heading
  251. this.fanUrl = "${pod}::${type}#${heading.anchorId}"
  252. this.webUrl = webBaseUrl + `${pod}/${type}#${heading.anchorId}`
  253. this.content = DocNode[,]
  254.  
  255. levs := Int[1]
  256. lev := heading.level
  257. bobs.eachr |sec| {
  258. if (sec.heading.level == lev)
  259. levs.push(levs.pop.increment)
  260. if (sec.heading.level < lev) {
  261. levs.push(1)
  262. lev = sec.heading.level
  263. parents.push(sec)
  264. }
  265. }
  266. // cater for missing out 'Overview' sections
  267. if (overview)
  268. levs.push(levs.pop.increment)
  269.  
  270. chapter := Version(levs.reverse)
  271. this.title = "${chapter}. ${heading.title}"
  272.  
  273. this.keywords = heading.title.toDisplayName.split.map { stem(it) }
  274. .exclude |Str key->Bool| { key.size < 2 || key.endsWith("-") } // remove nonsense
  275. .exclude |Str key->Bool| { ["and", "or", "the"].contains(key) } // remove stopwords
  276. }
  277.  
  278. // TODO proper stemming!
  279. static Str stem(Str word) {
  280. // classes -> class, closures -> closure!!!??
  281. if (word.endsWith("ses"))
  282. word = word[0..<-2]
  283. // pods -> pod, this -> this, class -> class
  284. if (word.endsWith("s") && !word.endsWith("is") && !word.endsWith("ss"))
  285. word = word[0..<-1]
  286. return word
  287. }
  288.  
  289. Void addContent(DocNode node) {
  290. content.add(node)
  291. }
  292.  
  293. Section toSection() {
  294. if (fandoc == null) {
  295. buf := Buf()
  296. out := FandocDocWriter(buf.out)
  297. content.each { it.write(out) }
  298. fandoc = buf.flip.readAllStr
  299. }
  300.  
  301. return section = Section {
  302. it.what = this.what
  303. it.pod = this.pod
  304. it.type = this.type
  305. it.title = this.title
  306. it.isApi = this.isApi
  307. it.isDoc = this.isApi.not
  308. it.keywords = this.keywords
  309. it.content = fandoc
  310. it.fanUrl = this.fanUrl
  311. it.webUrl = this.webUrl
  312. it.parents = this.parents.map { it.section }.exclude { it == null }
  313. }
  314. }
  315.  
  316. override Str toStr() { fanUrl }
  317. }
  318.  
  319. ** Utility class for pretty-printing text.
  320. **
  321. ** Example:
  322. **
  323. ** pre>
  324. ** text := TextWrapper().wrap("Chuck Norris once ordered a Big Mac at Burger King, and got one.", 25)
  325. **
  326. ** // --> Chuck Norris once ordered
  327. ** a Big Mac at Burger King,
  328. ** and got one.
  329. **
  330. ** <pre
  331. const class TextWrapper {
  332.  
  333. ** If 'true', then the text is trimmed.
  334. const Bool trim := true
  335.  
  336. ** If 'true', then each run of whitespace (including tabs and new lines) is replaced with a single space character.
  337. const Bool normaliseWhitespace := true
  338.  
  339. ** If 'true', then words longer than 'maxWidth' will be broken in order to ensure that no line
  340. ** is longer than 'maxWidth'.
  341. **
  342. ** If 'false', then long words will not be broken and some lines may be longer than 'maxWidth'.
  343. ** (Long words will be put on a line by themselves, in order to minimise the amount by which
  344. ** 'maxWidth' is exceeded.)
  345. const Bool breakLongWords := true
  346.  
  347. ** If 'true', wrapping will occur on whitespace and after hyphens in compound words.
  348. const Bool breakOnHyphens := true
  349.  
  350. ** Standard it-block ctor for setting field values.
  351. **
  352. ** syntax: fantom
  353. ** iceT := TextWrapper {
  354. ** it.breakLongWords = false
  355. ** }
  356. new make(|This|? f := null) { f?.call(this) }
  357.  
  358. ** Formats and word wraps the given text.
  359. Str wrap(Str text, Int maxWidth) {
  360. if (trim)
  361. text = text.trim
  362.  
  363. if (normaliseWhitespace) {
  364. chrs := Int[,]
  365. spce := false
  366. text.each |ch| {
  367. if (ch.isSpace) {
  368. if (!spce)
  369. chrs.add(' ')
  370. spce = true
  371. } else {
  372. spce = false
  373. chrs.add(ch)
  374. }
  375. }
  376. text = Str.fromChars(chrs)
  377. }
  378.  
  379. buff := StrBuf()
  380. line := StrBuf()
  381. word := StrBuf()
  382.  
  383. flushLine := |->| {
  384. if (!normaliseWhitespace || line.toStr.trimEnd.size > 0) {
  385. buff.join(line.toStr.trimEnd, "\n")
  386. line.clear
  387. }
  388. }
  389.  
  390. flushWord := |Str char| {
  391. if (word.size + char.size > 0) {
  392. if (line.size + (word.toStr + char).trim.size > maxWidth)
  393. flushLine()
  394.  
  395. if (word.size > maxWidth && breakLongWords) {
  396. flushLine()
  397. while (word.size > maxWidth) {
  398. part := word.getRange(0..<maxWidth)
  399. buff.join(part, "\n")
  400. word.removeRange(0..<maxWidth)
  401. }
  402. }
  403.  
  404. line.add(word.toStr)
  405. word.clear
  406.  
  407. if (char == "\n")
  408. flushLine()
  409. else
  410. line.add(char)
  411. }
  412. }
  413.  
  414. text.each |char| {
  415. if (char.isSpace || (breakOnHyphens && char == '-'))
  416. flushWord(char.toChar)
  417. else
  418. word.addChar(char)
  419. }
  420.  
  421. flushWord("")
  422. flushLine()
  423.  
  424. return buff.toStr
  425. }
  426. }
  427.  
Success #stdin #stdout 4.31s 2922496KB
stdin
Standard input is empty
stdout

(Documentation)
http://f...content-available-to-author-only...m.org/doc/docLang/Literals#map

docLang
  Literals
    13. Map

The [sys::Map]`sys::Map` class stores a set of key/value pairs using a hash table.  Maps may be instantiated using the
following literal syntax:

  // syntax format where K:V is the optional map type,
  // and the keys and values are arbitrary expressions:
  [V:K][key0:value0, key1:value1, ... keyN:valueN]

  // examples
  [Int:Str][1:"one", 2:"two"]  // map of Strs keyed by Int
  Int:Str[1:"one", 2:"two"]    // same as above with shorthand type syntax
  [1:"one", 2:"two"]           // same as above using type inference
  Int:Str[:]                   // empty Int:Str map
  [:]                          // empty map of Obj:Obj?

The 'Map' literal syntax is like 'List' except we specify the key value pairs using a colon.  The type prefix of a map
literal is any valid [map signature]`TypeSystem#mapSignature`.  If the type prefix is omitted, then type inference is
used to determine the type of the keys and values using the same rules as list literals.  For example:

  [1:"one", 2:"two"]    // evaluates to Int:Str
  [1:"one", 2:null]     // evaluates to Int:Str?
  [1:"one", 2f:"two"]   // evaluates to Num:Str
  [1:"one", 2f:null]    // evaluates to Num:Str?
  [1:"one", 2f:0xabcd]  // evaluates to Num:Obj
  [0:["one"]]           // evaluates to Int:Str[]

The empty map is denoted using the special syntax '[:]' with or without a type prefix.

Note that maps may not be typed with a nullable key.  If you are using type inference, you might need to explicitly type
a map which will store null:

  [1:"one", 2:"two"]           // cannot store null values
  Int:Str?[1:"one", 2:"two"]   // can store null values

The type 'Int:Str?' is a map with 'Int' keys and 'Str?' values.  However the type '[Int:Str]?' is map of 'Int:Str' where
the map variable itself might be null.

If a map literal without an explicit type is used as a field initializer, then it infers its type from the field's
declared type:

  Str:File[] files := [:]   // initial value inferred as Str:File[:]

See [Appendix]`Appendix#typeInference` for the formal rules used for type inference of maps.

--------------------------------------------------------------------------------

(Documentation)
http://f...content-available-to-author-only...m.org/doc/docLang/TypeSystem#map

docLang
  TypeSystem
    9. Collections
      9.2. Map

Maps are a hashmap of key-value pair, very similar to an 'HashMap' or 'Hashtable' in Java or C#.  Maps have a
[literal]`Literals#map` syntax and a special [type signature]`#mapSignature` syntax.

--------------------------------------------------------------------------------

(Documentation)
http://f...content-available-to-author-only...m.org/doc/docLang/TypeSystem#mapSignature

docLang
  TypeSystem
    10. Generics
      10.2. Map Type Signatures

The [sys::Map]`sys::Map` class uses three generic parameters:

- 'K': type of key stored by the map
- 'V': type of value stored by the map
- 'M': type of the parameterized map

The parameterization syntax of 'Map' is designed to mimic the [map literal]`Literals#map` syntax:

  // format
  [K:V]          // formal signature
  K:V            // brackets are optional in most cases

  // examples
  [Str:User]     // map of Users keyed by Str
  Str:User       // same as above without optional brackets
  Uri:File?      // map of File? keyed by Uri
  [Uri:File]?    // map of Uri:File where the entire map variable might be null
  Str:File[]     // map of File[] keyed by Str
  [Str:File][]   // list of Str:File (brackets not optional)

The formal syntax for 'Map' parameterization is '[K:V]'.  Typically the brackets are optional, and by convention left
off.  But in some complicated type declarations you will need to use the brackets such as the '[Str:File][]' example
above.  Brackets are always used in APIs which return formal signatures.

--------------------------------------------------------------------------------

(Type)
http://f...content-available-to-author-only...m.org/doc/sys/Map

sys
  Map

Map is a hash map of key/value pairs.

See [examples]`examples::sys-maps`.

--------------------------------------------------------------------------------

(Method)
http://f...content-available-to-author-only...m.org/doc/compiler/ReflectFacet#map

compiler
  ReflectFacet
    map(compiler::ReflectNamespace ns, sys::Facet? f)

--------------------------------------------------------------------------------

(Method)
http://f...content-available-to-author-only...m.org/doc/sys/List#map

sys
  List
    map(|sys::V,sys::Int->sys::Obj?| c)

Create a new list which is the result of calling c for
every item in this list.  The new list is typed based on
the return type of c.  This method is readonly safe.

Example:
  list := [3, 4, 5]
  list.map |Int v->Int| { return v*2 } => [6, 8, 10]

--------------------------------------------------------------------------------

(Method)
http://f...content-available-to-author-only...m.org/doc/sys/Map#map

sys
  Map
    map(|sys::V,sys::K->sys::Obj?| c)

Create a new map with the same keys, but apply the specified
closure to generate new values.  The new mapped is typed based
on the return type of c.  If this map is `ordered` or
`caseInsensitive`, then the resulting map is too.  This method
is readonly safe.

Example:
  m := [2:2, 3:3, 4:4]
  x := m.map |Int v->Int| { return v*2 }
  x => [2:4, 3:6, 4:8]

--------------------------------------------------------------------------------

(Method)
http://f...content-available-to-author-only...m.org/doc/sys/Range#map

sys
  Range
    map(|sys::Int->sys::Obj?| c)

Create a new list which is the result of calling c for
every integer in the range.  The new list is typed based on
the return type of c.

Example:
  (10..15).map |i->Str| { i.toHex }  =>  Str[a, b, c, d, e, f]

--------------------------------------------------------------------------------

(Method)
http://f...content-available-to-author-only...m.org/doc/web/WebSession#map

web
  WebSession
    map()

Application name/value pairs which are persisted
between HTTP requests.  The values stored in this
map must be serializable.