document.typ 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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. ),
  31. options,
  32. scope: "render-options",
  33. )
  34. if resolved.mode not in ("final", "draft", "clean-copy") {
  35. fail("render-options.mode", "допустимы final, draft, clean-copy")
  36. }
  37. if resolved.at("media-policy") not in ("hide", "placeholder", "reserve-space") {
  38. fail("render-options.media-policy", "допустимы hide, placeholder, reserve-space")
  39. }
  40. resolved
  41. }
  42. #let document-profile(
  43. id,
  44. render,
  45. metadata: (:),
  46. normalize: value => value,
  47. validate: value => value,
  48. ) = {
  49. if type(id) != str or id.trim() == "" {
  50. fail("document-profile.id", "нужна непустая строка")
  51. }
  52. if type(metadata) != dictionary {
  53. fail("document-profile.metadata", "ожидался dictionary")
  54. }
  55. if type(normalize) != function {
  56. fail("document-profile.normalize", "ожидалась function")
  57. }
  58. if type(validate) != function {
  59. fail("document-profile.validate", "ожидалась function")
  60. }
  61. if type(render) != function {
  62. fail("document-profile.render", "ожидалась function")
  63. }
  64. (
  65. kind: "document-profile",
  66. id: id,
  67. metadata: metadata,
  68. normalize: normalize,
  69. validate: validate,
  70. render: render,
  71. )
  72. }
  73. #let validate-profile-contract(profile) = {
  74. let profile = ensure-dictionary(profile, "profile")
  75. if profile.at("kind", default: none) != "document-profile" {
  76. fail("profile.kind", "ожидался document-profile")
  77. }
  78. for key in ("id", "metadata", "normalize", "validate", "render") {
  79. if key not in profile {
  80. fail("profile", "отсутствует обязательное поле " + repr(key))
  81. }
  82. }
  83. if type(profile.normalize) != function or type(profile.validate) != function or type(profile.render) != function {
  84. fail("profile", "normalize, validate и render должны быть functions")
  85. }
  86. profile
  87. }
  88. #let document-context(
  89. company,
  90. profile,
  91. metadata,
  92. options,
  93. parties: (),
  94. attachments: (),
  95. bibliographies: (),
  96. ) = {
  97. (
  98. kind: "document-context",
  99. company: company,
  100. profile-id: profile.id,
  101. metadata: ensure-dictionary(metadata, "document-context.metadata"),
  102. options: render-options(options: options),
  103. parties: parties,
  104. attachments: attachments,
  105. bibliographies: bibliographies,
  106. )
  107. }