build.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import subprocess
  2. from pathlib import Path
  3. import urllib.parse
  4. import os
  5. import sys
  6. SCRIPT_DIR = Path(__file__).parent.resolve()
  7. def generate_example(
  8. filename, title, strip_asserts=True, insert_run_link=True, footer=None
  9. ):
  10. path_in = SCRIPT_DIR.parent / "examples" / f"{filename}.nbt"
  11. path_out = SCRIPT_DIR / "src" / f"example-{filename}.md"
  12. print(path_in)
  13. print(path_out)
  14. code = []
  15. with open(path_in, "r", encoding="utf-8") as fin:
  16. for line in fin:
  17. if not (strip_asserts and "assert_eq" in line):
  18. code.append(line)
  19. url = f"https://numbat.dev/?q={urllib.parse.quote_plus(''.join(code))}"
  20. with open(path_out, "w", encoding="utf-8") as fout:
  21. fout.write("<!-- This file is autogenerated! Do not modify it -->\n")
  22. fout.write("\n")
  23. fout.write(f"# {title}\n")
  24. if insert_run_link:
  25. fout.write(
  26. f'<a href="{url}"><i class="fa fa-play"></i> Run this example</a>\n'
  27. )
  28. fout.write("\n")
  29. fout.write("``` numbat\n")
  30. fout.writelines(code)
  31. fout.write("```\n")
  32. if footer:
  33. fout.write("\n")
  34. fout.write(footer)
  35. fout.write("\n")
  36. def xkcd_footer(number, img_name):
  37. footer = '<p align="center" style="margin-top: 2em">'
  38. footer += f'<a href="https://xkcd.com/{number}/"><img src="https://imgs.xkcd.com/comics/{img_name}.png" alt="XKCD {number}" style="max-width: 100%"></a>'
  39. footer += f'<br>Source: <a href="https://xkcd.com/{number}/">https://xkcd.com/{number}/</a>'
  40. footer += "</p>"
  41. return footer
  42. generate_example("acidity", "Acidity")
  43. generate_example("barometric_formula", "Barometric formula")
  44. generate_example("body_mass_index", "Body mass index")
  45. generate_example("factorial", "Factorial", strip_asserts=False)
  46. generate_example("medication_dosage", "Medication dosage")
  47. generate_example("molarity", "Molarity")
  48. generate_example("musical_note_frequency", "Musical note frequency")
  49. generate_example("paper_size", "Paper sizes")
  50. generate_example("pipe_flow_rate", "Flow rate in a pipe")
  51. generate_example("population_growth", "Population growth")
  52. generate_example("recipe", "Recipe")
  53. generate_example("voyager", "Voyager")
  54. generate_example(
  55. "xkcd_681",
  56. "XKCD 681",
  57. footer=xkcd_footer(681, "gravity_wells"),
  58. )
  59. generate_example(
  60. "xkcd_687", "XKCD 687", footer=xkcd_footer(687, "dimensional_analysis")
  61. )
  62. generate_example("xkcd_2585", "XKCD 2585", footer=xkcd_footer(2585, "rounding"))
  63. generate_example(
  64. "xkcd_2812", "XKCD 2812", footer=xkcd_footer(2812, "solar_panel_placement")
  65. )
  66. generate_example(
  67. "numbat_syntax", "Syntax overview", strip_asserts=False, insert_run_link=False
  68. )
  69. path_units = SCRIPT_DIR / "src" / "list-units.md"
  70. with open(path_units, "w", encoding="utf-8") as f:
  71. print("Generating list of units...", flush=True)
  72. subprocess.run(
  73. ["cargo", "run", "--release", "--quiet", "--example=inspect", "units"],
  74. stdout=f,
  75. text=True,
  76. )
  77. def list_of_functions(file_name, document):
  78. path = SCRIPT_DIR / "src" / f"list-functions-{file_name}.md"
  79. with open(path, "w", encoding="utf-8") as f:
  80. print(f"# {document['title']}\n", file=f, flush=True)
  81. if introduction := document.get("introduction"):
  82. print(introduction + "\n", file=f, flush=True)
  83. sections = document["sections"]
  84. if len(sections) >= 3:
  85. links = []
  86. for section in sections:
  87. if title := section.get("title"):
  88. link = title.lower().replace(" ", "-").replace(",", "")
  89. links.append(f"[{title}](#{link})")
  90. print(f"{' · '.join(links)}\n", file=f, flush=True)
  91. for section in sections:
  92. modules = section["modules"]
  93. if title := section.get("title"):
  94. print(f"## {title}\n", file=f, flush=True)
  95. print(f"Defined in: `{'`, `'.join(modules)}`\n", file=f, flush=True)
  96. for module in modules:
  97. print(
  98. f"Generating list of functions for module '{module}'...", flush=True
  99. )
  100. env = os.environ.copy()
  101. env["TZ"] = "UTC"
  102. try:
  103. subprocess.run(
  104. [
  105. "cargo",
  106. "run",
  107. "--release",
  108. "--quiet",
  109. "--example=inspect",
  110. "--",
  111. "functions",
  112. module,
  113. ],
  114. stdout=f,
  115. text=True,
  116. env=env,
  117. check=True
  118. )
  119. except subprocess.CalledProcessError as e:
  120. sys.exit(e.returncode)
  121. list_of_functions(
  122. "math",
  123. {
  124. "title": "Mathematical functions",
  125. "sections": [
  126. {
  127. "title": "Basics",
  128. "modules": ["core::functions"],
  129. },
  130. {
  131. "title": "Transcendental functions",
  132. "modules": ["math::transcendental"],
  133. },
  134. {
  135. "title": "Trigonometry",
  136. "modules": ["math::trigonometry"],
  137. },
  138. {
  139. "title": "Statistics",
  140. "modules": ["math::statistics"],
  141. },
  142. {
  143. "title": "Combinatorics",
  144. "modules": ["math::combinatorics"],
  145. },
  146. {
  147. "title": "Random sampling, distributions",
  148. "modules": ["core::random", "math::distributions"],
  149. },
  150. {
  151. "title": "Number theory",
  152. "modules": ["math::number_theory"],
  153. },
  154. {
  155. "title": "Numerical methods",
  156. "modules": [
  157. "numerics::diff",
  158. "numerics::solve",
  159. "numerics::fixed_point",
  160. ],
  161. },
  162. {
  163. "title": "Percentage calculations",
  164. "modules": ["math::percentage_calculations"],
  165. },
  166. {
  167. "title": "Geometry",
  168. "modules": ["math::geometry"],
  169. },
  170. {
  171. "title": "Algebra",
  172. "modules": ["extra::algebra"],
  173. },
  174. {
  175. "title": "Trigonometry (extra)",
  176. "modules": ["math::trigonometry_extra"],
  177. },
  178. ],
  179. },
  180. )
  181. list_of_functions(
  182. "lists",
  183. {
  184. "title": "List-related functions",
  185. "sections": [
  186. {
  187. "modules": ["core::lists"],
  188. },
  189. ],
  190. },
  191. )
  192. list_of_functions(
  193. "strings",
  194. {
  195. "title": "String-related functions",
  196. "sections": [
  197. {
  198. "modules": ["core::strings"],
  199. },
  200. ],
  201. },
  202. )
  203. list_of_functions(
  204. "datetime",
  205. {
  206. "title": "Date and time",
  207. "introduction": "See [this page](./date-and-time.md) for a general introduction to date and time handling in Numbat.",
  208. "sections": [
  209. {
  210. "modules": ["datetime::functions", "datetime::human"],
  211. },
  212. ],
  213. },
  214. )
  215. list_of_functions(
  216. "other",
  217. {
  218. "title": "Other functions",
  219. "sections": [
  220. {
  221. "title": "Error handling",
  222. "modules": ["core::error"],
  223. },
  224. {
  225. "title": "Floating point",
  226. "modules": ["core::numbers"],
  227. },
  228. {
  229. "title": "Quantities",
  230. "modules": ["core::quantities"],
  231. },
  232. {
  233. "title": "Chemical elements",
  234. "modules": ["chemistry::elements"],
  235. },
  236. {
  237. "title": "Mixed unit conversion",
  238. "modules": ["units::mixed"],
  239. },
  240. {
  241. "title": "Temperature conversion",
  242. "modules": ["physics::temperature_conversion"],
  243. },
  244. {
  245. "title": "Color format conversion",
  246. "modules": ["extra::color"],
  247. },
  248. ],
  249. },
  250. )
  251. subprocess.run(["mdbook", "build"], text=True)