Skip to content

Commit efee0a7

Browse files
committed
Support anchor rewrites
Add support for anchor rewrites that can be used to change a previously published anchor to and updated one. Closes gh-29
1 parent ed51784 commit efee0a7

21 files changed

+422
-5
lines changed

README.adoc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,41 @@ For example, the following document will replace all package declarations with `
441441

442442

443443

444+
=== Anchor Rewriting
445+
Anchor rewriting can be used if you need to evolve a document and change previously published anchor links.
446+
447+
For example, say you publish a document with the following typo:
448+
449+
[source,asciidoctor]
450+
----
451+
[[initializzzing]]
452+
== Initializing
453+
This is how to do it.
454+
----
455+
456+
You then fix the typo and publish a new revision:
457+
458+
[source,asciidoctor]
459+
----
460+
[[initializing]]
461+
== Initializing
462+
This is how to do it.
463+
----
464+
465+
You can add a rewrite rule so that any links to `+++https://example.com/doc#initializzzing+++` will be automatically changed to `+++https://example.com/doc#initializing+++`.
466+
467+
To define the rewrite rules, add a `anchor-rewrite.properties` file to your `base_dir` (usually the same directory as your `index.adoc` file).
468+
469+
For example, the following file would be used in the example above:
470+
471+
[source,properties]
472+
.anchor-rewrite.properties
473+
----
474+
initializzzing=initializing
475+
----
476+
477+
478+
444479
== Contributing
445480
If you're looking to contribute to this project, or you're just trying to navigate the code please take a look at the link:CONTRIBUTING.adoc[CONTRIBUTING] file.
446481

src/main/java/io/spring/asciidoctor/backend/SpringBackendsExtensionRegistry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.spring.asciidoctor.backend;
1818

19+
import io.spring.asciidoctor.backend.anchorrewrite.AnchorRewriteDocinfoProcessor;
1920
import org.asciidoctor.Asciidoctor;
2021
import org.asciidoctor.jruby.extension.spi.ExtensionRegistry;
2122

@@ -29,6 +30,7 @@ public class SpringBackendsExtensionRegistry implements ExtensionRegistry {
2930
@Override
3031
public void register(Asciidoctor asciidoctor) {
3132
asciidoctor.requireLibrary("spring-asciidoctor-backends");
33+
asciidoctor.javaExtensionRegistry().docinfoProcessor(new AnchorRewriteDocinfoProcessor());
3234
}
3335

3436
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.asciidoctor.backend.anchorrewrite;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
22+
import org.asciidoctor.ast.Document;
23+
import org.asciidoctor.extension.DocinfoProcessor;
24+
25+
/**
26+
* {@link DocinfoProcessor} to add an anchor-rewrite script.
27+
*
28+
* @author Phillip Webb
29+
*/
30+
public class AnchorRewriteDocinfoProcessor extends DocinfoProcessor {
31+
32+
@Override
33+
public String process(Document document) {
34+
try {
35+
File directory = getDirectory(document);
36+
File propertiesFile = new File(directory, "anchor-rewrite.properties");
37+
return (propertiesFile.exists()) ? AnchorRewriteScriptTag.fromPropertiesFile(propertiesFile).getHtml() : "";
38+
}
39+
catch (IOException ex) {
40+
throw new IllegalStateException(ex);
41+
}
42+
}
43+
44+
private File getDirectory(Document document) {
45+
Object directory = document.getOptions().get("base_dir");
46+
directory = (directory != null) ? directory : document.getOptions().get("docdir");
47+
directory = (directory != null) ? directory : new File(".");
48+
return (directory instanceof File) ? (File) directory : new File(directory.toString());
49+
}
50+
51+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.asciidoctor.backend.anchorrewrite;
18+
19+
import java.io.File;
20+
import java.io.FileInputStream;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.util.Collections;
24+
import java.util.Iterator;
25+
import java.util.LinkedHashMap;
26+
import java.util.Map;
27+
import java.util.Map.Entry;
28+
import java.util.Objects;
29+
import java.util.Properties;
30+
31+
/**
32+
* Generates the script tag for anchor rewrites.
33+
*
34+
* @author Phillip Webb
35+
*/
36+
class AnchorRewriteScriptTag {
37+
38+
private final Map<String, String> entries;
39+
40+
AnchorRewriteScriptTag(Map<?, ?> entries) {
41+
this.entries = getValidEntries(entries);
42+
}
43+
44+
private Map<String, String> getValidEntries(Map<?, ?> entries) {
45+
Map<String, String> validEntries = new LinkedHashMap<>();
46+
entries.forEach((key, value) -> {
47+
if (!Objects.equals(key, value)) {
48+
validEntries.put(String.valueOf(key), String.valueOf(value));
49+
}
50+
});
51+
return Collections.unmodifiableMap(validEntries);
52+
}
53+
54+
String getHtml() {
55+
if (this.entries.isEmpty()) {
56+
return "";
57+
}
58+
StringBuilder html = new StringBuilder();
59+
html.append("<script type=\"application/json\" id=\"anchor-rewrite\">\n");
60+
html.append("{\n");
61+
Iterator<Entry<String, String>> iterator = this.entries.entrySet().iterator();
62+
while (iterator.hasNext()) {
63+
Entry<String, String> entry = iterator.next();
64+
html.append(jsonQuote(entry.getKey()));
65+
html.append(":");
66+
html.append(jsonQuote(entry.getValue()));
67+
html.append(iterator.hasNext() ? ",\n" : "\n");
68+
}
69+
html.append("}\n");
70+
html.append("</script>");
71+
return html.toString();
72+
}
73+
74+
private String jsonQuote(String string) {
75+
return "\"" + string.replace("\"", "\\\"") + "\"";
76+
}
77+
78+
static AnchorRewriteScriptTag fromPropertiesFile(File propertiesFile) throws IOException {
79+
Properties properties = new Properties();
80+
try (InputStream inputStream = new FileInputStream(propertiesFile)) {
81+
properties.load(inputStream);
82+
}
83+
return new AnchorRewriteScriptTag(properties);
84+
}
85+
86+
}

src/main/js/site/anchorrewrite.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the 'License');
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an 'AS IS' BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
(function () {
18+
"use strict";
19+
20+
window.addEventListener("load", onChange);
21+
window.addEventListener("hashchange", onChange);
22+
23+
function onChange() {
24+
const element = document.getElementById("anchor-rewrite");
25+
const anchor = window.location.hash.substr(1);
26+
if (element && anchor) {
27+
const rewites = JSON.parse(element.innerHTML);
28+
updateAnchor(anchor, rewites);
29+
}
30+
}
31+
32+
function updateAnchor(anchor, rewrites) {
33+
const seen = [anchor];
34+
console.debug(anchor);
35+
while (rewrites[anchor]) {
36+
anchor = rewrites[anchor];
37+
if (seen.includes(anchor)) {
38+
console.error("Skipping circular anchor update");
39+
return;
40+
}
41+
seen.push(anchor);
42+
}
43+
window.location.hash = anchor;
44+
}
45+
})();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
old1=old2
2+
old2=copy-to-clipboard

src/test/asciidoc/spring.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public void itAlsoLooksNice() {
6868

6969

7070

71+
[[copy-to-clipboard]]
7172
== Copy to Clipboard
7273
Hover over any code block and a copy-to-clipboard button is available:
7374

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
old-anchor=new-anchor
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.asciidoctor.backend.anchorrewrite;
18+
19+
import java.io.File;
20+
import java.net.URL;
21+
import java.util.LinkedHashMap;
22+
import java.util.Map;
23+
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.TestInfo;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.assertj.core.api.Assertions.contentOf;
30+
31+
/**
32+
* Tests for {@link AnchorRewriteScriptTag}.
33+
*
34+
* @author Phillip Webb
35+
*/
36+
class AnchorRewriteScriptTagTests {
37+
38+
private URL expected;
39+
40+
@BeforeEach
41+
void setup(TestInfo testInfo) {
42+
String name = "AnchorRewriteScriptTagTests_" + testInfo.getTestMethod().get().getName() + ".html";
43+
this.expected = getClass().getResource(name);
44+
}
45+
46+
@Test
47+
void getHtmlGeneratesScriptTag() {
48+
Map<String, String> entries = new LinkedHashMap<>();
49+
entries.put("foo", "bar");
50+
entries.put("green", "blue");
51+
AnchorRewriteScriptTag tag = new AnchorRewriteScriptTag(entries);
52+
assertThat(tag.getHtml()).isEqualTo(contentOf(this.expected));
53+
}
54+
55+
@Test
56+
void getHtmlWhenHasEqualKeyValueEntryGeneratesFilteredHtml() {
57+
Map<String, String> entries = new LinkedHashMap<>();
58+
entries.put("foo", "foo");
59+
entries.put("green", "blue");
60+
AnchorRewriteScriptTag tag = new AnchorRewriteScriptTag(entries);
61+
assertThat(tag.getHtml()).isEqualTo(contentOf(this.expected));
62+
}
63+
64+
@Test
65+
void getHtmlWhenEmptyGeneratesEmptyHtml() {
66+
Map<String, String> entries = new LinkedHashMap<>();
67+
AnchorRewriteScriptTag tag = new AnchorRewriteScriptTag(entries);
68+
assertThat(tag.getHtml()).isEqualTo("");
69+
}
70+
71+
@Test
72+
void fromPropertiesFileLoadsFileContents() throws Exception {
73+
File file = new File(
74+
"src/test/java/" + getClass().getPackage().getName().replace(".", "/") + "/anchor-rewrite.properties");
75+
AnchorRewriteScriptTag tag = AnchorRewriteScriptTag.fromPropertiesFile(file);
76+
assertThat(tag.getHtml()).contains("foo");
77+
78+
}
79+
80+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo=bar

0 commit comments

Comments
 (0)