diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e705c0e69..9847db1627 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,7 +113,7 @@ jobs: deploy: name: Deploy to production - needs: [test, linkcheck, firebase-validate] + needs: [test, excerpts, linkcheck, firebase-validate] runs-on: ubuntu-latest if: | github.event_name == 'push' diff --git a/src/_plugins/breadcrumb.rb b/src/_plugins/breadcrumb.rb deleted file mode 120000 index 76e90067fa..0000000000 --- a/src/_plugins/breadcrumb.rb +++ /dev/null @@ -1,136 +0,0 @@ -module Jekyll - - ## - # Used with permission. Based on code posted at - # biosphere.cc/software-engineering/jekyll-breadcrumbs-navigation-plugin/. - # - # Patch Jekyll's Page class - class Page - - ## - # We add a custom method to the page variable, that returns an ordered list of its - # parent pages ready for iteration. - def ancestors - # STDERR.puts "---------" - a = [] - url = self.url - # STDERR.puts "Page is #{url.inspect}" - if url.split(".")[-1] == "html" # ignore .css, .js, ... - while url != "/index.html" - pt = url.split("/") - if pt.length <= 2 then - url = "/index.html" - else - if pt[-1] != "index.html" then - # go to directory index - pt[-1] = "index.html" - url = pt.join("/") - else - # one level up - url = pt[0..-3].join("/") + "/index.html" - end - - # skip homepage - if url != "/index.html" then - potential_page = get_page_from_url(url) - - # skip missing index.html pages - if defined? potential_page.name then - a << potential_page - end - end - end - end - - if a != nil then - return a.reverse - else - return nil - end - end - end - - ## - # Make ancestors available in liquid - alias orig_to_liquid to_liquid - def to_liquid - h = orig_to_liquid - h['ancestors'] = self.ancestors - return h - end - - private - - ## - # Gets Page object that has given url. Very efficient O(n) solution. - def get_page_from_url(url) - site.pages.each do |page| - if page.url == url then - return page - end - end - end - end -end - - -module Drops - class BreadcrumbItem < Liquid::Drop - extend Forwardable - - def_delegator :@page, :data - def_delegator :@page, :url - - def initialize(page, payload) - @payload = payload - @page = page - end - - def title - @page.data["breadcrumb"] || @page.data["short-title"] || @page.data["title"] - end - - def subset - @page.data["subset"] - end - end -end - - -Jekyll::Hooks.register :pages, :pre_render do |page, payload| - drop = Drops::BreadcrumbItem - - if page.url == "/" - then payload["breadcrumbs"] = [ - drop.new(page, payload) - ] - else - payload["breadcrumbs"] = [] - pth = page.url.split("/") - - 0.upto(pth.size - 1) do |int| - joined_path = pth[0..int].join("/") - item = page.site.pages.find { |page_| joined_path == "" && page_.url == "/" || page_.url.chomp("/") == joined_path } - payload["breadcrumbs"] << drop.new(item, payload) if item - end - end -end - -Jekyll::Hooks.register :documents, :pre_render do |documents, payload| - drop = Drops::BreadcrumbItem - - if documents.url == "/" - then payload["breadcrumbs"] = [ - drop.new(documents, payload) - ] - else - payload["breadcrumbs"] = [] - pth = documents.url.split("/") - - 0.upto(pth.size - 1) do |int| - joined_path = pth[0..int].join("/") - item = documents.site.documents.find { |documents| joined_path == "" && documents.url == "/" || documents.url.chomp("/") == joined_path } - payload["breadcrumbs"] << drop.new(item, payload) if item - end - end -end diff --git a/src/_plugins/breadcrumb.rb b/src/_plugins/breadcrumb.rb new file mode 100644 index 0000000000..76e90067fa --- /dev/null +++ b/src/_plugins/breadcrumb.rb @@ -0,0 +1,136 @@ +module Jekyll + + ## + # Used with permission. Based on code posted at + # biosphere.cc/software-engineering/jekyll-breadcrumbs-navigation-plugin/. + # + # Patch Jekyll's Page class + class Page + + ## + # We add a custom method to the page variable, that returns an ordered list of its + # parent pages ready for iteration. + def ancestors + # STDERR.puts "---------" + a = [] + url = self.url + # STDERR.puts "Page is #{url.inspect}" + if url.split(".")[-1] == "html" # ignore .css, .js, ... + while url != "/index.html" + pt = url.split("/") + if pt.length <= 2 then + url = "/index.html" + else + if pt[-1] != "index.html" then + # go to directory index + pt[-1] = "index.html" + url = pt.join("/") + else + # one level up + url = pt[0..-3].join("/") + "/index.html" + end + + # skip homepage + if url != "/index.html" then + potential_page = get_page_from_url(url) + + # skip missing index.html pages + if defined? potential_page.name then + a << potential_page + end + end + end + end + + if a != nil then + return a.reverse + else + return nil + end + end + end + + ## + # Make ancestors available in liquid + alias orig_to_liquid to_liquid + def to_liquid + h = orig_to_liquid + h['ancestors'] = self.ancestors + return h + end + + private + + ## + # Gets Page object that has given url. Very efficient O(n) solution. + def get_page_from_url(url) + site.pages.each do |page| + if page.url == url then + return page + end + end + end + end +end + + +module Drops + class BreadcrumbItem < Liquid::Drop + extend Forwardable + + def_delegator :@page, :data + def_delegator :@page, :url + + def initialize(page, payload) + @payload = payload + @page = page + end + + def title + @page.data["breadcrumb"] || @page.data["short-title"] || @page.data["title"] + end + + def subset + @page.data["subset"] + end + end +end + + +Jekyll::Hooks.register :pages, :pre_render do |page, payload| + drop = Drops::BreadcrumbItem + + if page.url == "/" + then payload["breadcrumbs"] = [ + drop.new(page, payload) + ] + else + payload["breadcrumbs"] = [] + pth = page.url.split("/") + + 0.upto(pth.size - 1) do |int| + joined_path = pth[0..int].join("/") + item = page.site.pages.find { |page_| joined_path == "" && page_.url == "/" || page_.url.chomp("/") == joined_path } + payload["breadcrumbs"] << drop.new(item, payload) if item + end + end +end + +Jekyll::Hooks.register :documents, :pre_render do |documents, payload| + drop = Drops::BreadcrumbItem + + if documents.url == "/" + then payload["breadcrumbs"] = [ + drop.new(documents, payload) + ] + else + payload["breadcrumbs"] = [] + pth = documents.url.split("/") + + 0.upto(pth.size - 1) do |int| + joined_path = pth[0..int].join("/") + item = documents.site.documents.find { |documents| joined_path == "" && documents.url == "/" || documents.url.chomp("/") == joined_path } + payload["breadcrumbs"] << drop.new(item, payload) if item + end + end +end diff --git a/src/_plugins/code_diff.rb b/src/_plugins/code_diff.rb deleted file mode 120000 index 93f7f2e40f..0000000000 --- a/src/_plugins/code_diff.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2018, the project authors. Please see the AUTHORS file -# for details. All rights reserved. Use of this source code is governed by a -# BSD-style license that can be found in the LICENSE file. - -require 'liquid/tag/parser' # https://github.com/envygeeks/liquid-tag-parser -require_relative 'code_diff_core' - -module Jekyll - - module Tags - - class CodeDiff < Liquid::Block - - def initialize(tag_name, string_of_args, tokens) - super - @args = Liquid::Tag::Parser.new(string_of_args).args - @log_diffs = false - end - - def render(liquid_context) - helper = DartSite::CodeDiffCore.new - helper.render(@args, super) - end - - end - end -end - -Liquid::Template.register_tag('diff'.freeze, Jekyll::Tags::CodeDiff) - diff --git a/src/_plugins/code_diff.rb b/src/_plugins/code_diff.rb new file mode 100644 index 0000000000..93f7f2e40f --- /dev/null +++ b/src/_plugins/code_diff.rb @@ -0,0 +1,30 @@ +# Copyright (c) 2018, the project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +require 'liquid/tag/parser' # https://github.com/envygeeks/liquid-tag-parser +require_relative 'code_diff_core' + +module Jekyll + + module Tags + + class CodeDiff < Liquid::Block + + def initialize(tag_name, string_of_args, tokens) + super + @args = Liquid::Tag::Parser.new(string_of_args).args + @log_diffs = false + end + + def render(liquid_context) + helper = DartSite::CodeDiffCore.new + helper.render(@args, super) + end + + end + end +end + +Liquid::Template.register_tag('diff'.freeze, Jekyll::Tags::CodeDiff) + diff --git a/src/_plugins/code_diff_core.rb b/src/_plugins/code_diff_core.rb deleted file mode 120000 index 477bf50792..0000000000 --- a/src/_plugins/code_diff_core.rb +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (c) 2018, the project authors. Please see the AUTHORS file -# for details. All rights reserved. Use of this source code is governed by a -# BSD-style license that can be found in the LICENSE file. - -require 'nokogiri' -require 'open3' -require_relative 'dart_site_util' - -module DartSite - - class CodeDiffCore - - def initialize - @log_diffs = false - end - - def render(args, diff) - return '' if diff.empty? - - # Get the indentation before the closing tag. - indentation = _get_indentation_string(diff) - - diff = DartSite::Util.trim_min_leading_space(diff) - lines = _diff(diff, args).split(/\n/) - - _log_puts ">> CodeDiff content (#{args}):\n#{diff}\n---\n" if @log_diffs - - # We're rendering to markdown, and we don't want the diff table HTML - # to be adjacent to any text, otherwise the text might not be rendered - # as a paragraph (e.g., esp. if inside an
  • ). - lines.unshift('') - lines.push('') - - # Indent the table in case the diff is inside a markdown list, - # which has its content indented. - indented_lines = lines.map{|s| indentation + s} - indented_lines.join("\n") - end - - def _diff(unified_diff_text, args) - _log_puts ">> Diff input:\n#{unified_diff_text}" if @log_diffs - begin - o, e, _s = Open3.capture3('npx diff2html --su hidden -i stdin -o stdout', - stdin_data: unified_diff_text) - _log_puts e if e.length > 0 - rescue Errno::ENOENT => _e - raise "** ERROR: diff2html isn't installed or could not be found. " \ - 'To install with NPM run: npm install -g diff2html-cli' - end - doc = Nokogiri::HTML(o) - doc.css('div.d2h-file-header span.d2h-tag').remove - diff_html = doc.search('.d2h-wrapper') - _trim_diff(diff_html, args) if args[:from] || args[:to] - _log_puts "Diff output:\n#{diff_html.to_s[0, [diff_html.to_s.length, 100].min]}...\n" if @log_diffs - diff_html.to_s - end - - def _trim_diff(diff_html, args) - # The code updater truncates the diff after `to`. Only trim before `from` here. - # (We don't trim after `to` here because of an unwanted optimizing behavior of diff2html.) - _log_puts ">>> from='#{args[:from]}' to='#{args[:to]}'" if @log_diffs - inside_matching_lines = done = false - diff_html.css('tbody.d2h-diff-tbody tr').each do |tr| - if tr.text.strip.start_with?('@') - tr.remove - next - end - code_line = tr.xpath('td[2]//span').text - inside_matching_lines = true if !done && !inside_matching_lines && code_line.match(args[:from] || '.') - saved_inside_matching_lines = inside_matching_lines - # if inside_matching_lines && args[:to] && code_line.match(args[:to]) - # inside_matching_lines = false - # done = true; - # end - _log_puts ">>> tr (#{saved_inside_matching_lines}) #{code_line} -> #{tr.text.gsub(/\s+/, ' ')}" if @log_diffs - tr.remove unless saved_inside_matching_lines - end - end - - def _get_indentation_string(diff) - lines = diff.split(/\n/, -1) - # Try to figure out if the diff is part of a Jekyll block or a markdown block. - # For a Jekyll block, figure out the indentation from the whitespace before - # the block closing tag. Otherwise, look at the indentation before the first line - # (which should be a file specifier of the form '--- 1-base/lib/main.dart ...'). - line = lines.last.match?(/^[ \t]*$/) ? lines.last : lines[0] - DartSite::Util.get_indentation_string(line) - end - - def _log_puts(s) - puts(s) - end - - end -end diff --git a/src/_plugins/code_diff_core.rb b/src/_plugins/code_diff_core.rb new file mode 100644 index 0000000000..477bf50792 --- /dev/null +++ b/src/_plugins/code_diff_core.rb @@ -0,0 +1,95 @@ +# Copyright (c) 2018, the project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +require 'nokogiri' +require 'open3' +require_relative 'dart_site_util' + +module DartSite + + class CodeDiffCore + + def initialize + @log_diffs = false + end + + def render(args, diff) + return '' if diff.empty? + + # Get the indentation before the closing tag. + indentation = _get_indentation_string(diff) + + diff = DartSite::Util.trim_min_leading_space(diff) + lines = _diff(diff, args).split(/\n/) + + _log_puts ">> CodeDiff content (#{args}):\n#{diff}\n---\n" if @log_diffs + + # We're rendering to markdown, and we don't want the diff table HTML + # to be adjacent to any text, otherwise the text might not be rendered + # as a paragraph (e.g., esp. if inside an
  • ). + lines.unshift('') + lines.push('') + + # Indent the table in case the diff is inside a markdown list, + # which has its content indented. + indented_lines = lines.map{|s| indentation + s} + indented_lines.join("\n") + end + + def _diff(unified_diff_text, args) + _log_puts ">> Diff input:\n#{unified_diff_text}" if @log_diffs + begin + o, e, _s = Open3.capture3('npx diff2html --su hidden -i stdin -o stdout', + stdin_data: unified_diff_text) + _log_puts e if e.length > 0 + rescue Errno::ENOENT => _e + raise "** ERROR: diff2html isn't installed or could not be found. " \ + 'To install with NPM run: npm install -g diff2html-cli' + end + doc = Nokogiri::HTML(o) + doc.css('div.d2h-file-header span.d2h-tag').remove + diff_html = doc.search('.d2h-wrapper') + _trim_diff(diff_html, args) if args[:from] || args[:to] + _log_puts "Diff output:\n#{diff_html.to_s[0, [diff_html.to_s.length, 100].min]}...\n" if @log_diffs + diff_html.to_s + end + + def _trim_diff(diff_html, args) + # The code updater truncates the diff after `to`. Only trim before `from` here. + # (We don't trim after `to` here because of an unwanted optimizing behavior of diff2html.) + _log_puts ">>> from='#{args[:from]}' to='#{args[:to]}'" if @log_diffs + inside_matching_lines = done = false + diff_html.css('tbody.d2h-diff-tbody tr').each do |tr| + if tr.text.strip.start_with?('@') + tr.remove + next + end + code_line = tr.xpath('td[2]//span').text + inside_matching_lines = true if !done && !inside_matching_lines && code_line.match(args[:from] || '.') + saved_inside_matching_lines = inside_matching_lines + # if inside_matching_lines && args[:to] && code_line.match(args[:to]) + # inside_matching_lines = false + # done = true; + # end + _log_puts ">>> tr (#{saved_inside_matching_lines}) #{code_line} -> #{tr.text.gsub(/\s+/, ' ')}" if @log_diffs + tr.remove unless saved_inside_matching_lines + end + end + + def _get_indentation_string(diff) + lines = diff.split(/\n/, -1) + # Try to figure out if the diff is part of a Jekyll block or a markdown block. + # For a Jekyll block, figure out the indentation from the whitespace before + # the block closing tag. Otherwise, look at the indentation before the first line + # (which should be a file specifier of the form '--- 1-base/lib/main.dart ...'). + line = lines.last.match?(/^[ \t]*$/) ? lines.last : lines[0] + DartSite::Util.get_indentation_string(line) + end + + def _log_puts(s) + puts(s) + end + + end +end diff --git a/src/_plugins/code_excerpt_framer.rb b/src/_plugins/code_excerpt_framer.rb deleted file mode 120000 index c5d267295b..0000000000 --- a/src/_plugins/code_excerpt_framer.rb +++ /dev/null @@ -1,44 +0,0 @@ -module DartSite - - # Takes the given code excerpt (with the given attributes) and creates - # some framing HTML: e.g., a div with possible excerpt title in a header. - class CodeExcerptFramer - def frame_code(title, classes, attrs, escaped_code, indent, secondary_class) - _unindented_template(title, classes, attrs, escaped_code.gsub('\\','\\\\\\\\'), secondary_class) - end - - private - # @param [String] div_classes, in the form "foo bar" - # @param [Hash] attrs: attributes as attribute-name/value pairs. - def _unindented_template(title, _div_classes, attrs, escaped_code, secondary_class) - div_classes = ['code-excerpt'] - div_classes << _div_classes if _div_classes - - pre_classes = attrs[:class] || [] - pre_classes.unshift("lang-#{attrs[:lang]}") if attrs[:lang] - pre_classes.unshift('prettyprint') - - # Was: escaped_code!n - # Also had: - - # Ensure that the template starts and ends with a blank line. - # We're rendering to markdown, and we don't want the frame HTML - # to be adjacent to any text, otherwise the text might not be rendered - # as a paragraph (e.g., esp. if inside an
  • ). - <<~TEMPLATE.gsub(/!n\s*/,'').sub(/\bescaped_code\b/,escaped_code) - -
    - #{title ? "
    #{title}
    " : '!n'} -
    !n -
    !n
    -            !n
    -              escaped_code!n
    -            !n
    -          
    !n -
    -
    - - TEMPLATE - end - end -end diff --git a/src/_plugins/code_excerpt_framer.rb b/src/_plugins/code_excerpt_framer.rb new file mode 100644 index 0000000000..c5d267295b --- /dev/null +++ b/src/_plugins/code_excerpt_framer.rb @@ -0,0 +1,44 @@ +module DartSite + + # Takes the given code excerpt (with the given attributes) and creates + # some framing HTML: e.g., a div with possible excerpt title in a header. + class CodeExcerptFramer + def frame_code(title, classes, attrs, escaped_code, indent, secondary_class) + _unindented_template(title, classes, attrs, escaped_code.gsub('\\','\\\\\\\\'), secondary_class) + end + + private + # @param [String] div_classes, in the form "foo bar" + # @param [Hash] attrs: attributes as attribute-name/value pairs. + def _unindented_template(title, _div_classes, attrs, escaped_code, secondary_class) + div_classes = ['code-excerpt'] + div_classes << _div_classes if _div_classes + + pre_classes = attrs[:class] || [] + pre_classes.unshift("lang-#{attrs[:lang]}") if attrs[:lang] + pre_classes.unshift('prettyprint') + + # Was: escaped_code!n + # Also had: + + # Ensure that the template starts and ends with a blank line. + # We're rendering to markdown, and we don't want the frame HTML + # to be adjacent to any text, otherwise the text might not be rendered + # as a paragraph (e.g., esp. if inside an
  • ). + <<~TEMPLATE.gsub(/!n\s*/,'').sub(/\bescaped_code\b/,escaped_code) + +
    + #{title ? "
    #{title}
    " : '!n'} +
    !n +
    !n
    +            !n
    +              escaped_code!n
    +            !n
    +          
    !n +
    +
    + + TEMPLATE + end + end +end diff --git a/src/_plugins/code_excerpt_processor.rb b/src/_plugins/code_excerpt_processor.rb deleted file mode 120000 index 940b8ec786..0000000000 --- a/src/_plugins/code_excerpt_processor.rb +++ /dev/null @@ -1,276 +0,0 @@ -## -## Classes that support the processing of and -## instructions. -## - -require 'active_support' -require 'active_support/isolated_execution_state' -require 'active_support/core_ext/string' -require 'open3' -require 'nokogiri' -require 'yaml' -require_relative 'code_diff_core' -require_relative 'dart_site_util' - -module DartSite - - class CodeExcerptProcessor - - # @param code_framer is used to wrap code blocks with HTML that provides - # features like code block headers and a copy-code button. - def initialize(code_framer) - @@log_file_name = 'code-excerpt-log.txt' - @@log_entry_count = 0 - @log_diffs = false - - File.delete(@@log_file_name) if File.exist?(@@log_file_name) - - # @site_title = Jekyll.configuration({})['title'] - @code_differ = DartSite::CodeDiffCore.new - @code_framer = code_framer - end - - def code_excerpt_regex - /^(\s*(<\?(code-\w+)[^>]*>)\n)((\s*)```((\w*)([^\n]*))\n(.*?)\n(\s*)```\n?)?/m; - end - - def code_excerpt_processing_init - @path_base = '' - end - - def process_code_excerpt(match) - # pi_line_with_whitespace = match[1] - pi = match[2] # full processing instruction - pi_name = match[3] - args = process_pi_args(pi) - optional_code_block = match[4] - indent = match[5] - secondary_class = match[6] - lang = !match[7] || match[7].empty? ? (args['ext'] || 'nocode') : match[7] - attrs = mk_code_example_directive_attr(lang, args['linenums']) - - return process_code_pane(pi, attrs, args) if pi_name == 'code-pane' - - if pi_name != 'code-excerpt' - log_puts "Warning: unrecognized instruction: #{pi}" - return match[0] - elsif !optional_code_block - # w/o a code block assume it is a set cmd - process_set_command(pi, args) - return '' - end - - code = match[9] - leading_whitespace = get_indentation_string(optional_code_block) - code = Util.trim_min_leading_space(code) - - if lang == 'diff' - diff = @code_differ.render(args, code) - diff.indent!(leading_whitespace.length) if leading_whitespace - return diff - end - - title = args['title'] - classes = args['class'] - - # We escape all code fragments (not just HTML fragments), - # because we're rendering the code block as HTML. - escaped_code = CGI.escapeHTML(code) - - code = @code_framer.frame_code(title, classes, attrs, _process_highlight_markers(escaped_code), indent, secondary_class) - code.indent!(leading_whitespace.length) if leading_whitespace - code - end - - def _process_highlight_markers(s) - # Only replace [! and !] if both exist - s.gsub(/\[!(.*?)!\]/m, '\1') - end - - def trim_min_leading_space(code) - lines = code.split(/\n/); - non_blank_lines = lines.reject { |s| s.match(/^\s*$/) } - - # Length of leading spaces to be trimmed - len = non_blank_lines.map{ |s| - matches = s.match(/^[ \t]*/) - matches ? matches[0].length : 0 }.min - - len == 0 ? code : lines.map{|s| s.length < len ? s : s[len..-1]}.join("\n") - end - - # @return [Hash] of attributes as attribute-name/value pairs. - # Supported attribute names (all optional): - # - [Array] `:class` - # - [String] `:lang` - def mk_code_example_directive_attr(lang, linenums) - classes = [] - classes << 'linenums' if linenums - classes << 'nocode' if lang == 'nocode' - attrs = {} - attrs[:class] = classes unless classes.empty? - attrs[:lang] = lang unless lang == 'nocode' - attrs - end - - # @param [Hash] attrs: attributes as attribute-name/value pairs. - # @return [String] Attributes as a single string: 'foo="bar" baz="..."' - def attr_str(attrs) - attributes = [] - attrs.each do |name, value| - attr_as_s = name.to_s - value *= ' ' if value.kind_of?(Array) - attr_as_s += %Q(="#{value}") if value - attributes << attr_as_s - end - attributes * ' ' - end - - def process_pi_args(pi) - # match = /<\?code-\w+\s*(("([^"]*)")?((\s+[-\w]+="[^"]*"\s*)*))\?>/.match(pi) - match = /<\?code-\w+\s*(.*?)\s*\?>/.match(pi) - unless match - log_puts "ERROR: improperly formatted instruction: #{pi}" - return nil - end - - arg_string = match[1] - args = { } - - # First argument can be unnamed. When present, it is saved as - # args['']. It is used to define a path and an optional region. - match = /^"(([^("]*)(\s+\(([^"]+)\))?)"/.match(arg_string) - if match - arg_string = $' # reset to remaining args - args[''] = match[1] - path = args['path'] = match[2] - args['ext'] = File.extname(path)&.sub(/^\./,'') - args['region'] = match[4]&.gsub(/[^\w]+/, '-') || '' - end - - # Process remaining args - arg_string.scan(/\b(\w[-\w]*)(="([^"]*)")?/) { |id,arg,val| - if id == 'title' && !arg then val = trim_file_vers(args['']) end - args[id] = val || '' - } - # puts " >> args: #{args}" - args - end - - # @param [Hash] attrs: attributes as attribute-name/value pairs. - def process_code_pane(pi, _attrs, args) - # TODO: support use of globally set replace args. - title = args['title'] || trim_file_vers(args['']) - escaped_code = get_code_frag(args['path'], - full_frag_path(args['path'], args['region']), - src_path(args['path'], args['region']), - args['region']) - # args['replace'] syntax: /regex/replacement/g - # Replacement and '_g' are currently mandatory (but not checked) - if args['replace'] - _, re, replacement, _g = args['replace'].split '/' - escaped_code.gsub!(Regexp.new(re)) { - match = Regexp.last_match - # TODO: doesn't yet recognize escaped '$' ('\$') - while (arg_match = /(?<=\$)(\d)(?!\d)/.match(replacement)) do - next unless arg_match - replacement.gsub!("$#{arg_match[0]}", match[arg_match[0].to_i]) - end - if /\$\d+|\\\$/.match?(replacement) - raise "Plugin doesn't support \\$, or more than 9 match groups $1, ..., $9: #{replacement}.\nAborting." - end - if replacement.include? '$&' - replacement.gsub('$&', match[0]) - else - replacement - end - } - end - escaped_code = _process_highlight_markers(escaped_code) - attrs = {} - attrs[:language] = _attrs[:lang] if _attrs[:lang] - attrs[:format] = _attrs[:class] if _attrs[:class] - <<~TEMPLATE - #{pi} - #{escaped_code} - TEMPLATE - end - - def process_set_command(_pi, args) - # Ignore all commands other than path-base. - path_base = args['path-base'] - return unless path_base - @path_base = path_base.sub(/\/$/, '') - # puts ">> path base set to '#{@path_base}'" - end - - def get_code_frag(proj_rel_path, _frag_path, src_path, region) - excerpt_yaml_path = File.join(Dir.pwd, 'tmp', '_fragments', @path_base, proj_rel_path + '.excerpt.yaml'); - if File.exist? excerpt_yaml_path - yaml = YAML.load_file(excerpt_yaml_path) - result = yaml[region] - if result.nil? - result = "CODE EXCERPT not found: region '#{region}' not found in #{excerpt_yaml_path}" - log_puts result - else - lines = result.split(/(?<=\n)/) # split and keep separator - result = escape_and_trim_code(lines) - end - # We don't generate frag_path fragments anymore: - # elsif File.exists? frag_path - # lines = File.readlines frag_path - # result = escapeAndTrimCode(lines) - elsif region.empty? && src_path && (File.exist? src_path) - lines = File.readlines src_path - result = escape_and_trim_code(lines) - raise 'CODE EXCERPT not found: no .excerpt.yaml file ' \ - "and source contains docregions: #{src_path}" if result.include? '#docregion' - else - result = "CODE EXCERPT not found: #{excerpt_yaml_path}, region='#{region}'" - log_puts result - end - result - end - - def full_frag_path(proj_rel_path, region) - frag_rel_path = File.join(@path_base, proj_rel_path) - if region && !region.empty? - dir = File.dirname(frag_rel_path) - basename = File.basename(frag_rel_path, '.*') - ext = File.extname(frag_rel_path) - frag_rel_path = File.join(dir, "#{basename}-#{region}#{ext}") - end - frag_extension = '.txt' - File.join(Dir.pwd, 'tmp', '_fragments', frag_rel_path + frag_extension) - end - - def src_path(proj_rel_path, region) - region == '' ? File.join(@path_base, proj_rel_path) : nil - end - - def escape_and_trim_code(lines) - # Skip blank lines at the end too - while !lines.empty? && lines.last.strip == '' do lines.pop end - CGI.escapeHTML(lines.join) - end - - def log_puts(s) - puts(s) - file_mode = (@@log_entry_count += 1) <= 1 ? 'w' : 'a' - File.open(@@log_file_name, file_mode) do |logFile| logFile.puts(s) end - end - - def trim_file_vers(s) - # Path/title like styles.1.css or foo_1.dart? Then drop the '.1' or '_1' qualifier: - match = /^(.*)[._]\d(\.\w+)(\s+.+)?$/.match(s) - s = "#{match[1]}#{match[2]}#{match[3]}" if match - s - end - - def get_indentation_string(s) - match = s.match(/^[ \t]*/) - match ? match[0] : nil - end - - end -end diff --git a/src/_plugins/code_excerpt_processor.rb b/src/_plugins/code_excerpt_processor.rb new file mode 100644 index 0000000000..940b8ec786 --- /dev/null +++ b/src/_plugins/code_excerpt_processor.rb @@ -0,0 +1,276 @@ +## +## Classes that support the processing of and +## instructions. +## + +require 'active_support' +require 'active_support/isolated_execution_state' +require 'active_support/core_ext/string' +require 'open3' +require 'nokogiri' +require 'yaml' +require_relative 'code_diff_core' +require_relative 'dart_site_util' + +module DartSite + + class CodeExcerptProcessor + + # @param code_framer is used to wrap code blocks with HTML that provides + # features like code block headers and a copy-code button. + def initialize(code_framer) + @@log_file_name = 'code-excerpt-log.txt' + @@log_entry_count = 0 + @log_diffs = false + + File.delete(@@log_file_name) if File.exist?(@@log_file_name) + + # @site_title = Jekyll.configuration({})['title'] + @code_differ = DartSite::CodeDiffCore.new + @code_framer = code_framer + end + + def code_excerpt_regex + /^(\s*(<\?(code-\w+)[^>]*>)\n)((\s*)```((\w*)([^\n]*))\n(.*?)\n(\s*)```\n?)?/m; + end + + def code_excerpt_processing_init + @path_base = '' + end + + def process_code_excerpt(match) + # pi_line_with_whitespace = match[1] + pi = match[2] # full processing instruction + pi_name = match[3] + args = process_pi_args(pi) + optional_code_block = match[4] + indent = match[5] + secondary_class = match[6] + lang = !match[7] || match[7].empty? ? (args['ext'] || 'nocode') : match[7] + attrs = mk_code_example_directive_attr(lang, args['linenums']) + + return process_code_pane(pi, attrs, args) if pi_name == 'code-pane' + + if pi_name != 'code-excerpt' + log_puts "Warning: unrecognized instruction: #{pi}" + return match[0] + elsif !optional_code_block + # w/o a code block assume it is a set cmd + process_set_command(pi, args) + return '' + end + + code = match[9] + leading_whitespace = get_indentation_string(optional_code_block) + code = Util.trim_min_leading_space(code) + + if lang == 'diff' + diff = @code_differ.render(args, code) + diff.indent!(leading_whitespace.length) if leading_whitespace + return diff + end + + title = args['title'] + classes = args['class'] + + # We escape all code fragments (not just HTML fragments), + # because we're rendering the code block as HTML. + escaped_code = CGI.escapeHTML(code) + + code = @code_framer.frame_code(title, classes, attrs, _process_highlight_markers(escaped_code), indent, secondary_class) + code.indent!(leading_whitespace.length) if leading_whitespace + code + end + + def _process_highlight_markers(s) + # Only replace [! and !] if both exist + s.gsub(/\[!(.*?)!\]/m, '\1') + end + + def trim_min_leading_space(code) + lines = code.split(/\n/); + non_blank_lines = lines.reject { |s| s.match(/^\s*$/) } + + # Length of leading spaces to be trimmed + len = non_blank_lines.map{ |s| + matches = s.match(/^[ \t]*/) + matches ? matches[0].length : 0 }.min + + len == 0 ? code : lines.map{|s| s.length < len ? s : s[len..-1]}.join("\n") + end + + # @return [Hash] of attributes as attribute-name/value pairs. + # Supported attribute names (all optional): + # - [Array] `:class` + # - [String] `:lang` + def mk_code_example_directive_attr(lang, linenums) + classes = [] + classes << 'linenums' if linenums + classes << 'nocode' if lang == 'nocode' + attrs = {} + attrs[:class] = classes unless classes.empty? + attrs[:lang] = lang unless lang == 'nocode' + attrs + end + + # @param [Hash] attrs: attributes as attribute-name/value pairs. + # @return [String] Attributes as a single string: 'foo="bar" baz="..."' + def attr_str(attrs) + attributes = [] + attrs.each do |name, value| + attr_as_s = name.to_s + value *= ' ' if value.kind_of?(Array) + attr_as_s += %Q(="#{value}") if value + attributes << attr_as_s + end + attributes * ' ' + end + + def process_pi_args(pi) + # match = /<\?code-\w+\s*(("([^"]*)")?((\s+[-\w]+="[^"]*"\s*)*))\?>/.match(pi) + match = /<\?code-\w+\s*(.*?)\s*\?>/.match(pi) + unless match + log_puts "ERROR: improperly formatted instruction: #{pi}" + return nil + end + + arg_string = match[1] + args = { } + + # First argument can be unnamed. When present, it is saved as + # args['']. It is used to define a path and an optional region. + match = /^"(([^("]*)(\s+\(([^"]+)\))?)"/.match(arg_string) + if match + arg_string = $' # reset to remaining args + args[''] = match[1] + path = args['path'] = match[2] + args['ext'] = File.extname(path)&.sub(/^\./,'') + args['region'] = match[4]&.gsub(/[^\w]+/, '-') || '' + end + + # Process remaining args + arg_string.scan(/\b(\w[-\w]*)(="([^"]*)")?/) { |id,arg,val| + if id == 'title' && !arg then val = trim_file_vers(args['']) end + args[id] = val || '' + } + # puts " >> args: #{args}" + args + end + + # @param [Hash] attrs: attributes as attribute-name/value pairs. + def process_code_pane(pi, _attrs, args) + # TODO: support use of globally set replace args. + title = args['title'] || trim_file_vers(args['']) + escaped_code = get_code_frag(args['path'], + full_frag_path(args['path'], args['region']), + src_path(args['path'], args['region']), + args['region']) + # args['replace'] syntax: /regex/replacement/g + # Replacement and '_g' are currently mandatory (but not checked) + if args['replace'] + _, re, replacement, _g = args['replace'].split '/' + escaped_code.gsub!(Regexp.new(re)) { + match = Regexp.last_match + # TODO: doesn't yet recognize escaped '$' ('\$') + while (arg_match = /(?<=\$)(\d)(?!\d)/.match(replacement)) do + next unless arg_match + replacement.gsub!("$#{arg_match[0]}", match[arg_match[0].to_i]) + end + if /\$\d+|\\\$/.match?(replacement) + raise "Plugin doesn't support \\$, or more than 9 match groups $1, ..., $9: #{replacement}.\nAborting." + end + if replacement.include? '$&' + replacement.gsub('$&', match[0]) + else + replacement + end + } + end + escaped_code = _process_highlight_markers(escaped_code) + attrs = {} + attrs[:language] = _attrs[:lang] if _attrs[:lang] + attrs[:format] = _attrs[:class] if _attrs[:class] + <<~TEMPLATE + #{pi} + #{escaped_code} + TEMPLATE + end + + def process_set_command(_pi, args) + # Ignore all commands other than path-base. + path_base = args['path-base'] + return unless path_base + @path_base = path_base.sub(/\/$/, '') + # puts ">> path base set to '#{@path_base}'" + end + + def get_code_frag(proj_rel_path, _frag_path, src_path, region) + excerpt_yaml_path = File.join(Dir.pwd, 'tmp', '_fragments', @path_base, proj_rel_path + '.excerpt.yaml'); + if File.exist? excerpt_yaml_path + yaml = YAML.load_file(excerpt_yaml_path) + result = yaml[region] + if result.nil? + result = "CODE EXCERPT not found: region '#{region}' not found in #{excerpt_yaml_path}" + log_puts result + else + lines = result.split(/(?<=\n)/) # split and keep separator + result = escape_and_trim_code(lines) + end + # We don't generate frag_path fragments anymore: + # elsif File.exists? frag_path + # lines = File.readlines frag_path + # result = escapeAndTrimCode(lines) + elsif region.empty? && src_path && (File.exist? src_path) + lines = File.readlines src_path + result = escape_and_trim_code(lines) + raise 'CODE EXCERPT not found: no .excerpt.yaml file ' \ + "and source contains docregions: #{src_path}" if result.include? '#docregion' + else + result = "CODE EXCERPT not found: #{excerpt_yaml_path}, region='#{region}'" + log_puts result + end + result + end + + def full_frag_path(proj_rel_path, region) + frag_rel_path = File.join(@path_base, proj_rel_path) + if region && !region.empty? + dir = File.dirname(frag_rel_path) + basename = File.basename(frag_rel_path, '.*') + ext = File.extname(frag_rel_path) + frag_rel_path = File.join(dir, "#{basename}-#{region}#{ext}") + end + frag_extension = '.txt' + File.join(Dir.pwd, 'tmp', '_fragments', frag_rel_path + frag_extension) + end + + def src_path(proj_rel_path, region) + region == '' ? File.join(@path_base, proj_rel_path) : nil + end + + def escape_and_trim_code(lines) + # Skip blank lines at the end too + while !lines.empty? && lines.last.strip == '' do lines.pop end + CGI.escapeHTML(lines.join) + end + + def log_puts(s) + puts(s) + file_mode = (@@log_entry_count += 1) <= 1 ? 'w' : 'a' + File.open(@@log_file_name, file_mode) do |logFile| logFile.puts(s) end + end + + def trim_file_vers(s) + # Path/title like styles.1.css or foo_1.dart? Then drop the '.1' or '_1' qualifier: + match = /^(.*)[._]\d(\.\w+)(\s+.+)?$/.match(s) + s = "#{match[1]}#{match[2]}#{match[3]}" if match + s + end + + def get_indentation_string(s) + match = s.match(/^[ \t]*/) + match ? match[0] : nil + end + + end +end diff --git a/src/_plugins/dart_site_util.rb b/src/_plugins/dart_site_util.rb deleted file mode 120000 index 0f34e996f4..0000000000 --- a/src/_plugins/dart_site_util.rb +++ /dev/null @@ -1,53 +0,0 @@ -module DartSite - - class Util - - def self.get_indentation_string(s) - s.match(/^[ \t]*/)[0] - end - - # String to string transformation. - def self.trim_min_leading_space(code) - lines = code.split(/\n/); - non_blank_lines = lines.reject { |s| s.match(/^\s*$/) } - - # Length of leading spaces to be trimmed - len = non_blank_lines.map{ |s| - matches = s.match(/^[ \t]*/) - matches ? matches[0].length : 0 }.min - - len == 0 ? code : lines.map{|s| s.length < len ? s : s[len..-1]}.join("\n") - end - - # This method is for trimming the content of Jekyll blocks. - # - # @return a copy of the input lines array, with lines unindented by the - # maximal amount of whitespace possible without affecting relative - # (non-whitespace) line indentation. Also trim off leading and trailing blank lines. - def self.block_trim_leading_whitespace(lines) - # 1. Trim leading blank lines - while lines.first =~ /^\s*$/ do lines.shift; end - - # 2. Trim trailing blank lines. Also determine minimal - # indentation for the entire block. - - # Last line should consist of the indentation of the end tag - # (when it is on a separate line). - last_line = lines.last =~ /^\s*$/ ? lines.pop : '' - while lines.last =~ /^\s*$/ do lines.pop end - min_len = last_line.length - - non_blank_lines = lines.reject { |s| s.match(/^\s*$/) } - - # 3. Determine length of leading spaces to be trimmed - len = non_blank_lines.map{ |s| - matches = s.match(/^[ \t]*/) - matches ? matches[0].length : 0 }.min - - # Only trim the excess relative to min_len - len = len < min_len ? min_len : len - min_len - - len == 0 ? lines : lines.map{|s| s.length < len ? s : s.sub(/^[ \t]{#{len}}/, '')} - end - end -end diff --git a/src/_plugins/dart_site_util.rb b/src/_plugins/dart_site_util.rb new file mode 100644 index 0000000000..0f34e996f4 --- /dev/null +++ b/src/_plugins/dart_site_util.rb @@ -0,0 +1,53 @@ +module DartSite + + class Util + + def self.get_indentation_string(s) + s.match(/^[ \t]*/)[0] + end + + # String to string transformation. + def self.trim_min_leading_space(code) + lines = code.split(/\n/); + non_blank_lines = lines.reject { |s| s.match(/^\s*$/) } + + # Length of leading spaces to be trimmed + len = non_blank_lines.map{ |s| + matches = s.match(/^[ \t]*/) + matches ? matches[0].length : 0 }.min + + len == 0 ? code : lines.map{|s| s.length < len ? s : s[len..-1]}.join("\n") + end + + # This method is for trimming the content of Jekyll blocks. + # + # @return a copy of the input lines array, with lines unindented by the + # maximal amount of whitespace possible without affecting relative + # (non-whitespace) line indentation. Also trim off leading and trailing blank lines. + def self.block_trim_leading_whitespace(lines) + # 1. Trim leading blank lines + while lines.first =~ /^\s*$/ do lines.shift; end + + # 2. Trim trailing blank lines. Also determine minimal + # indentation for the entire block. + + # Last line should consist of the indentation of the end tag + # (when it is on a separate line). + last_line = lines.last =~ /^\s*$/ ? lines.pop : '' + while lines.last =~ /^\s*$/ do lines.pop end + min_len = last_line.length + + non_blank_lines = lines.reject { |s| s.match(/^\s*$/) } + + # 3. Determine length of leading spaces to be trimmed + len = non_blank_lines.map{ |s| + matches = s.match(/^[ \t]*/) + matches ? matches[0].length : 0 }.min + + # Only trim the excess relative to min_len + len = len < min_len ? min_len : len - min_len + + len == 0 ? lines : lines.map{|s| s.length < len ? s : s.sub(/^[ \t]{#{len}}/, '')} + end + end +end diff --git a/src/_plugins/markdown_with_code_excerpts.rb b/src/_plugins/markdown_with_code_excerpts.rb deleted file mode 120000 index cf1672c161..0000000000 --- a/src/_plugins/markdown_with_code_excerpts.rb +++ /dev/null @@ -1,51 +0,0 @@ -require_relative 'code_excerpt_processor' - -module Jekyll - module Converters - - # This converter does some markdown preprocessing before using [Kramdown] to - # do the full markdown conversion (to HTML). - # - # Currently, the only preprocessing that is done is for code-excerpt - # instructions. See [code_excerpt_processor.rb] for details. - # - module MarkdownWithCodeExcerptsConverterMixin - # Ensure subclass sets the following property: - # - # priority :high - - def matches(ext) - ext =~ /^\.md$/i - end - - def output_ext(_ext) - '.html' - end - - end - - class MarkdownWithCodeExcerpts - def initialize(config = {}, code_framer = nil) - @config = config - @code_framer = code_framer || IdentityCodeFramer.new - end - - def convert(content) - @cep ||= DartSite::CodeExcerptProcessor.new(@code_framer) - @cep.code_excerpt_processing_init - content.gsub!(@cep.code_excerpt_regex) { - @cep.process_code_excerpt(Regexp.last_match) - } - - @base_conv ||= Markdown::KramdownParser.new(@config) - @base_conv.convert(content) - end - end - - class IdentityCodeFramer - def frame_code(title, classes, attrs, escaped_code, indent, secondary_class) - escaped_code - end - end - end -end diff --git a/src/_plugins/markdown_with_code_excerpts.rb b/src/_plugins/markdown_with_code_excerpts.rb new file mode 100644 index 0000000000..cf1672c161 --- /dev/null +++ b/src/_plugins/markdown_with_code_excerpts.rb @@ -0,0 +1,51 @@ +require_relative 'code_excerpt_processor' + +module Jekyll + module Converters + + # This converter does some markdown preprocessing before using [Kramdown] to + # do the full markdown conversion (to HTML). + # + # Currently, the only preprocessing that is done is for code-excerpt + # instructions. See [code_excerpt_processor.rb] for details. + # + module MarkdownWithCodeExcerptsConverterMixin + # Ensure subclass sets the following property: + # + # priority :high + + def matches(ext) + ext =~ /^\.md$/i + end + + def output_ext(_ext) + '.html' + end + + end + + class MarkdownWithCodeExcerpts + def initialize(config = {}, code_framer = nil) + @config = config + @code_framer = code_framer || IdentityCodeFramer.new + end + + def convert(content) + @cep ||= DartSite::CodeExcerptProcessor.new(@code_framer) + @cep.code_excerpt_processing_init + content.gsub!(@cep.code_excerpt_regex) { + @cep.process_code_excerpt(Regexp.last_match) + } + + @base_conv ||= Markdown::KramdownParser.new(@config) + @base_conv.convert(content) + end + end + + class IdentityCodeFramer + def frame_code(title, classes, attrs, escaped_code, indent, secondary_class) + escaped_code + end + end + end +end diff --git a/src/_plugins/prettify.rb b/src/_plugins/prettify.rb deleted file mode 120000 index 4845b6d3da..0000000000 --- a/src/_plugins/prettify.rb +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file -# for details. All rights reserved. Use of this source code is governed by a -# BSD-style license that can be found in the LICENSE file. - -require 'liquid/tag/parser' # https://github.com/envygeeks/liquid-tag-parser -require_relative 'dart_site_util' -require_relative 'prettify_core' - -module Jekyll - - module Tags - - # Liquid Block plugin to render code that gets - # prettified by https://github.com/google/code-prettify. - # - # Arguments: - # - # - The first unnamed optional argument is the prettifier lang argument. - # Use 'nocode' or 'none' as the language to turn off prettifying. - # - class="...". CSS classes to be added to the opening
     tag.
    -    # - context="html". When unspecified, the context is assumed to be markdown.
    -    #   In markdown, indentation of the block is preserved, in HTML the block
    -    #   isn't indented.
    -    # - tag="...". See [PrettifyCore.code2html()] for a description of
    -    #   accepted tag specifiers. Defaults to 'pre'.
    -    #
    -    # Code highlighting is supported; see see [PrettifyCore] for details.
    -    #
    -    # Example usage:
    -    #
    -    # {% prettify dart %}
    -    #   var hello = 'world';
    -    # {% endprettify %}
    -    #
    -    class Prettify < Liquid::Block
    -
    -      def initialize(tag_name, string_of_args, tokens)
    -        super
    -        @args = Liquid::Tag::Parser.new(string_of_args).args
    -      end
    -
    -      def render(_context)
    -        helper = DartSite::PrettifyCore.new
    -        helper.code2html(super,
    -                         lang: @args[:argv1],
    -                         context: @args[:context] || 'markdown',
    -                         tag_specifier: @args[:tag],
    -                         user_classes: @args[:class])
    -      end
    -
    -    end
    -  end
    -end
    -
    -Liquid::Template.register_tag('prettify', Jekyll::Tags::Prettify)
    diff --git a/src/_plugins/prettify.rb b/src/_plugins/prettify.rb
    new file mode 100644
    index 0000000000..4845b6d3da
    --- /dev/null
    +++ b/src/_plugins/prettify.rb
    @@ -0,0 +1,55 @@
    +# Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
    +# for details. All rights reserved. Use of this source code is governed by a
    +# BSD-style license that can be found in the LICENSE file.
    +
    +require 'liquid/tag/parser' # https://github.com/envygeeks/liquid-tag-parser
    +require_relative 'dart_site_util'
    +require_relative 'prettify_core'
    +
    +module Jekyll
    +
    +  module Tags
    +
    +    # Liquid Block plugin to render code that gets
    +    # prettified by https://github.com/google/code-prettify.
    +    #
    +    # Arguments:
    +    #
    +    # - The first unnamed optional argument is the prettifier lang argument.
    +    #   Use 'nocode' or 'none' as the language to turn off prettifying.
    +    # - class="...". CSS classes to be added to the opening 
     tag.
    +    # - context="html". When unspecified, the context is assumed to be markdown.
    +    #   In markdown, indentation of the block is preserved, in HTML the block
    +    #   isn't indented.
    +    # - tag="...". See [PrettifyCore.code2html()] for a description of
    +    #   accepted tag specifiers. Defaults to 'pre'.
    +    #
    +    # Code highlighting is supported; see see [PrettifyCore] for details.
    +    #
    +    # Example usage:
    +    #
    +    # {% prettify dart %}
    +    #   var hello = 'world';
    +    # {% endprettify %}
    +    #
    +    class Prettify < Liquid::Block
    +
    +      def initialize(tag_name, string_of_args, tokens)
    +        super
    +        @args = Liquid::Tag::Parser.new(string_of_args).args
    +      end
    +
    +      def render(_context)
    +        helper = DartSite::PrettifyCore.new
    +        helper.code2html(super,
    +                         lang: @args[:argv1],
    +                         context: @args[:context] || 'markdown',
    +                         tag_specifier: @args[:tag],
    +                         user_classes: @args[:class])
    +      end
    +
    +    end
    +  end
    +end
    +
    +Liquid::Template.register_tag('prettify', Jekyll::Tags::Prettify)
    diff --git a/src/_plugins/prettify_core.rb b/src/_plugins/prettify_core.rb
    deleted file mode 120000
    index 6b4124cb3c..0000000000
    --- a/src/_plugins/prettify_core.rb
    +++ /dev/null
    @@ -1,89 +0,0 @@
    -require 'cgi'
    -require_relative 'dart_site_util'
    -
    -module DartSite
    -
    -  # Base class used by some Liquid Block plugins to render code that gets
    -  # prettified by https://github.com/google/code-prettify.
    -  #
    -  # The following markup syntax can be used to apply a CSS class to a span
    -  # of code:
    -  #
    -  #   `[[foo]]some code[[/foo]]`
    -  #
    -  # will render as
    -  #
    -  #   `some code`
    -  #
    -  # Note that `[[highlight]]...[[/highlight]]` can be abbreviated using the
    -  # following shorthand: `[!...!]`.
    -  #
    -  class PrettifyCore
    -
    -    # @param code [String], raw code to be converted to HTML.
    -    # @param lang [String], e.g., 'dart', 'json' or 'yaml'
    -    # @param tag_specifier [String] matching "pre|pre+code|code|code+br".
    -    #   This is the HTML element used to wrap the prettified
    -    #   code. The `code` element is used for `code+br`; in addition,
    -    #   newlines in the code excerpt are reformatted at `
    ` elements. - # @param user_classes [String] zero or more space separated CSS class names - # to be applied to the outter-most enclosing tag. - # @param context [String] 'html' or 'markdown' (default), represents whether - # the tag is being rendered in an HTML or a markdown document. Indentation - # is preserved for markdown but not for HTML. - def code2html(code, lang: nil, context: 'markdown', tag_specifier: 'pre', user_classes: nil) - tag = _get_real_tag(tag_specifier || 'pre') - css_classes = _css_classes(lang, user_classes) - class_attr = css_classes.empty? ? '' : " class=\"#{css_classes.join(' ')}\"" - - out = "<#{tag}#{class_attr}>" - out += '' if tag_specifier == 'pre+code' - - code = context == 'markdown' ? - Util.block_trim_leading_whitespace(code.split(/\n/)).join("\n") : - Util.trim_min_leading_space(code) - # Strip leading and trailing whitespace so that
     and 
    tags wrap tightly - code.strip! - code = CGI.escapeHTML(code) - - if tag_specifier == 'code+br' - code.gsub!(/\n[ \t]*/) { |s| - "
    \n#{' ' * (s.length - 1)}" - } - end - - # Names of tags previously supported: highlight, note, red, strike. - code.gsub!(/\[\[([\w-]+)\]\]/, '') - code.gsub!(/\[\[\/([\w-]*)\]\]/, '') - - # Flutter tag syntax variant: - code.gsub!(/\/\*\*([\w-]+)\*\//, '') - code.gsub!(/\/\*-([\w-]*)\*\//, '') - - code.gsub!('[!', '') - code.gsub!('!]', '') - - out += code - out += '
    ' if tag_specifier == 'pre+code' - out += "" - end - - private - - def _css_classes(lang, user_classes) - css_classes = [] - unless lang == 'nocode' || lang == 'none' - css_classes << 'prettyprint' - css_classes << "lang-#{lang}" if lang - end - css_classes << user_classes if user_classes - css_classes - end - - # Returns the word before the '+' if tag_specifier contains a '+', - # tag_specifier otherwise - def _get_real_tag(tag_specifier) - tag_specifier[/^[^\+]+(?=\+)/] || tag_specifier - end - end -end diff --git a/src/_plugins/prettify_core.rb b/src/_plugins/prettify_core.rb new file mode 100644 index 0000000000..6b4124cb3c --- /dev/null +++ b/src/_plugins/prettify_core.rb @@ -0,0 +1,89 @@ +require 'cgi' +require_relative 'dart_site_util' + +module DartSite + + # Base class used by some Liquid Block plugins to render code that gets + # prettified by https://github.com/google/code-prettify. + # + # The following markup syntax can be used to apply a CSS class to a span + # of code: + # + # `[[foo]]some code[[/foo]]` + # + # will render as + # + # `some code` + # + # Note that `[[highlight]]...[[/highlight]]` can be abbreviated using the + # following shorthand: `[!...!]`. + # + class PrettifyCore + + # @param code [String], raw code to be converted to HTML. + # @param lang [String], e.g., 'dart', 'json' or 'yaml' + # @param tag_specifier [String] matching "pre|pre+code|code|code+br". + # This is the HTML element used to wrap the prettified + # code. The `code` element is used for `code+br`; in addition, + # newlines in the code excerpt are reformatted at `
    ` elements. + # @param user_classes [String] zero or more space separated CSS class names + # to be applied to the outter-most enclosing tag. + # @param context [String] 'html' or 'markdown' (default), represents whether + # the tag is being rendered in an HTML or a markdown document. Indentation + # is preserved for markdown but not for HTML. + def code2html(code, lang: nil, context: 'markdown', tag_specifier: 'pre', user_classes: nil) + tag = _get_real_tag(tag_specifier || 'pre') + css_classes = _css_classes(lang, user_classes) + class_attr = css_classes.empty? ? '' : " class=\"#{css_classes.join(' ')}\"" + + out = "<#{tag}#{class_attr}>" + out += '' if tag_specifier == 'pre+code' + + code = context == 'markdown' ? + Util.block_trim_leading_whitespace(code.split(/\n/)).join("\n") : + Util.trim_min_leading_space(code) + # Strip leading and trailing whitespace so that
     and 
    tags wrap tightly + code.strip! + code = CGI.escapeHTML(code) + + if tag_specifier == 'code+br' + code.gsub!(/\n[ \t]*/) { |s| + "
    \n#{' ' * (s.length - 1)}" + } + end + + # Names of tags previously supported: highlight, note, red, strike. + code.gsub!(/\[\[([\w-]+)\]\]/, '') + code.gsub!(/\[\[\/([\w-]*)\]\]/, '') + + # Flutter tag syntax variant: + code.gsub!(/\/\*\*([\w-]+)\*\//, '') + code.gsub!(/\/\*-([\w-]*)\*\//, '') + + code.gsub!('[!', '') + code.gsub!('!]', '') + + out += code + out += '
    ' if tag_specifier == 'pre+code' + out += "" + end + + private + + def _css_classes(lang, user_classes) + css_classes = [] + unless lang == 'nocode' || lang == 'none' + css_classes << 'prettyprint' + css_classes << "lang-#{lang}" if lang + end + css_classes << user_classes if user_classes + css_classes + end + + # Returns the word before the '+' if tag_specifier contains a '+', + # tag_specifier otherwise + def _get_real_tag(tag_specifier) + tag_specifier[/^[^\+]+(?=\+)/] || tag_specifier + end + end +end diff --git a/src/_plugins/regex_replace_filter.rb b/src/_plugins/regex_replace_filter.rb deleted file mode 120000 index 262b0494e7..0000000000 --- a/src/_plugins/regex_replace_filter.rb +++ /dev/null @@ -1,8 +0,0 @@ -module RegexReplaceFilter - # From https://github.com/Shopify/liquid/issues/202#issuecomment-19112872 - def regex_replace(input, regex, replacement = '') - input.to_s.gsub(Regexp.new(regex), replacement.to_s) - end -end - -Liquid::Template.register_filter(RegexReplaceFilter) diff --git a/src/_plugins/regex_replace_filter.rb b/src/_plugins/regex_replace_filter.rb new file mode 100644 index 0000000000..262b0494e7 --- /dev/null +++ b/src/_plugins/regex_replace_filter.rb @@ -0,0 +1,8 @@ +module RegexReplaceFilter + # From https://github.com/Shopify/liquid/issues/202#issuecomment-19112872 + def regex_replace(input, regex, replacement = '') + input.to_s.gsub(Regexp.new(regex), replacement.to_s) + end +end + +Liquid::Template.register_filter(RegexReplaceFilter) diff --git a/src/add-to-app/android/project-setup.md b/src/add-to-app/android/project-setup.md index b31c2e4ac6..c485c3fca8 100644 --- a/src/add-to-app/android/project-setup.md +++ b/src/add-to-app/android/project-setup.md @@ -338,7 +338,7 @@ host Android app, make the following changes. ## Add the Flutter module as a dependency -### 将 Flutter module 作为依赖项 +## 将 Flutter module 作为依赖项 Add the Flutter module as a dependency of your existing app in Gradle. You can achieve this in two ways. diff --git a/src/robots.txt b/src/robots.txt deleted file mode 120000 index 6fdba7bd32..0000000000 --- a/src/robots.txt +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: none ---- - -{% comment %} - -This is the default robots file used when serving locally or on staging servers. -Instructions for deploying to the site's official server are in the README.md. - -{% endcomment -%} - -User-agent: linkcheck -Disallow: - -User-agent: * -Disallow: / diff --git a/src/robots.txt b/src/robots.txt new file mode 100644 index 0000000000..6fdba7bd32 --- /dev/null +++ b/src/robots.txt @@ -0,0 +1,16 @@ +--- +layout: none +--- + +{% comment %} + +This is the default robots file used when serving locally or on staging servers. +Instructions for deploying to the site's official server are in the README.md. + +{% endcomment -%} + +User-agent: linkcheck +Disallow: + +User-agent: * +Disallow: / diff --git a/src/sitemap.xml b/src/sitemap.xml deleted file mode 120000 index d0ae481ca0..0000000000 --- a/src/sitemap.xml +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: null -sitemap: false ---- - - -{% assign collections = site.collections | map: 'label' | join: ',' | prepend: 'pages,' | split: ',' | sort -%} - -{%- for colName in collections -%} -{% assign pages = site[colName] | sort: 'url' -%} - -{%- for page in pages -%} - -{%- unless page.sitemap == false -%} - - {{ site.url | append: page.url | regex_replace: '/index(\.html)?$|\.html$|/$' }} - - {%- if page.sitemap.lastmod -%} - {{ page.sitemap.lastmod | date: "%Y-%m-%d" }} - {%- else -%} - {{ site.now | default: page.date | default: site.time | date_to_xmlschema }} - {%- endif -%} - - {{ page.sitemap.changefreq | default: 'monthly' }} - {% if page.sitemap.priority -%} - {{ page.sitemap.priority }} - {%- endif -%} - -{% endunless -%} - -{%- endfor -%} - -{%- endfor -%} - diff --git a/src/sitemap.xml b/src/sitemap.xml new file mode 100644 index 0000000000..d0ae481ca0 --- /dev/null +++ b/src/sitemap.xml @@ -0,0 +1,34 @@ +--- +layout: null +sitemap: false +--- + + +{% assign collections = site.collections | map: 'label' | join: ',' | prepend: 'pages,' | split: ',' | sort -%} + +{%- for colName in collections -%} +{% assign pages = site[colName] | sort: 'url' -%} + +{%- for page in pages -%} + +{%- unless page.sitemap == false -%} + + {{ site.url | append: page.url | regex_replace: '/index(\.html)?$|\.html$|/$' }} + + {%- if page.sitemap.lastmod -%} + {{ page.sitemap.lastmod | date: "%Y-%m-%d" }} + {%- else -%} + {{ site.now | default: page.date | default: site.time | date_to_xmlschema }} + {%- endif -%} + + {{ page.sitemap.changefreq | default: 'monthly' }} + {% if page.sitemap.priority -%} + {{ page.sitemap.priority }} + {%- endif -%} + +{% endunless -%} + +{%- endfor -%} + +{%- endfor -%} + diff --git a/src/testing/build-modes.md b/src/testing/build-modes.md index f3676fddb1..59731a60d6 100644 --- a/src/testing/build-modes.md +++ b/src/testing/build-modes.md @@ -9,18 +9,11 @@ keywords: Flutter的构建模式,Flutter debug模式,Flutter release模式,Flutt The Flutter tooling supports three modes when compiling your app, and a headless mode for testing. -This doc explains the three modes and tells you when to use which. -For more information on headless testing, see -[Unit testing.]({{site.url}}/testing#unit-tests) - -Flutter 支持三种模式编译 app,也支持使用 headless 模式来测试。 -这篇文档解释了这三种模式,并且告诉你什么时候应该使用哪种模式。 -关于 headless 测试的更多信息,可以查看 [单元测试]({{site.url}}/testing#unit-tests)。 - You choose a compilation mode depending on where you are in the development cycle. Are you debugging your code? Do you need profiling information? Are you ready to deploy your app? +Flutter 支持三种模式编译 app,也支持使用 headless 模式来测试。 选择哪种编译模式取决于你处于哪个开发周期中。 是调试代码阶段,还是需要性能优化分析,抑或是准备部署你的应用了呢? diff --git a/tool/config/linkcheck-skip-list.txt b/tool/config/linkcheck-skip-list.txt index 8e82d3fdf8..f1adc3b9ab 100644 --- a/tool/config/linkcheck-skip-list.txt +++ b/tool/config/linkcheck-skip-list.txt @@ -50,5 +50,11 @@ fonts.googleapis.com # FIXME Not sure here, "connection failed" on every go /tools/devtools/vscode +# flutter.cn | There may be an error in the anchors of these links, so let's skip them for now. +/add-to-app/android/project-setup +/cookbook/design/fonts +/resources/architectural-overview +/tools/devtools/inspector + # robots http:///robots.txt diff --git a/tool/flutter_site/lib/src/commands/check_links.dart b/tool/flutter_site/lib/src/commands/check_links.dart index babfcbc6a8..6f2cccc2fc 100644 --- a/tool/flutter_site/lib/src/commands/check_links.dart +++ b/tool/flutter_site/lib/src/commands/check_links.dart @@ -83,7 +83,7 @@ Future _checkLinks({bool checkExternal = false}) async { try { final result = await linkcheck.run( [ - ':$_emulatorPort', + 'http://localhost:$_emulatorPort/docs', '--skip-file', _skipFilePath, if (checkExternal) 'external' diff --git a/tool/translator/package.json b/tool/translator/package.json index ab3b45ac06..83a868e838 100644 --- a/tool/translator/package.json +++ b/tool/translator/package.json @@ -9,7 +9,7 @@ "author": "Zhicheng WANG", "license": "MIT", "dependencies": { - "@awesome-fe/translate": "1.7.4", + "@awesome-fe/translate": "1.7.10", "gulp": "^4.0.2", "gulp-replace": "^1.0.0" }