spring / / 2023. 7. 27. 15:21

Restdocs 작성 시 다른 모듈에 있는 adoc 파일 불러오기

일반적으로 Restdocs를 작성할 때 해당 프로젝트(모듈)에 있는 adoc 문서로 html을 만드는 것이 일반적이다. 하지만 adoc파일을 다른 모듈에 있고 이를 활용하여 html을 만들어야 하는 경우는 어떻게 할까? spring restdocs에서는 이런 방식은 기본적으로 제공하지 않는다.

그래서 유사하게 만들어 놓은게 있어서 참고해서 만들어봤다. (https://github.com/abelsromero/asciidoctorj-maven-includeprocessor)


시나리오

  • 프로젝트는 2개
    • Project1, Project2
  • Project1의 adoc을 Project2에서 활용해야 한다.
    • 예: include: {Project1}/project1.adoc (<-- project2에서 이런식으로 사용)

작업 순서

  1. asciidoc 문서 복사

    1. Project1을 빌드할 때 process-resources로 src/main/asciidoc을 classes로 복사
  2. Asciidoc extension 개발

    1. include를 사용할 때 classpath에서 읽을 수 있는 extension
  3. Project2에서 include: classpath:asciidoc/... 형식으로 include

작성 모듈

필요한 모듈은 3개이다.

  • project1
  • asciidoc-extension
  • project2

1) Project1 작업

adoc 파일 생성

src/main/asciidoc/project1.adoc 파일 생성

컨텐츠는 중요하지 않으므로 아주 간단히 작성한다.

= Project1 API Document

Project1 문서의 내용입니다.

pom.xml에 추가

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-asciidoc-resources</id>
            <phase>process-resources</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <resources>
                    <resource>
                        <directory>src/main/asciidoc</directory>
                    </resource>
                </resources>
                <outputDirectory>${project.build.directory}/classes/asciidoc</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

process-resources 시 classes/asciidoc에 복사를 한다.

2) asciidoc extension 개발

다른 모듈에 있는 adoc을 읽어올 수 있도록 classpath로 부터 파일을 읽을 수 있는 extension을 개발한다.

asciidoc에는 기능을 확장할 수 있는 몇 가지 extension이 존재하는데 여기서는 include를 할 때 사용할 수 있는 IncludeProcessor를 사용하였다.

AsiidoctorJ extension API: https://docs.asciidoctor.org/asciidoctorj/latest/extensions/extensions-introduction/

pom.xml에 dependency 추가

<dependencies>
  <dependency>
      <groupId>org.asciidoctor</groupId>
      <artifactId>asciidoctorj</artifactId>
      <version>2.5.7</version>
  </dependency>
  <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.11.0</version>
  </dependency>
</dependencies>

ClasspathIncludeProcessor 추가

public class ClasspathIncludeProcessor extends IncludeProcessor {

    private static final String CLASSPATH_PREFIX = "classpath:";

    @Override
    public boolean handles(String target) {
        return isClasspathTarget(target);
    }

    @Override
    public void process(Document document, PreprocessorReader reader, String target, Map<String, Object> attributes) {
        try {
            final String sanitizedTarget = cleanPrefix(target);
            final URL resourceURL = isClasspathTarget(reader.getDir())
                ? findResource(cleanPrefix(reader.getDir()) + "/" + sanitizedTarget)
                : findResource(sanitizedTarget);

            if (resourceURL != null) {
                try (InputStream resourceAsStream = resourceURL.openStream()) {
                    reader.pushInclude(IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8), target, resourceURL.toString(), 1, attributes);
                }
            }
        } catch (IOException e) {
            log(new LogRecord(Severity.ERROR, "Could not include target: " + target));
        }
    }

    private String cleanPrefix(String target) {
        return target.substring(CLASSPATH_PREFIX.length());
    }

    private URL findResource(String target) {
        return this.getClass().getClassLoader().getResource(target);
    }

    private boolean isClasspathTarget(String dir) {
        return dir.startsWith(CLASSPATH_PREFIX);
    }

}

ClasspathIncludeExtension 추가

public class ClasspathIncludeExtension implements ExtensionRegistry {

    @Override
    public void register(Asciidoctor asciidoctor) {
        JavaExtensionRegistry javaExtensionRegistry = asciidoctor.javaExtensionRegistry();
        javaExtensionRegistry.includeProcessor(ClasspathIncludeProcessor.class);
    }
}

위에서 만든 extension을 사용하기 위해서는 아래 경로에 추가해줘야 한다.

resources/META-INF/services 하위에 org.asciidoctor.jruby.extension.spi.ExtensionRegistry 파일을 추가한다.

example.ClasspathIncludeExtension

3) Project2 작업

project2에서 실제 project1의 adoc파일을 사용을 해보자.

pom.xml에 추가

<plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <version>2.2.2</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-asciidoctor</artifactId>
            <version>2.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.asciidoctor</groupId>
            <artifactId>asciidoctorj</artifactId>
            <version>2.5.7</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>asciidoc-extension</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>project1</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <executions>
        <execution>
            <id>generate-docs</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>process-asciidoc</goal>
            </goals>
            <configuration>
                <backend>html</backend>
                <doctype>book</doctype>
            </configuration>
        </execution>
    </executions>
</plugin>

project2.adoc에서 project1.adoc을 include한다.

project2.adoc

== Project2 문서

include::classpath:asciidoc/project1.adoc[]

Include::classpath를 사용하였는데 위에서 만든 extension에서 이런 방식을 지원할 수 있도록 한 것이다.

project2에서 mvn prepare-package를 실행한다.

target하위에 project2.html 파일을 열어 결과가 잘 나오는지 확인한다.

실행결과

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유