![](https://habrastorage.org/getpro/habr/upload_files/541/c15/65e/541c1565e6f3c6edad93f4f23d673869.png)
Наконец-то я "раздобыл" немного свободного времени, а значит пришла пора продолжить серию туториалов по XWiki.
После публикации одной из моих статей MaxK82 спросил у меня, можно ли как-то в XWiki подключить документацию из git репозитория, так чтобы наладить её версионирование.
К сожалению эта статья не ответит на его вопрос, но возможно укажет направление, в котором стоит "копать".
Поэтому сегодня мы с вами:
Cразу дам дисклеймер - я техпис, а не программист. Поэтому не стоит ожидать в стать хороших примеров кода и удачных решений. Это скорее материал для других технических писателей или может быть docops, которым выпадет честь работать с XWiki.
Уделим пару минут очевидному:
Для туториала нам понадобится аккаунт в Gitlab (не важно коммерческий или на вашем сервере).
Нужно будет получить access token к аккаунту.
Нужен будет репозиторий, я использую "Learn GitLab" (не помню откуда он у меня взялся). Но вам подойдет любой репозиторий в котором есть Readme.md (или другой markdown файл). Логика будет похожей.
В своей статье я использовал docker версию XWiki 13.10.5 со Standart Flavor, но работать будет и в XWiki 12.X.
-
Нам понадобится установить расширение GitAPI.
Ну и самое главное, нам нужно будет установить расширение поддержки синтаксиса Markdown.
![](https://habrastorage.org/getpro/habr/upload_files/28c/2b8/71c/28c2b871cd51a27090dcc02132e517e8.png)
.
Вообще у Xwiki есть разные варианты интеграции с git, например импорт с GitHub wiki или вот просто подключение к GitHub через его API.
Возможно Вам удастся найти готовые примеры и решения.
Сразу скажу, что в этот раз мы сделаем очень топорный макрос, его прообраз мне был нужен для того чтобы коллегам не пришлось добавлять домен с XWiki в настройки CORS нашего GitLab.
Прошу вас рассматривайте этот туториал скорее как демонстрацию, а не как четкое руководство к действию, потому что клонировать целый репозиторий на сервер XWiki ради 1 markdown файла может быть не лучшей идеей.
Пару слов о том, что такое макрос.
Макрос в Xwiki - это набор инструкций, который может быть включен в контент страницы. Если вы использовали Conflunce, то вам это знакомо.
Создать свой макрос в XWiki очень просто, для этого надо следовать инструкции.
Для начала создадим новую страницу, можно прямо в корне. Название страницы не критично, контент можно не размещать.
![Создание пустой страницы для макроса Создание пустой страницы для макроса](https://habrastorage.org/getpro/habr/upload_files/26f/1db/08e/26f1db08ef8fa41c05ac863e82eb2ed5.png)
Но если вы возьметесь за дело всерьез, то я бы рекомендовал размещать макросы в каком-то одном месте (например, внутри страницы "Macros").
Далее перейдите в редактор объектов. Можно просто нажать латинскую клавишу "O" на странице после её создания (если чудо не произошло, посмотрите как включить продвинутый режим редактора в одной из прошлых статей).
Страница станет макросом, как только мы добавим ей объект WikiMacroClass
![](https://habrastorage.org/getpro/habr/upload_files/9aa/716/186/9aa71618669491df0f848def2ae26fa0.png)
Это по сути и есть сам макрос, но прежде, чем перейти к его детальному описанию, добавим еще несколько объектов с переменными для макросаWikiMacroParameterClass
на страницу.
![](https://habrastorage.org/getpro/habr/upload_files/9a5/532/5e6/9a55325e6f2004a8854c8195a5a8a997.png)
Создайте 6 объектов с переменными, они позволят нам управлять макросом из пользовательского интерфейса.
Первый разберем подробнее, а остальные я дам скриншотами ниже.
![](https://habrastorage.org/getpro/habr/upload_files/83f/52b/3dd/83f52b3dd88fd0cf16db7fb9545df57e.png)
Мы задаем путь к репозиторию в git.
Название параметра (GIT_URL), то как будет называться поле в UI пользователя.
Описание параметра - текст подсказки к параметру (ссылки работать не будут).
Обязательность параметра - если да, то макрос не создаться без ввода значения. В первом случае параметр обязательный.
Значение по умолчанию, если задать, то поле будет предзаполненно.
Тип параметра - формат переменной, я так понимаю основные типы JAVA работают нормально, в данном случае String. Если честно, я уже точно не помню, как это работает, поэтому оставим так.
Скриншоты с другими параметрами я спрячу под спойлер, чтобы не раздувать статью.
Остальные параметры
![Адрес размещения репозитория на сервере XWiki Адрес размещения репозитория на сервере XWiki](https://habrastorage.org/getpro/habr/upload_files/4dd/dfb/85e/4dddfb85ebaed7bed7fceb89a16b52ce.png)
![Путь к вставляемому файлу, включая его имя. Путь к вставляемому файлу, включая его имя.](https://habrastorage.org/getpro/habr/upload_files/57b/2db/c08/57b2dbc081fbae9669d0c3be869c181a.png)
![Формат в котором выводится текст (вдруг мы захотим не только markdown) Формат в котором выводится текст (вдруг мы захотим не только markdown)](https://habrastorage.org/getpro/habr/upload_files/a71/0b3/cb4/a710b3cb44f1689441ae8e4246ed0f1b.png)
![Ветка в которой ищем файл. Ветка в которой ищем файл.](https://habrastorage.org/getpro/habr/upload_files/1eb/936/e04/1eb936e04d574cbe04b9e4ba8fb87da0.png)
![Иногда бывают глюки, поэтому мы сделаем возможность очистки папки перед клонированием. Иногда бывают глюки, поэтому мы сделаем возможность очистки папки перед клонированием.](https://habrastorage.org/getpro/habr/upload_files/084/706/64e/08470664e242043cf6a32f3ec4dc3cc9.png)
Если боитесь, что что-то забыли можно проверить себя на скриншоте UI макроса в редакторе страницы.
Так же страничку можно забрать из GitHub (вы можете её импортировать через панель администратора в разделе "Контент").
Вернемся к Макросу.
![Настройки макроса Настройки макроса](https://habrastorage.org/getpro/habr/upload_files/525/801/806/525801806b4894c51117b3b18198be8e.png)
ID макроса - то как он будет называться в режиме редактора исходного кода.
Название макроса - то как будет отображаться в UI пользователя.
Описание - подсказка для пользователя.
Категория - категория для фильтров по типам макросов.
Можно ли вставить в линию - если нет, то нужен будет отступ в редакторе исходного кода.
Видимость макроса - в каких границах можно использовать (у нас в рамках текущей вики).
Можно ли вставить контент в макрос - в нашем случае нет.
Тип контента - выберите Wiki.
Осталось загрузить непосредственно код макроса.
На всякий случай сохраните ваш труд и вздохните с облегчением.
Добавим клонирование и удаление репозитория
Перейдем непосредственно к коду.
Полный код под спойлером.
{{groovy}}
import org.apache.commons.io.*
import org.eclipse.jgit.api.*
import org.eclipse.jgit.lib.*
import org.eclipse.jgit.revwalk.*
import org.eclipse.jgit.storage.file.*
import org.eclipse.jgit.transport.*;
import org.xwiki.environment.*;
import org.apache.commons.io.FilenameUtils;
import groovy.io.*;
import org.apache.commons.io.FileUtils;
Git git;
def CredentialsProvider getCredentialsProvider() {
return new UsernamePasswordCredentialsProvider("UXXXX", "glXXXt-XXXXXXXXXXXXXXXXXXXXX")
}
def service = services.get("git");
def Repository getRepository(String repositoryURI, String localDirectoryName) {
Repository repository;
Environment environment = services.component.getInstance(Environment);
File permDir = environment.getPermanentDirectory();
File localGitDirectory = new File(permDir, "git")
File localDirectory = new File(localGitDirectory, localDirectoryName);
File gitDirectory = new File(localDirectory, ".git");
// println "Local Git repository is at [${gitDirectory}]"
FileRepositoryBuilder builder = new FileRepositoryBuilder();
try {
// Step 1: Initialize Git environment
repository = builder.setGitDir(gitDirectory)
.readEnvironment()
.findGitDir()
.build();
Git git = new Git(repository);
// Step 2: Verify if the directory exists and isn't empty.
if (!gitDirectory.exists()) {
// Step 2.1: Need to clone the remote repository since it doesn't exist
git.cloneRepository()
.setCredentialsProvider(getCredentialsProvider())
.setDirectory(localDirectory)
.setURI(repositoryURI).setCloneAllBranches(false).setBranch(wikimacro.parameters.GIT_TREE)
.call();
}
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to execute Git command in [%s]", gitDirectory), e);
}
return repository;
}
def sourceRepoURL = wikimacro.parameters.GIT_URL;
def sourceRepoName = org.apache.commons.io.FilenameUtils.getName("/" + wikimacro.parameters.GIT_LOCAL_PATH);
// delete folder before cloning, for clean pull.
if (wikimacro.parameters.DELETE_FOLDER)
{
def file = new File("../xwiki/data/git/"+sourceRepoName)
if (file.exists()){
FileUtils.deleteDirectory(file)
}
}
def textFilePath = wikimacro.parameters.TEXT_FILE_PATH;
def shortFileName = org.apache.commons.io.FilenameUtils.getName(textFilePath)
def repo = getRepository(sourceRepoURL, sourceRepoName)
result = new Git(repo).pull().setCredentialsProvider(getCredentialsProvider()).call()
def baseUrl = repo.getWorkTree().toString();
def fileName =org.apache.commons.io.FilenameUtils.separatorsToSystem(baseUrl + "/" + textFilePath);
def String fileContents = new File(fileName).getText('UTF-8');
xcontext.put("fileContent", fileContents)
{{/groovy}}
{{velocity}}
#set($fileText =$xcontext.get("fileContent"))
{{content syntax="$wikimacro.parameters.OUTFORMAT.trim()"}}
$fileText
{{/content}}
{{/velocity}}
В целом я не на 100% понимаю этот код, так как часть решений заимствовал. Но постараюсь объяснить что смогу.
{{groovy}}
import org.apache.commons.io.*
import org.eclipse.jgit.api.*
import org.eclipse.jgit.lib.*
import org.eclipse.jgit.revwalk.*
import org.eclipse.jgit.storage.file.*
import org.eclipse.jgit.transport.*;
import org.xwiki.environment.*;
import org.apache.commons.io.FilenameUtils;
import groovy.io.*;
import org.apache.commons.io.FileUtils;
Git git;
Импортируем зависимости.
def CredentialsProvider getCredentialsProvider() {
return new UsernamePasswordCredentialsProvider("UXXXX", "glXXXt-XXXXXXXXXXXXXXXXXXXXX")
}
Функция для авторизации в GitLab, замените UХХХХ на имя пользователя, а glXX-XXX на access token.
Позаимствованный кусок под спойлером
def service = services.get("git");
def Repository getRepository(String repositoryURI, String localDirectoryName) {
Repository repository;
Environment environment = services.component.getInstance(Environment);
File permDir = environment.getPermanentDirectory();
File localGitDirectory = new File(permDir, "git")
File localDirectory = new File(localGitDirectory, localDirectoryName);
File gitDirectory = new File(localDirectory, ".git");
// println "Local Git repository is at [${gitDirectory}]"
FileRepositoryBuilder builder = new FileRepositoryBuilder();
try {
// Step 1: Initialize Git environment
repository = builder.setGitDir(gitDirectory)
.readEnvironment()
.findGitDir()
.build();
Git git = new Git(repository);
// Step 2: Verify if the directory exists and isn't empty.
if (!gitDirectory.exists()) {
// Step 2.1: Need to clone the remote repository since it doesn't exist
git.cloneRepository()
.setCredentialsProvider(getCredentialsProvider())
.setDirectory(localDirectory)
.setURI(repositoryURI).setCloneAllBranches(false).setBranch(wikimacro.parameters.GIT_TREE)
.call();
}
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to execute Git command in [%s]", gitDirectory), e);
}
return repository;
}
Эта функция, как я понимаю непосредственно клонирует репозиторий.
def sourceRepoURL = wikimacro.parameters.GIT_URL;
def sourceRepoName = org.apache.commons.io.FilenameUtils.getName("/" + wikimacro.parameters.GIT_LOCAL_PATH);
Сохраняем и обрабатываем параметры с URL из параметров макроса.
// delete folder before cloning, for clean pull.
if (wikimacro.parameters.DELETE_FOLDER)
{
def file = new File("../xwiki/data/git/"+sourceRepoName)
if (file.exists()){
FileUtils.deleteDirectory(file)
}
}
Если включен параметр "Удалять папку", то мы ее удаляем.
../xwiki/data/git/ - я уже
плохо помню, но скорее всего это относительный путь, куда Xwiki по умолчанию складывает репозитории.
def textFilePath = wikimacro.parameters.TEXT_FILE_PATH;
def shortFileName = org.apache.commons.io.FilenameUtils.getName(textFilePath)
def repo = getRepository(sourceRepoURL, sourceRepoName)
result = new Git(repo).pull().setCredentialsProvider(getCredentialsProvider()).call()
def baseUrl = repo.getWorkTree().toString();
def fileName =org.apache.commons.io.FilenameUtils.separatorsToSystem(baseUrl + "/" + textFilePath);
def String fileContents = new File(fileName).getText('UTF-8');
Получаем данные из репозитория и сохраняем их в переменную.
xcontext.put("fileContent", fileContents)
{{/groovy}}
Отправляем содержимое переменной в общий контекст, чтобы забрать из макроса на Velocity.
По идее это не обязательно, так как должен был сработать обмен переменными между языками, но у меня что-то пошло не так.
На этом кусок Groovy кода заканчивается.
И начинается небольшой кусочек кода на Velocity.
Код на Velocity нам нужен потому, что из него можно вызывать другие макросы XWiki, в данном случае макрос Content, который позволит нам отобразить наш текст в выбранном формате. Обратите внимание, как вставляется переменная макроса (OUTFORMAT) в Velocity.
Кстати переменные макроса было не обязательно писать с больших букв, это моя прихоть.
{{velocity}}
#set($fileText =$xcontext.get("fileContent"))
{{content syntax="$wikimacro.parameters.OUTFORMAT.trim()"}}
$fileText
{{/content}}
{{/velocity}}
Ну вот и всё.
Остается сохранить и проставить настройки как на скриншоте.
![](https://habrastorage.org/getpro/habr/upload_files/66c/0ac/9d1/66c0ac9d120a4ac82994b0e04c3d729f.png)
Наслаждаемся результатом
Создадим еще страницу и разместим на ней наш свеженький макрос.
![Окно выбора макроса Окно выбора макроса](https://habrastorage.org/getpro/habr/upload_files/1a2/06a/f04/1a206af04b356055d5dbc17f2b07b83b.png)
![Параметры макроса Параметры макроса](https://habrastorage.org/getpro/habr/upload_files/903/ccb/c68/903ccbc68dc7dafe280e1b44c1a7f1dd.png)
Сохраняем результат и проверяем.
![GitLab GitLab](https://habrastorage.org/getpro/habr/upload_files/60f/1bc/82c/60f1bc82c4c4a9f7d24334d86d7a8014.png)
![Xwiki Xwiki](https://habrastorage.org/getpro/habr/upload_files/40a/ae9/3dc/40aae93dcb1c786bd994041c0583aa33.png)
Обратите внимание, что все относительные пути в ссылках и у картинок, сломаются. Лучше использовать абсолютные.
Ну вот и всё. Я понимаю, что пример получился не самым лучшим, потому что я уже сам подзабыл как работает часть кода и полей, но он демонстрирует разные приемы работы с XWiki и надеюсь, что будет кому-то полезен в условиях недостатка документации на русском языке.
P.S. Изначально вместо этой статьи я хотел написать материал о моём субъективном опыте одновременного использования XWiki и Confluence в качестве базы знаний для команды разработки.
Однако, в итоге решил, что человечество к такому пока еще не готово.
Но если вы все же хотите почитать материал на тему Confluence vs Xwiki напишите об этом в комментариях.