From bb19dfc6859edc8e567b8142a872e80dd3e6e3e5 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Fri, 30 Nov 2018 00:46:46 +0100 Subject: [PATCH] build: highlight example sources files with bazel (#14325) Part of #13454 --- src/material-examples/BUILD.bazel | 6 ++ tools/highlight-files/BUILD.bazel | 21 +++++ .../highlight-code-block.ts | 0 tools/highlight-files/highlight-files.ts | 45 ++++++++++ tools/highlight-files/index.bzl | 83 +++++++++++++++++++ tools/highlight-files/tsconfig.json | 12 +++ tools/markdown-to-html/BUILD.bazel | 3 +- .../markdown-to-html/docs-marked-renderer.ts | 7 +- tools/markdown-to-html/transform-markdown.ts | 7 +- 9 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 tools/highlight-files/BUILD.bazel rename tools/{markdown-to-html => highlight-files}/highlight-code-block.ts (100%) create mode 100644 tools/highlight-files/highlight-files.ts create mode 100644 tools/highlight-files/index.bzl create mode 100644 tools/highlight-files/tsconfig.json diff --git a/src/material-examples/BUILD.bazel b/src/material-examples/BUILD.bazel index 351685a5f6e..4df064d9937 100644 --- a/src/material-examples/BUILD.bazel +++ b/src/material-examples/BUILD.bazel @@ -3,6 +3,7 @@ package(default_visibility=["//visibility:public"]) load("@angular//:index.bzl", "ng_package") load("//:packages.bzl", "CDK_TARGETS", "MATERIAL_TARGETS", "ROLLUP_GLOBALS") load("//tools:defaults.bzl", "ng_module") +load("//tools/highlight-files:index.bzl", "highlight_files") ng_module( name = "examples", @@ -21,6 +22,11 @@ ng_module( tsconfig = ":tsconfig-build.json", ) +highlight_files( + name = "highlighted-files", + srcs = glob(["*/*.html", "*/*.css", "*/*.ts"]) +) + ng_package( name = "npm_package", srcs = ["package.json"], diff --git a/tools/highlight-files/BUILD.bazel b/tools/highlight-files/BUILD.bazel new file mode 100644 index 00000000000..d35348f4e15 --- /dev/null +++ b/tools/highlight-files/BUILD.bazel @@ -0,0 +1,21 @@ +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("//tools:defaults.bzl", "ts_library") + +nodejs_binary( + name = "highlight-files", + entry_point = "angular_material/tools/highlight-files/highlight-files.js", + data = [ + "@matdeps//highlight.js", + "@matdeps//source-map-support", + ":sources", + ], +) + +ts_library( + name = "sources", + srcs = glob(["**/*.ts"]), + deps = ["@matdeps//@types/node"], + tsconfig = ":tsconfig.json", +) diff --git a/tools/markdown-to-html/highlight-code-block.ts b/tools/highlight-files/highlight-code-block.ts similarity index 100% rename from tools/markdown-to-html/highlight-code-block.ts rename to tools/highlight-files/highlight-code-block.ts diff --git a/tools/highlight-files/highlight-files.ts b/tools/highlight-files/highlight-files.ts new file mode 100644 index 00000000000..cc6eb7b5523 --- /dev/null +++ b/tools/highlight-files/highlight-files.ts @@ -0,0 +1,45 @@ +/** + * Script that will be used by the highlight_files Bazel rule in order to highlight + * multiple input files using highlight.js. The output will be HTML files. + */ + +import {readFileSync, writeFileSync} from 'fs'; +import {extname, join} from 'path'; +import {highlightCodeBlock} from './highlight-code-block'; + +/** + * Determines the command line arguments for the current Bazel action. Since this action can + * have a large set of input files, Bazel may write the arguments into a parameter file. + * This function is responsible for handling normal argument passing or Bazel parameter files. + * Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + */ +function getBazelActionArguments() { + const args = process.argv.slice(2); + + // If Bazel uses a parameter file, we've specified that it passes the file in the following + // format: "arg0 arg1 --param-file={path_to_param_file}" + if (args[0].startsWith('--param-file=')) { + return readFileSync(args[0].split('=')[1], 'utf8').trim().split('\n'); + } + + return args; +} + +if (require.main === module) { + // The script expects the bazel-bin path as first argument. All remaining arguments will be + // considered as markdown input files that need to be transformed. + const [bazelBinPath, ...inputFiles] = getBazelActionArguments(); + + // Walk through each input file and write transformed markdown output to the specified + // Bazel bin directory. + inputFiles.forEach(inputPath => { + const fileExtension = extname(inputPath).substring(1); + // Convert "my-component-example.ts" into "my-component-example-ts.html" + const baseOutputPath = inputPath.replace(`.${fileExtension}`, `-${fileExtension}.html`); + const outputPath = join(bazelBinPath, baseOutputPath); + const htmlOutput = highlightCodeBlock(readFileSync(inputPath, 'utf8'), fileExtension); + + writeFileSync(outputPath, htmlOutput); + }); + +} diff --git a/tools/highlight-files/index.bzl b/tools/highlight-files/index.bzl new file mode 100644 index 00000000000..a0fa7d0146a --- /dev/null +++ b/tools/highlight-files/index.bzl @@ -0,0 +1,83 @@ +""" + Gets a path relative to the specified label. This is achieved by just removing the label + package path from the specified path. e.g. the path is "guides/test/my-text.md" and the + label package is "guides/". The expected path would be "test/my-text.md". +""" +def _relative_to_label(label, short_path): + # TODO(devversion): extract into generic utility under tools/ + return short_path[len(label.package) + 1:] + +""" + Implementation of the "highlight_files" rule. The implementation runs the + highlight-files executable in order to highlight the specified source files. +""" +def _highlight_files(ctx): + input_files = ctx.files.srcs; + args = ctx.actions.args() + expected_outputs = []; + + # Do nothing if there are no input files. Bazel will throw if we schedule an action + # that returns no outputs. + if not input_files: + return None + + # Support passing arguments through a parameter file. This is necessary because on Windows + # there is an argument limit and we need to handle a large amount of input files. Bazel + # switches between parameter file and normal argument passing based on the operating system. + # Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + args.use_param_file(param_file_arg = "--param-file=%s") + + # Add the bazel bin directory to the command arguments. The script needs to know about + # the output directory because the input files are not in the same location as the bazel + # bin directory. + args.add(ctx.bin_dir.path) + + for input_file in input_files: + # Extension of the input file (e.g. "ts" or "css") + file_extension = input_file.extension + + # Determine the input file path relatively to the current package path. This is necessary + # because we want to preserve directories for the input files and `declare_file` expects a + # path that is relative to the current package. We remove the file extension including the dot + # because we will constructo an output file using a different extension. + relative_basepath = _relative_to_label(ctx.label, input_file.short_path)[ + :-len(file_extension) - 1] + + # Construct the output path from the relative basepath and file extension. For example: + # "autocomplete.ts" should result in "autocomplete-ts.html". + expected_outputs += [ + ctx.actions.declare_file("%s-%s.html" % (relative_basepath, file_extension)), + ] + + # Add the path for the input file to the command line arguments, so that the executable + # can process it. + args.add(input_file.path) + + # Run the highlight-files executable that highlights the specified source files. + ctx.actions.run( + inputs = input_files, + executable = ctx.executable._highlight_files, + outputs = expected_outputs, + arguments = [args], + ) + + return DefaultInfo(files = depset(expected_outputs)) + +""" + Rule definition for the "highlight_files" rule that can accept arbritary source files + that will be transformed into HTML files which reflect the highlighted source code of + the given files. The outputs can be referenced through the default output provider. +""" +highlight_files = rule( + implementation = _highlight_files, + attrs = { + "srcs": attr.label_list(allow_files = True), + + # Executable for this rule that is responsible for highlighting the specified + # input files. + "_highlight_files": attr.label( + default = Label("//tools/highlight-files"), + executable = True, + cfg = "host" + )}, +) diff --git a/tools/highlight-files/tsconfig.json b/tools/highlight-files/tsconfig.json new file mode 100644 index 00000000000..13dba047bbc --- /dev/null +++ b/tools/highlight-files/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "lib": ["es2015"], + "module": "commonjs", + "target": "es5", + "sourceMap": true, + "types": ["node"] + }, + "bazelOptions": { + "suppressTsconfigOverrideWarnings": true + } +} diff --git a/tools/markdown-to-html/BUILD.bazel b/tools/markdown-to-html/BUILD.bazel index e57bd8f5113..216077b1e18 100644 --- a/tools/markdown-to-html/BUILD.bazel +++ b/tools/markdown-to-html/BUILD.bazel @@ -8,7 +8,8 @@ ts_library( srcs = glob(["**/*.ts"]), deps = [ "@matdeps//@types/node", - "@matdeps//@types/marked" + "@matdeps//@types/marked", + "//tools/highlight-files:sources", ], tsconfig = ":tsconfig.json", ) diff --git a/tools/markdown-to-html/docs-marked-renderer.ts b/tools/markdown-to-html/docs-marked-renderer.ts index 6a4cc75c55b..21362086859 100644 --- a/tools/markdown-to-html/docs-marked-renderer.ts +++ b/tools/markdown-to-html/docs-marked-renderer.ts @@ -1,6 +1,5 @@ -import {MarkedOptions, Renderer} from 'marked'; +import {Renderer} from 'marked'; import {basename, extname} from 'path'; -import {highlightCodeBlock} from './highlight-code-block'; /** Regular expression that matches whitespace. */ const whitespaceRegex = /\W+/g; @@ -14,10 +13,6 @@ const exampleCommentRegex = //g; */ export class DocsMarkdownRenderer extends Renderer { - constructor(options?: MarkedOptions) { - super({highlight: highlightCodeBlock, baseUrl: 'material.angular.io/', ...options}); - } - /** * Transforms a markdown heading into the corresponding HTML output. In our case, we * want to create a header-link for each H3 and H4 heading. This allows users to jump to diff --git a/tools/markdown-to-html/transform-markdown.ts b/tools/markdown-to-html/transform-markdown.ts index 3c56d456113..819670d24e1 100644 --- a/tools/markdown-to-html/transform-markdown.ts +++ b/tools/markdown-to-html/transform-markdown.ts @@ -4,9 +4,10 @@ */ import {readFileSync, writeFileSync} from 'fs'; -import {join} from 'path'; -import {DocsMarkdownRenderer} from './docs-marked-renderer'; import * as marked from 'marked'; +import {join} from 'path'; +import {highlightCodeBlock} from '../highlight-files/highlight-code-block'; +import {DocsMarkdownRenderer} from './docs-marked-renderer'; // Regular expression that matches the markdown extension of a given path. const markdownExtension = /.md$/; @@ -15,7 +16,7 @@ const markdownExtension = /.md$/; const markdownRenderer = new DocsMarkdownRenderer(); // Setup our custom docs renderer by default. -marked.setOptions({renderer: markdownRenderer}); +marked.setOptions({renderer: markdownRenderer, highlight: highlightCodeBlock}); if (require.main === module) { // The script expects the bazel-bin path as first argument. All remaining arguments will be