Jade и Pug — шаблоны, API и экранирование

Jade — прежнее название шаблонизатора, который развивается как Pug. Ниже разобраны теги, атрибуты, экранирование, условия, циклы, include и mixin. Отдельные примеры старого Jade сохранены для чтения существующих шаблонов; рядом с отличающимися API приведён действующий вариант Pug.

Pug работает в Node.js и компилирует шаблон в функцию, возвращающую HTML. Компиляция шаблона и подстановка пользовательских данных — разные операции. Не компилируйте строку шаблона из непроверенного ввода.

Возможности

Основные конструкции шаблонизатора:

  • Вложенность тегов задаётся отступами.
  • Выражения = и #{...} экранируют текст; != и !{...} выводят HTML без экранирования.
  • include, наследование и mixin позволяют переиспользовать разметку.
  • Шаблон можно скомпилировать заранее, а затем передавать в полученную функцию только данные.
  • Pug интегрируется с Express через поддерживаемый движок представлений.

Старые списки фильтров Sass, Less, Markdown и CoffeeScript не означают, что все эти компиляторы встроены в текущий Pug. Проверяйте нужный фильтр и его зависимости отдельно; не устанавливайте заброшенные плагины по историческому каталогу.

Реализации

Существуют отдельные шаблонизаторы и реализации с похожим синтаксисом в разных языках. Совпадение названия или отступов не гарантирует одинаковый API. Это руководство использует Node.js и Pug; сторонние PHP/Scala/Ruby-порты не нужны для выполнения примеров.

Установка

Установите пакет в отдельном учебном проекте:

npm init -y
npm install pug

Сохраните шаблон в файл hello.pug, например p Привет, #{name}. Файл принадлежит разработчику; имя и другие значения передаются данными, а не конкатенацией строк исходника шаблона. У старого проекта с пакетом jade переход проверяйте по официальной миграции Pug.

Поддержка браузерами

Для браузера шаблон можно предварительно скомпилировать через pug.compileClient. Он возвращает JavaScript-код. По умолчанию нужные runtime-функции можно включить в результат; не копируйте старые команды make jade.js и расположение jade/runtime.js в новый проект.

const pug = require('pug');
const source = pug.compileClient('p Привет, #{name}', {
  name: 'helloTemplate',
  inlineRuntimeFunctions: true,
  compileDebug: false
});

source нужно сохранить в файл и включить в сборку, а не выполнить как произвольный ввод посетителя. Альтернативно сервер сам возвращает готовый HTML. API и опции перечислены в Pug API.

Утилиты сборки, поддерживающие Jade

От переводчика - спустя год после публикации данного перевода:

Это была теория, теперь немного практики. В своей статье об утилите сборки browserify я привожу пример компиляции Jade-шаблонов как CommonJS-модулей, которые можно подключать через require(). Считаю такой способ предпочтительным, особенно в случае если browserify уже используется в вашем проекте и такой стиль программирования близок вам идеологически.

Публичный API

Три операции различаются входом и моментом выполнения:

const pug = require('pug');

// Строка доверенного шаблона → HTML
const html = pug.render('p Привет, #{name}', {name: 'Анна'});

// Файл доверенного шаблона → HTML
const page = pug.renderFile('hello.pug', {name: 'Анна'});

// Шаблон → функция, которую можно повторно вызывать с данными
const template = pug.compile('p Привет, #{name}');
const first = template({name: 'Анна'});
const second = template({name: 'Борис'});

Опция filename нужна для относительных include и диагностики. compileDebug управляет отладочной информацией. Не передавайте весь объект запроса как объект опций компилятора: выбирайте допустимые поля явно. Для повторного использования достаточно передать объект данных в скомпилированную функцию; старый fn.call(scope, locals) не нужен как инструкция современного API.

Опции

В старом Jade параметры компилятора и данные шаблона часто стояли рядом. При переносе сначала разделите настройки, которые выбирает разработчик, и значения, поступающие при рендеринге. В таблице сохранены все восемь параметров исходного руководства; одинаковое назначение не означает одинаковое имя или контракт в Pug.

Параметр старого Jade Для чего он использовался Как разбирать его при работе с Pug
scope Задавал значение this для шаблона. В текущей публичной справке Pug такой параметр не описан. Передавайте данные явно в полученную функцию fn(locals), а не рассчитывайте на старое управление контекстом.
self Помещал локальные переменные в пространство имён self; по умолчанию выключен. Документирован и в Pug. При {self: true} обращайтесь к данным как self.name, а не name. Это изменение доступа к данным, а не проверка их безопасности.
locals Объект значений, доступных шаблону. У скомпилированной функции данные передаются аргументом: fn({name: 'Анна'}). У render и renderFile объект опций одновременно содержит данные. Не предполагайте, что обёртка {locals: ...} нужна любому методу.
filename Имя файла для сообщений об ошибке и разрешения include. Документированное имя компилируемого файла; требуется для относительных include и extends. При компиляции строки задайте его сами, а при renderFile путь известен из аргумента. Не позволяйте посетителю выбирать произвольный путь.
debug Печатал токены и тело сгенерированной функции. В Pug выводит диагностическую информацию о компиляции в stdout. Это не режим показа ошибки посетителю; такой вывод может раскрыть исходный шаблон в журнале.
compiler Позволял заменить компилятор Jade. Старый контракт подмены компилятора не описан в текущей публичной справке Pug. Не переносите этот объект как совместимый параметр; используйте документированные pug.compile и pug.compileClient, а расширения проверяйте отдельно.
compileDebug При false убирал отладочные сведения из скомпилированного шаблона. В Pug управляет информацией для диагностики ошибок шаблона. Отличается от печати через debug; учитывайте различия режима Express production и обычной компиляции. Сам флаг не заменяет безопасную обработку ошибок приложения.
inline Управлял включением вспомогательных функций Jade в результат. Не переносите это имя молча. В Pug документирован inlineRuntimeFunctions: для семейства compileClient он по умолчанию включён, для других вариантов компиляции и рендеринга — выключен. Внешний runtime должен соответствовать компилятору.

Например, явное пространство имён self можно проверить отдельно от остальных параметров:

const pug = require('pug');
const template = pug.compile('p= self.name', {self: true});
const html = template({name: '<Анна>'});
// <p>&lt;Анна&gt;</p>

Здесь self меняет способ обращения к значению, а = по-прежнему экранирует его. Не передавайте в опции компиляции целиком req.query или req.body: выберите допустимые поля данных и передайте их отдельно. Контракты методов и текущие параметры приведены в официальной справке Pug.

Синтаксис

Окончания строк

CRLF и CR преобразуются в LF перед парсингом.

Теги

Тег обозначается просто начальным словом:

html

это, например, преобразуется в <html></html>.

Теги могут иметь id-ы:

div#container

отрендерится в <div id="container"></div>

Классы:

div.user-details

рендерится в <div class="user-details"></div>

несколько класов? а также id? запросто:

div#foo.bar.baz

рендерится в <div id="foo" class="bar baz"></div>

Конечно div div div это фигня, поэтому можно просто написать так:

#foo
.bar

Это "синтаксический сахар" для сокращенной записи тега div. На выходе имеем:

<div id="foo"></div><div class="bar"></div>

Текст тега

Просто поместите некоторый контент после тега:

p wahoo!

рендерится в <p>wahoo!</p>.

Большие куски текста записываются так:

p
  | foo bar baz
  | rawr rawr
  | super cool
  | go jade go

рендерится в <p>foo bar baz rawr.....</p>.

Интерполяция: любой тип текста имеет возможность интерполяции.

Если передать { name: 'tj', email: 'tj@vision-media.ca' } в render()

можно сделать следующее:

#user #{name} &lt;#{email}&gt;

на выходе будет: <div id="user">tj &lt;tj@vision-media.ca&gt;</div>

Если надо отобразить именно последовательность символов #{}, то можно заэкранировать ее обратной косой чертой:

p \#{something}

превратится в: <p>#{something}</p>

Можно также использовать незаэкранированный вариант !{html}, так что следующее

Не передавайте сюда комментарий посетителя или другой недоверенный текст: он станет HTML и может выполнить скрипт. Для обычного текста используйте #{html}. Если продукту нужен пользовательский HTML, обработайте его подходящим sanitizer с явной политикой разрешённых элементов.

превратится в литеральный тег script:

- var html = "<script></script>"
| !{html}

Вложенные теги, содержащие текст, могут использовать как текстовый блок:

label
  | Username:
  input(name='user[name]')

так и inline-текст:

label Username:
  input(name='user[name]')

Теги, которые допускают только текст, например, script, style, и textarea, не нуждаются в начальном символе |, например:

  html
    head
      title Example
      script
        if (foo) {
          bar();
        } else {
          baz();
        }

Также альтернативно мы можем использовать точку (.), чтобы обозначить текстовый блок:

  p.
    foo asdf
    asdf
     asdfasdfaf
     asdf
    asd.

На выходе будет:

    <p>foo asdf
    asdf
      asdfasdfaf
      asdf
    asd
    .
    </p>

Однако это отличается от точки, перед которой идет пробел. Такая точка воспринимается парсером Jade просто как литерал точки:

p .

на выходе даст:

<p>.</p>

Следует заметить, что текстовые блоки должны быть дважды заэкранированы. Например, если нужен следующий HTML:

<p>foo\bar</p>

, следует использовать:

p.
  foo\\bar

Комментарии

Однострочные комментарии выглядят просто как комментарии JavaScript, то есть "//", и должны помещаться на своей строке:

// just some paragraphs
p foo
p bar

на выходе даст:

<!-- just some paragraphs -->
<p>foo</p>
<p>bar</p>

Jade также поддерживает небуферизируемые комментарии, просто добавьте дефис:

//- will not output within markup
p foo
p bar

на выходе имеем:

<p>foo</p>
<p>bar</p>

Блочные комментарии

Блочные комментарии задаются отступами:

  body
    //
      #content
        h1 Example

на выходе:

<body>
  <!--
  <div id="content">
    <h1>Example</h1>
  </div>
  -->
</body>

Jade также поддерживает условные комментарии:

body
  //if IE
    a(href='http://www.mozilla.com/en-US/firefox/') Get Firefox

на выходе:

<body>
  <!--[if IE]>
    <a href="http://www.mozilla.com/en-US/firefox/">Get Firefox</a>
  <![endif]-->
</body>

Вложенность

Jade поддерживает вложенность тегов, выполняемую естественным способом:

ul
  li.first
    a(href='#') foo
  li
    a(href='#') bar
  li.last
    a(href='#') baz

Раскрытие блока

Раскрытие блока позволяет писать компактные однострочные конструкции вложенных тегов,

следующий пример эквивалентен примеру вложенности выше.

  ul
    li.first: a(href='#') foo
    li: a(href='#') bar
    li.last: a(href='#') baz

Атрибуты

Jade в настоящее время поддерживает разделители атрибутов '(' и ')'.

a(href='/login', title='View login page') Login

Когда значение undefined или null, атрибут не добавляется,

и тогда следующий пример не даст в результате 'something="null"':

div(something=null)

Булевы атрибуты также поддерживаются:

input(type="checkbox", checked)

Булев атрибут с кодом будет рендериться только если он равен true:

input(type="checkbox", checked=someValue)

Атрибуты можно разбивать на несколько строк:

input(type='checkbox',
  name='agreement',
  checked)

При переносе запятую можно опускать:

input(type='checkbox'
  name='agreement'
  checked)

Можно манипулировать отступами:

input(
  type='checkbox'
  name='agreement'
  checked)

Двоеточия учитываются:

rss(xmlns:atom="atom")

Например у нас есть переменная user = { id: 12, name: 'tobi' } и нам надо создать тег гиперссылки с href, указывающим на "/user/12".

Можно конечно использовать обычную JS-конкатенацию:

a(href='/user/' + user.id)= user.name

, а можно - Jade-овскую интерполяцию, которая была добавлена по просьбам трудящихся рубистов и CoffeeScript-разработчиков:

a(href='/user/#{user.id}')= user.name

Атрибут class - особенный, в него можно передавать массив, например, bodyClasses = ['user', 'authenticated']:

body(class=bodyClasses)

Doctypes

Для HTML5 используйте doctype html. Следующий список !!! и объектов jade.doctypes описывает старый Jade и не является API текущего пакета Pug. У нового шаблона достаточно:

doctype html
html(lang='ru')
  head
    title Пример
  body
    p Привет

Актуальные варианты: Pug Doctype.

Чтобы добавить doctype, просто используйте !!!, или doctype, с необязательным параметром:

!!!

Выдаст transitional doctype, однако:

!!! 5

или

!!! html

или

doctype html

doctypes нечувствительны к регистру, так что следующее эквивалентно:

doctype Basic
doctype basic

Это выдаст html 5 doctype.

Вот doctypes определенные по умолчанию (можно легко дополнить своими):

var doctypes = exports.doctypes = {
    '5': '<!DOCTYPE html>',
    'xml': '<?xml version="1.0" encoding="utf-8" ?>',
    'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
    'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
    'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
    'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
    '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
    'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
    'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
};

Чтобы изменить значения по умолчанию:

jade.doctypes.default = 'whatever you want';

Фильтры

Фильтры — отдельные преобразования с собственными зависимостями. Ниже показан синтаксис и исторический AST-пример Jade. Для текущего Pug сверяйте контракт фильтров; conditionals: без двоеточия в начале не следует считать встроенной возможностью.

Фильтры пишутся с префиксом :, например: :markdown и им передается на обработку последующий блок текста.

См. возможности вверху документа - там есть список доступных фильтров.

body
  :markdown
    Woah! jade _and_ markdown, very **cool**
    we can even link to [stuff](http://google.com)

Рендерится в:

<body>
	<p>Woah! jade <em>and</em> markdown, very
	<strong>cool</strong> we can even link to
	<a href="http://google.com">stuff</a>
	</p>
</body>

Фильтры также могут манипулировать AST-деревом.

Например если надо компилировать условные конструкции прямо в Jade, это можно сделать с помощью фильтра conditionals. Обычно фильтры оперируют текстом, однако если фильтру передан обыкновенный блок, он может делать что угодно с тегами, вложенными в него:

body
  conditionals:
    if role == 'admin'
      p You are amazing
    else
      p Not so amazing

Обратите внимание, что мы не ставим префикс "-" для наших блоков кода.

Исторические примеры conditionals.js и model.js относились к внутреннему AST старого Jade. Для действующего расширения используйте публичный контракт Pug, а не плавающие ссылки master на удалённые файлы.

Код

Jade поддерживает три типа исполняемого кода. Первый - это когда ставится префикс -, буферизации не происходит:

- var foo = 'bar';

Этим можно пользоваться для условных конструкций, циклов:

- for (var key in obj)
  p= obj[key]

В соответствии с техникой буферизации Jade, следующее также валидно:

- if (foo)
  ul
    li yay
    li foo
    li worked
- else
  p oh no! didnt work

Да, можно итерировать явно:

- if (items.length)
  ul
    - items.forEach(function(item){
      li= item
    - })

Все что угодно!

Теперь получим заэкранированный буферизированный код, который буферизирует возвращаемое значение, с префиксом =:

- var foo = 'bar'
= foo
h1= foo

Что дает на выходе bar<h1>bar</h1>. Код, буферизированый с помощью =, по умолчанию экранируется.

Из соображений безопасности.

Однако можно использовать неэкранируемый вариант:

!= отключает экранирование. Этот пример предназначен только для заранее доверенного HTML; для значения из формы используйте p= value. Подробнее: экранируемая и неэкранируемая интерполяция.

p!= aVarContainingMoreHTML

В текущем Pug цикл пишется each value, key in object, без начального дефиса. Далее показаны массив, индекс и пары ключ–значение. сделано одно исключение - each. Вот в такой форме:

each VAL[, KEY] in OBJ

Пример итерации по массиву:

- var items = ["one", "two", "three"]
each item in items
  li= item

на выходе имеем:

<li>one</li>
<li>two</li>
<li>three</li>

Итерация по массиву с индексами:

- var items = ["one", "two", "three"]
each item, i in items
  li #{item}: #{i}

на выходе получаем:

<li>one: 0</li>
<li>two: 1</li>
<li>three: 2</li>

Итерация по ключам и значениям объекта:

- var obj = { foo: 'bar' }
each val, key in obj
  li #{key}: #{val}

дает на выходе <li>foo: bar</li>

Также можно вкладывать структуры одна в другую!

each user in users
  each role in user.roles
    li= role

Когда свойство undefined, Jade выдаст пустую строку. Например:

textarea= user.signature

хотя undefined должно рисоваться в HTML как "undefined",

последние версии Jade просто выдадут:

<textarea></textarea>

Инклюды

Имена include задаются кодом шаблона. Не подставляйте туда путь из запроса. В Pug используйте расширение .pug и проверяйте разрешение относительных путей. Дерево файлов ниже поясняет ту же идею на исторических именах .jade.

Инклюды позволяют статически подключать куски кода шаблонов Jade,

находящиеся в отдельных файлах. Классический пример

это подключение хедера и футера. Например есть следующая

структура папок:

 ./layout.jade
 ./includes/
   ./head.jade
   ./tail.jade

и следующий layout.jade:

  html
    include includes/head  
    body
      h1 My Site
      p Welcome to my super amazing site.
      include includes/foot

оба инклюда includes/head и includes/foot читаются относительно опции filename, переданной в layout.jade, где должен быть абсолютный путь к файлу данного шаблона, однако Express и метод renderFile() делают это за вас. Include потом парсит эти файлы, и вставляет получившееся AST, чтобы отрендерить то, что ожидается:

<html>
  <head>
	<title>My Site</title>
	<script src="/javascripts/jquery.js">
	</script><script src="/javascripts/app.js"></script>
  </head>
  
  <body>
	<h1>My Site</h1>
	<p>Welcome to my super lame site.</p>
	<div id="footer">
	  <p>Copyright>(c) foobar</p>
	</div>
  </body>

</html>

Примеси (Mixin)

Для текущего Pug определение остаётся mixin name(...), а вызов обозначается +name(...). В старых примерах Jade встречается вызов через mixin; его необходимо адаптировать. См. Pug mixin.

Примеси конвертируются в обычные JavaScript функции внутри скомпилированного Jade-ом шаблона. Примеси могут принимать аргументы, впрочем необязательные:

  mixin list
    ul
      li foo
      li bar
      li baz

Вызов примеси без аргументов выглядит похоже, но под ней не пишется блок:

  h2 Groceries
  mixin list

Примесь может принимать несколько аргументов, аргументы - это обычные javascript выражения, так например следующее:

  mixin pets(pets)
    ul.pets
      each pet in pets
        li= pet

  mixin profile(user)
    .user
      h2= user.name
      mixin pets(user.pets)

Породит нечто подобное следующему html:

<div class="user">
  <h2>tj</h2>
  <ul class="pets">
	<li>tobi</li>
	<li>loki</li>
	<li>jane</li>
	<li>manny</li>
  </ul>
</div>

Генерируемый код

Следующие with(locals), jade.attrs и __.lineno — пример результата старого компилятора Jade. Это иллюстрация механизма, а не стабильный публичный интерфейс. Не редактируйте такой вывод вручную и не рассчитывайте на эти имена в Pug; компилируйте доверенный исходный шаблон через публичный API.

Например у нас есть такой Jade:

- var title = 'yay'
h1.title #{title}
p Just an example

Когда опция compileDebug не указана явно как false, Jade скомпилирует функцию, оснащенную отладочной информацией вида __.lineno = n;, которая в случае исключения передается в rethrow(), который конструирует читабельное сообщение об ошибке.

function anonymous(locals) {
  var __ = { lineno: 1, input: "- var title = 'yay'\nh1.title #{title}\np Just an example", filename: "testing/test.js" };
  var rethrow = jade.rethrow;
  try {
	var attrs = jade.attrs, escape = jade.escape;
	var buf = [];
	with (locals || {}) {
	  var interp;
	  __.lineno = 1;
	   var title = 'yay'
	  __.lineno = 2;
	  buf.push('<h1');
	  buf.push(attrs({ "class": ('title') }));
	  buf.push('>');
	  buf.push('' + escape((interp = title) == null ? '' : interp) + '');
	  buf.push('</h1>');
	  __.lineno = 3;
	  buf.push('<p>');
	  buf.push('Just an example');
	  buf.push('</p>');
	}
	return buf.join("");
  } catch (err) {
	rethrow(err, __.input, __.filename, __.lineno);
  }
}

__ Когда опция compileDebug явно задана как false, эта отладочная информация опускается, что очень важно для легковесных клиентских шаблонов.

Комбинируя Jade-овские опции и ./runtime.js из github-репозитария, можно сделать скомпилированной функции toString() и избежать подключения целого компилятора Jade в клиенте, что повысит производительность, и уменьшит трафик JS-скриптов.

function anonymous(locals) {
  var attrs = jade.attrs, escape = jade.escape;
  var buf = [];
  with (locals || {}) {
	var interp;
	var title = 'yay'
	buf.push('<h1');
	buf.push(attrs({ "class": ('title') }));
	buf.push('>');
	buf.push('' + escape((interp = title) == null ? '' : interp) + '');
	buf.push('</h1>');
	buf.push('<p>');
	buf.push('Just an example');
	buf.push('</p>');
  }
  return buf.join("");
}

bin/jade

Старые команды jade ... требовали CLI старого Jade. Чтобы явно работать с текущим Node.js API, создайте небольшой render.cjs:

const fs = require('node:fs');
const pug = require('pug');
const html = pug.renderFile('hello.pug', {name: 'Анна'});
fs.writeFileSync('hello.html', html);

Запустите node render.cjs и откройте hello.html. Проверка экранирования: передайте вместо имени строку <script>alert(1)</script>. При #{name} она должна отображаться текстом, а не исполняться. Намеренно ошибочный отступ должен привести к ошибке компиляции. Командный интерфейс Pug устанавливается отдельно от пакета pug, если он нужен вашей сборке.

Лицензия

(The MIT License)

Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca>

Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно использовать Программное Обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, добавление, публикацию, распространение, сублицензирование и/или продажу копий Программного Обеспечения, также как и лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий:

Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения.

ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.

Заметки и перевод: @jsmarkus