Skip to content

Commit

Permalink
Fixes asciidoctor#1265. Support setting column widths
Browse files Browse the repository at this point in the history
  • Loading branch information
robertpanzer committed Apr 1, 2024
1 parent 9f9248a commit 01456e1
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 9 deletions.
6 changes: 6 additions & 0 deletions asciidoctorj-api/src/main/java/org/asciidoctor/ast/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ enum VerticalAlignment {
*/
void setGrid(String grid);

/**
* Computes the percentual column widths for all columns.
* Before calling this method all columns must have been added to the Table by adding them to
* {@link #getColumns()} and the desired widths must have been set by calling {@link Column#setWidth(int)}.
*/
void assignColumnWidths();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.asciidoctor.jruby.internal.RubyObjectWrapper;
import org.jruby.RubyArray;
import org.jruby.RubyNil;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import java.util.AbstractList;
Expand Down Expand Up @@ -72,6 +73,26 @@ public List<Row> getHeader() {
return rows.getHeader();
}

@Override
public void assignColumnWidths() {
int widthBase = 0;
RubyArray autowidthCols = getRuntime().newArray();
for (Column column : getColumns()) {
int width = column.getWidth();
if (width < 0) {
autowidthCols.add(((ColumnImpl)column).getRubyObject());
} else {
widthBase += width;
}
}
ThreadContext threadContext = getRuntime().getThreadService().getCurrentContext();

getRubyObject().callMethod(threadContext, "assign_column_widths", new IRubyObject[] {
getRuntime().newFixnum(widthBase),
autowidthCols.isEmpty() ? getRuntime().getNil() : autowidthCols
});
}

private class Rows extends RubyObjectWrapper {

public Rows(IRubyObject rubyNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.asciidoctor.util.TestHttpServer
class GithubContributorsBlockMacro extends BlockMacroProcessor {

private static final String IMAGE = 'image'
private static final String WIDTHS = 'widths'

GithubContributorsBlockMacro(String macroName) {
super(macroName)
Expand All @@ -29,12 +30,22 @@ class GithubContributorsBlockMacro extends BlockMacroProcessor {
table.grid = 'rows'
table.title = "Github contributors of $target"

List<Integer> widths = [1,2,2]
if (attributes.containsKey(WIDTHS)) {
widths = (attributes[WIDTHS] as String).split(',').collect {Integer.parseInt(it) }
}
// Create the columns 'Login' and 'Contributions'
Column avatarColumn = createTableColumn(table, 0)
avatarColumn.width = widths[0]
Column loginColumn = createTableColumn(table, 1)
loginColumn.width = widths[1]
Column contributionsColumn = createTableColumn(table, 2)
contributionsColumn.width = widths[2]
contributionsColumn.horizontalAlignment = Table.HorizontalAlignment.CENTER

table.columns << avatarColumn
table.columns << loginColumn
table.columns << contributionsColumn
table.assignColumnWidths()
// Create the single header row with the column titles
Row headerRow = createTableRow(table)
headerRow.cells << createTableCell(avatarColumn, 'Avatar')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.asciidoctor.extension

import org.asciidoctor.Asciidoctor
import org.asciidoctor.Options
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.SafeMode
import org.asciidoctor.util.ClasspathResources
Expand All @@ -9,36 +10,59 @@ import org.jboss.arquillian.spock.ArquillianSputnik
import org.jboss.arquillian.test.api.ArquillianResource
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.select.Elements
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import spock.lang.Specification

import static java.nio.charset.StandardCharsets.UTF_8

@RunWith(ArquillianSputnik)
class WhenABlockMacroProcessorCreatesATable extends Specification {

public static final String FIRST_TD = 'td:first-child'
public static final String SECOND_TD = 'td:nth-child(2)'
public static final String THIRD_TD = 'td:nth-child(3)'
public static final String IMG_ELEMENT = 'img'
public static final String COL = 'col'
public static final String STYLE = 'style'
public static final String WIDTH = 'width'
public static final String WIDTH_2 = '2%'
public static final String WIDTH_3 = '3%'
public static final String WIDTH_20 = '20%'
public static final String WIDTH_40 = '40%'
public static final String CONTRIBUTOR = 'bob'
public static final String BLOCKMACRO_NAME = 'githubcontributors'

public static final String AVATAR_URL_REGEXP = /https:\/\/avatars.githubusercontent.com\/u\/.*/
public static final String CSS_QUERY_TABLE = 'table'
public static final String CLASS_GRID_ROWS = 'grid-rows'
public static final String CLASS_HALIGN_LEFT = 'halign-left'
public static final String CLASS_HALIGN_CENTER = 'halign-center'
public static final String ATTR_SRC = 'src'
public static final int THREE = 3

@ArquillianResource
private Asciidoctor asciidoctor

@ArquillianResource
private TemporaryFolder tmp

@ArquillianResource
private ClasspathResources classpathResources

private static final String DOCUMENT = '''
= AsciidoctorJRuby contributors
githubcontributors::asciidoctor/asciidoctorj[]
'''

@ArquillianResource
private ClasspathResources classpathResources
private static final String DOCUMENT_WITH_NEGATIVE_WIDTHS = '''
= AsciidoctorJRuby contributors
githubcontributors::asciidoctor/asciidoctorj[widths="2,3,-1"]
'''


def setup() {
TestHttpServer.start(['http://api.github.com/repos/asciidoctor/asciidoctorj/contributors' : classpathResources.getResource('githubcontributors.json')])
Expand All @@ -60,18 +84,61 @@ githubcontributors::asciidoctor/asciidoctorj[]
asciidoctor.convert(DOCUMENT, OptionsBuilder.options().safe(SafeMode.SAFE).inPlace(false).baseDir(tmp.getRoot()).toDir(tmp.getRoot()).toFile(resultFile))

then:
Document htmlDocument = Jsoup.parse(resultFile, 'UTF-8')
Document htmlDocument = Jsoup.parse(resultFile, UTF_8.name())

Elements cols = htmlDocument.select(COL)
cols.size() == THREE
cols.get(0).attr(STYLE).contains(WIDTH_20) || cols.get(0).attr(WIDTH).equals(WIDTH_20)
cols.get(1).attr(STYLE).contains(WIDTH_40) || cols.get(1).attr(WIDTH).equals(WIDTH_40)
cols.get(2).attr(STYLE).contains(WIDTH_40) || cols.get(2).attr(WIDTH).equals(WIDTH_40)

htmlDocument.select(CSS_QUERY_TABLE).hasClass(CLASS_GRID_ROWS)

htmlDocument.select(FIRST_TD).every { tdElement -> tdElement.select(IMG_ELEMENT).size() == 1 }
htmlDocument.select(FIRST_TD).every { tdElement -> tdElement.select(IMG_ELEMENT)[0].attr(ATTR_SRC) =~ AVATAR_URL_REGEXP }

htmlDocument.select(SECOND_TD).size() == htmlDocument.select(SECOND_TD) != 0
htmlDocument.select(SECOND_TD).size() != 0

htmlDocument.select(SECOND_TD).every { tdElement -> tdElement.hasClass(CLASS_HALIGN_LEFT) }
htmlDocument.select(THIRD_TD).every { tdElement -> tdElement.hasClass(CLASS_HALIGN_CENTER) }

htmlDocument.select(SECOND_TD).find { tdElement -> tdElement.text() == CONTRIBUTOR } != null
}

def "the table should be rendered to html with negative widths"() {
given:
asciidoctor.javaExtensionRegistry().blockMacro(BLOCKMACRO_NAME, GithubContributorsBlockMacro)
File resultFile = tmp.newFile('resultWithNegativeWidth.html')

when:
def options = Options.builder()
.safe(SafeMode.SAFE)
.inPlace(false)
.baseDir(tmp.getRoot())
.toFile(resultFile)
.build()
asciidoctor.convert(DOCUMENT_WITH_NEGATIVE_WIDTHS, options)

then:
Document htmlDocument = Jsoup.parse(resultFile, UTF_8.name())

Elements cols = htmlDocument.select(COL)
cols.size() == THREE
cols.get(0).attr(STYLE).contains(WIDTH_2) || cols.get(0).attr(WIDTH).equals(WIDTH_2)
cols.get(1).attr(STYLE).contains(WIDTH_3) || cols.get(1).attr(WIDTH).equals(WIDTH_3)
cols.get(2).attr(STYLE).length() == 0 && cols.get(2).attr(WIDTH).length() == 0

htmlDocument.select('table').hasClass('grid-rows')
htmlDocument.select(CSS_QUERY_TABLE).hasClass(CLASS_GRID_ROWS)

htmlDocument.select(FIRST_TD).every { tdElement -> tdElement.select(IMG_ELEMENT).size() == 1 }
htmlDocument.select(FIRST_TD).every { tdElement -> tdElement.select(IMG_ELEMENT)[0].attr('src') =~ AVATAR_URL_REGEXP }
htmlDocument.select(FIRST_TD).every { tdElement -> tdElement.select(IMG_ELEMENT)[0].attr(ATTR_SRC) =~ AVATAR_URL_REGEXP }

htmlDocument.select(SECOND_TD).size() == htmlDocument.select(SECOND_TD) != 0
htmlDocument.select(SECOND_TD).size() != 0

htmlDocument.select(SECOND_TD).every { tdElement -> tdElement.hasClass('halign-left')}
htmlDocument.select(THIRD_TD).every { tdElement -> tdElement.hasClass('halign-center')}
htmlDocument.select(SECOND_TD).every { tdElement -> tdElement.hasClass(CLASS_HALIGN_LEFT) }
htmlDocument.select(THIRD_TD).every { tdElement -> tdElement.hasClass(CLASS_HALIGN_CENTER) }

htmlDocument.select(SECOND_TD).find { tdElement -> tdElement.text() == CONTRIBUTOR } != null
}
Expand Down
4 changes: 3 additions & 1 deletion config/codenarc/codenarc.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ ruleset {
ruleset('rulesets/braces.xml') {
exclude 'IfStatementBraces'
}
ruleset('rulesets/size.xml')
ruleset('rulesets/size.xml') {
exclude 'AbcMetric'
}
ruleset('rulesets/junit.xml') {
// Does not play well with Spock tests
exclude 'JUnitPublicNonTestMethod'
Expand Down

0 comments on commit 01456e1

Please sign in to comment.