Skip to content

Commit bb19dfc

Browse files
devversionjelbourn
authored andcommitted
build: highlight example sources files with bazel (#14325)
Part of #13454
1 parent 6347ace commit bb19dfc

File tree

9 files changed

+173
-9
lines changed

9 files changed

+173
-9
lines changed

src/material-examples/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package(default_visibility=["//visibility:public"])
33
load("@angular//:index.bzl", "ng_package")
44
load("//:packages.bzl", "CDK_TARGETS", "MATERIAL_TARGETS", "ROLLUP_GLOBALS")
55
load("//tools:defaults.bzl", "ng_module")
6+
load("//tools/highlight-files:index.bzl", "highlight_files")
67

78
ng_module(
89
name = "examples",
@@ -21,6 +22,11 @@ ng_module(
2122
tsconfig = ":tsconfig-build.json",
2223
)
2324

25+
highlight_files(
26+
name = "highlighted-files",
27+
srcs = glob(["*/*.html", "*/*.css", "*/*.ts"])
28+
)
29+
2430
ng_package(
2531
name = "npm_package",
2632
srcs = ["package.json"],

tools/highlight-files/BUILD.bazel

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
4+
load("//tools:defaults.bzl", "ts_library")
5+
6+
nodejs_binary(
7+
name = "highlight-files",
8+
entry_point = "angular_material/tools/highlight-files/highlight-files.js",
9+
data = [
10+
"@matdeps//highlight.js",
11+
"@matdeps//source-map-support",
12+
":sources",
13+
],
14+
)
15+
16+
ts_library(
17+
name = "sources",
18+
srcs = glob(["**/*.ts"]),
19+
deps = ["@matdeps//@types/node"],
20+
tsconfig = ":tsconfig.json",
21+
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Script that will be used by the highlight_files Bazel rule in order to highlight
3+
* multiple input files using highlight.js. The output will be HTML files.
4+
*/
5+
6+
import {readFileSync, writeFileSync} from 'fs';
7+
import {extname, join} from 'path';
8+
import {highlightCodeBlock} from './highlight-code-block';
9+
10+
/**
11+
* Determines the command line arguments for the current Bazel action. Since this action can
12+
* have a large set of input files, Bazel may write the arguments into a parameter file.
13+
* This function is responsible for handling normal argument passing or Bazel parameter files.
14+
* Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file
15+
*/
16+
function getBazelActionArguments() {
17+
const args = process.argv.slice(2);
18+
19+
// If Bazel uses a parameter file, we've specified that it passes the file in the following
20+
// format: "arg0 arg1 --param-file={path_to_param_file}"
21+
if (args[0].startsWith('--param-file=')) {
22+
return readFileSync(args[0].split('=')[1], 'utf8').trim().split('\n');
23+
}
24+
25+
return args;
26+
}
27+
28+
if (require.main === module) {
29+
// The script expects the bazel-bin path as first argument. All remaining arguments will be
30+
// considered as markdown input files that need to be transformed.
31+
const [bazelBinPath, ...inputFiles] = getBazelActionArguments();
32+
33+
// Walk through each input file and write transformed markdown output to the specified
34+
// Bazel bin directory.
35+
inputFiles.forEach(inputPath => {
36+
const fileExtension = extname(inputPath).substring(1);
37+
// Convert "my-component-example.ts" into "my-component-example-ts.html"
38+
const baseOutputPath = inputPath.replace(`.${fileExtension}`, `-${fileExtension}.html`);
39+
const outputPath = join(bazelBinPath, baseOutputPath);
40+
const htmlOutput = highlightCodeBlock(readFileSync(inputPath, 'utf8'), fileExtension);
41+
42+
writeFileSync(outputPath, htmlOutput);
43+
});
44+
45+
}

tools/highlight-files/index.bzl

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Gets a path relative to the specified label. This is achieved by just removing the label
3+
package path from the specified path. e.g. the path is "guides/test/my-text.md" and the
4+
label package is "guides/". The expected path would be "test/my-text.md".
5+
"""
6+
def _relative_to_label(label, short_path):
7+
# TODO(devversion): extract into generic utility under tools/
8+
return short_path[len(label.package) + 1:]
9+
10+
"""
11+
Implementation of the "highlight_files" rule. The implementation runs the
12+
highlight-files executable in order to highlight the specified source files.
13+
"""
14+
def _highlight_files(ctx):
15+
input_files = ctx.files.srcs;
16+
args = ctx.actions.args()
17+
expected_outputs = [];
18+
19+
# Do nothing if there are no input files. Bazel will throw if we schedule an action
20+
# that returns no outputs.
21+
if not input_files:
22+
return None
23+
24+
# Support passing arguments through a parameter file. This is necessary because on Windows
25+
# there is an argument limit and we need to handle a large amount of input files. Bazel
26+
# switches between parameter file and normal argument passing based on the operating system.
27+
# Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file
28+
args.use_param_file(param_file_arg = "--param-file=%s")
29+
30+
# Add the bazel bin directory to the command arguments. The script needs to know about
31+
# the output directory because the input files are not in the same location as the bazel
32+
# bin directory.
33+
args.add(ctx.bin_dir.path)
34+
35+
for input_file in input_files:
36+
# Extension of the input file (e.g. "ts" or "css")
37+
file_extension = input_file.extension
38+
39+
# Determine the input file path relatively to the current package path. This is necessary
40+
# because we want to preserve directories for the input files and `declare_file` expects a
41+
# path that is relative to the current package. We remove the file extension including the dot
42+
# because we will constructo an output file using a different extension.
43+
relative_basepath = _relative_to_label(ctx.label, input_file.short_path)[
44+
:-len(file_extension) - 1]
45+
46+
# Construct the output path from the relative basepath and file extension. For example:
47+
# "autocomplete.ts" should result in "autocomplete-ts.html".
48+
expected_outputs += [
49+
ctx.actions.declare_file("%s-%s.html" % (relative_basepath, file_extension)),
50+
]
51+
52+
# Add the path for the input file to the command line arguments, so that the executable
53+
# can process it.
54+
args.add(input_file.path)
55+
56+
# Run the highlight-files executable that highlights the specified source files.
57+
ctx.actions.run(
58+
inputs = input_files,
59+
executable = ctx.executable._highlight_files,
60+
outputs = expected_outputs,
61+
arguments = [args],
62+
)
63+
64+
return DefaultInfo(files = depset(expected_outputs))
65+
66+
"""
67+
Rule definition for the "highlight_files" rule that can accept arbritary source files
68+
that will be transformed into HTML files which reflect the highlighted source code of
69+
the given files. The outputs can be referenced through the default output provider.
70+
"""
71+
highlight_files = rule(
72+
implementation = _highlight_files,
73+
attrs = {
74+
"srcs": attr.label_list(allow_files = True),
75+
76+
# Executable for this rule that is responsible for highlighting the specified
77+
# input files.
78+
"_highlight_files": attr.label(
79+
default = Label("//tools/highlight-files"),
80+
executable = True,
81+
cfg = "host"
82+
)},
83+
)

tools/highlight-files/tsconfig.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"lib": ["es2015"],
4+
"module": "commonjs",
5+
"target": "es5",
6+
"sourceMap": true,
7+
"types": ["node"]
8+
},
9+
"bazelOptions": {
10+
"suppressTsconfigOverrideWarnings": true
11+
}
12+
}

tools/markdown-to-html/BUILD.bazel

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ ts_library(
88
srcs = glob(["**/*.ts"]),
99
deps = [
1010
"@matdeps//@types/node",
11-
"@matdeps//@types/marked"
11+
"@matdeps//@types/marked",
12+
"//tools/highlight-files:sources",
1213
],
1314
tsconfig = ":tsconfig.json",
1415
)

tools/markdown-to-html/docs-marked-renderer.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import {MarkedOptions, Renderer} from 'marked';
1+
import {Renderer} from 'marked';
22
import {basename, extname} from 'path';
3-
import {highlightCodeBlock} from './highlight-code-block';
43

54
/** Regular expression that matches whitespace. */
65
const whitespaceRegex = /\W+/g;
@@ -14,10 +13,6 @@ const exampleCommentRegex = /<!--\W*example\(([^)]+)\)\W*-->/g;
1413
*/
1514
export class DocsMarkdownRenderer extends Renderer {
1615

17-
constructor(options?: MarkedOptions) {
18-
super({highlight: highlightCodeBlock, baseUrl: 'material.angular.io/', ...options});
19-
}
20-
2116
/**
2217
* Transforms a markdown heading into the corresponding HTML output. In our case, we
2318
* want to create a header-link for each H3 and H4 heading. This allows users to jump to

tools/markdown-to-html/transform-markdown.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
*/
55

66
import {readFileSync, writeFileSync} from 'fs';
7+
import * as marked from 'marked';
78
import {join} from 'path';
9+
import {highlightCodeBlock} from '../highlight-files/highlight-code-block';
810
import {DocsMarkdownRenderer} from './docs-marked-renderer';
9-
import * as marked from 'marked';
1011

1112
// Regular expression that matches the markdown extension of a given path.
1213
const markdownExtension = /.md$/;
@@ -15,7 +16,7 @@ const markdownExtension = /.md$/;
1516
const markdownRenderer = new DocsMarkdownRenderer();
1617

1718
// Setup our custom docs renderer by default.
18-
marked.setOptions({renderer: markdownRenderer});
19+
marked.setOptions({renderer: markdownRenderer, highlight: highlightCodeBlock});
1920

2021
if (require.main === module) {
2122
// The script expects the bazel-bin path as first argument. All remaining arguments will be

0 commit comments

Comments
 (0)