document.typ 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. // ==========================================
  2. // DOMAIN: ДОКУМЕНТ И КОНТРАКТ ПРОФИЛЯ
  3. // ==========================================
  4. #let fail(scope, message) = panic("Scientia / " + scope + ": " + message)
  5. #let ensure-dictionary(value, scope) = {
  6. if type(value) != dictionary {
  7. fail(scope, "ожидался dictionary, получено " + repr(type(value)))
  8. }
  9. value
  10. }
  11. #let merge-known(defaults, overrides, scope: "options", allow-extra: false) = {
  12. let defaults = ensure-dictionary(defaults, scope + ".defaults")
  13. let overrides = ensure-dictionary(overrides, scope)
  14. let result = defaults
  15. for (key, value) in overrides {
  16. if not allow-extra and key not in defaults {
  17. fail(scope, "неизвестный параметр " + repr(key))
  18. }
  19. result.insert(key, value)
  20. }
  21. result
  22. }
  23. #let render-options(options: (:)) = {
  24. let resolved = merge-known(
  25. (
  26. mode: "final",
  27. watermark: none,
  28. media-policy: "placeholder",
  29. diagnostics: true,
  30. toc: true,
  31. ),
  32. options,
  33. scope: "render-options",
  34. )
  35. if resolved.mode not in ("final", "draft", "clean-copy") {
  36. fail("render-options.mode", "допустимы final, draft, clean-copy")
  37. }
  38. if resolved.at("media-policy") not in ("hide", "placeholder", "reserve-space") {
  39. fail("render-options.media-policy", "допустимы hide, placeholder, reserve-space")
  40. }
  41. resolved
  42. }
  43. #let document-profile(
  44. id,
  45. render,
  46. metadata: (:),
  47. normalize: value => value,
  48. validate: value => value,
  49. ) = {
  50. if type(id) != str or id.trim() == "" {
  51. fail("document-profile.id", "нужна непустая строка")
  52. }
  53. if type(metadata) != dictionary {
  54. fail("document-profile.metadata", "ожидался dictionary")
  55. }
  56. if type(normalize) != function {
  57. fail("document-profile.normalize", "ожидалась function")
  58. }
  59. if type(validate) != function {
  60. fail("document-profile.validate", "ожидалась function")
  61. }
  62. if type(render) != function {
  63. fail("document-profile.render", "ожидалась function")
  64. }
  65. (
  66. kind: "document-profile",
  67. id: id,
  68. metadata: metadata,
  69. normalize: normalize,
  70. validate: validate,
  71. render: render,
  72. )
  73. }
  74. #let validate-profile-contract(profile) = {
  75. let profile = ensure-dictionary(profile, "profile")
  76. if profile.at("kind", default: none) != "document-profile" {
  77. fail("profile.kind", "ожидался document-profile")
  78. }
  79. for key in ("id", "metadata", "normalize", "validate", "render") {
  80. if key not in profile {
  81. fail("profile", "отсутствует обязательное поле " + repr(key))
  82. }
  83. }
  84. if type(profile.normalize) != function or type(profile.validate) != function or type(profile.render) != function {
  85. fail("profile", "normalize, validate и render должны быть functions")
  86. }
  87. profile
  88. }
  89. #let document-context(
  90. company,
  91. profile,
  92. metadata,
  93. options,
  94. parties: (),
  95. attachments: (),
  96. bibliographies: (),
  97. ) = {
  98. (
  99. kind: "document-context",
  100. company: company,
  101. profile-id: profile.id,
  102. metadata: ensure-dictionary(metadata, "document-context.metadata"),
  103. options: render-options(options: options),
  104. parties: parties,
  105. attachments: attachments,
  106. bibliographies: bibliographies,
  107. )
  108. }