Module: Kettle::Jem::ReadmePostProcessor
- Defined in:
- lib/kettle/jem.rb
Constant Summary collapse
- ENGINE_COMPATIBILITY_MRI_VERSION =
{ "jruby" => { "9.1" => Gem::Version.new("2.3"), "9.2" => Gem::Version.new("2.5"), "9.3" => Gem::Version.new("2.6"), "9.4" => Gem::Version.new("3.1"), "10.0" => Gem::Version.new("3.4") }.freeze, "truby" => { "22.3" => Gem::Version.new("3.0"), "23.0" => Gem::Version.new("3.0"), "23.1" => Gem::Version.new("3.1"), "24.2" => Gem::Version.new("3.3"), "25.0" => Gem::Version.new("3.3"), "33.0" => Gem::Version.new("3.3") }.freeze }.freeze
- COMPATIBILITY_REFERENCE_LABEL_RE =
/\A(?:💎(?:ruby|jruby|truby)-|🚎)/- ENGINE_ROW_PATTERNS =
Ractor.make_shareable({ "jruby" => { row_prefix: "| Works with JRuby", badge_prefixes: %w[💎jruby-], ref_prefixes: [/\A🚎jruby-/, /\A🚎\d+-j-/] }.freeze, "truffleruby" => { row_prefix: "| Works with Truffle Ruby", badge_prefixes: %w[💎truby-], ref_prefixes: [/\A🚎truby-/, /\A🚎\d+-t-/] }.freeze })
- VERSIONED_ENGINE_COMPATIBILITY_BADGES =
[ { engine: "jruby", version: "10.0", workflow_path: ".github/workflows/jruby-10.0.yml", workflow_file: "jruby-10.0.yml", row_prefix: "| Works with JRuby", badge_label: "💎jruby-10.0i", workflow_label: "🚎jruby-10.0-wf", inline: "[![JRuby 10.0 Compat][💎jruby-10.0i]][🚎jruby-10.0-wf]", insert_before: "💎jruby-c-i", badge_definition: "[💎jruby-10.0i]: https://img.shields.io/badge/JRuby-10.0-FBE742?style=for-the-badge&logo=ruby&logoColor=red" }.freeze, { engine: "truby", version: "33.0", workflow_path: ".github/workflows/truffleruby-33.0.yml", workflow_file: "truffleruby-33.0.yml", row_prefix: "| Works with Truffle Ruby", badge_label: "💎truby-33.0i", workflow_label: "🚎truby-33.0-wf", inline: "[![Truffle Ruby 33.0 Compat][💎truby-33.0i]][🚎truby-33.0-wf]", insert_before: "💎truby-c-i", badge_definition: "[💎truby-33.0i]: https://img.shields.io/badge/Truffle_Ruby-33.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink" }.freeze ].freeze
Class Method Summary collapse
- .add_supported_versioned_engine_badges(content, min_ruby, workflow_paths) ⇒ Object
- .add_versioned_engine_badge_definitions(content, badge) ⇒ Object
- .add_versioned_engine_badge_to_row(content, badge) ⇒ Object
- .compatibility_badge_min_mri(label) ⇒ Object
- .compatibility_row?(line) ⇒ Boolean
- .delete_markdown_link_definitions(content, labels) ⇒ Object
- .ensure_markdown_link_definition(content, definition) ⇒ Object
- .markdown_inline_reference_owners(content) ⇒ Object
- .markdown_link_definition_owners(content) ⇒ Object
- .markdown_structural_owners(content, *owner_scopes) ⇒ Object
- .markdown_table_row_owners(content) ⇒ Object
- .normalize_compatibility_badge_cell(cell) ⇒ Object
- .normalize_compatibility_rows(content) ⇒ Object
- .process(content:, min_ruby:, engines: nil, workflow_paths: nil) ⇒ Object
- .prune_orphaned_workflow_inline_references(content) ⇒ Object
- .prune_unused_compatibility_reference_definitions(content) ⇒ Object
- .remove_badge_occurrences(content, label) ⇒ Object
- .remove_disabled_engine_content(content, engines) ⇒ Object
- .remove_incompatible_compatibility_badges(content, min_ruby) ⇒ Object
- .remove_markdown_inline_references(content, label) ⇒ Object
- .remove_markdown_table_rows(content) ⇒ Object
- .remove_missing_workflow_badges(content, workflow_paths) ⇒ Object
- .remove_workflow_badge_occurrences(content, workflow_label) ⇒ Object
- .ruby_minor_version(version) ⇒ Object
- .versioned_engine_badge_compatible?(badge, min_ruby) ⇒ Boolean
- .workflow_definition_base_url(content) ⇒ Object
- .workflow_references(content) ⇒ Object
Class Method Details
.add_supported_versioned_engine_badges(content, min_ruby, workflow_paths) ⇒ Object
893 894 895 896 897 898 899 900 901 902 903 904 905 |
# File 'lib/kettle/jem.rb', line 893 def add_supported_versioned_engine_badges(content, min_ruby, workflow_paths) existing_workflows = Array(workflow_paths).map { |path| path.to_s.delete_prefix("./") }.to_set VERSIONED_ENGINE_COMPATIBILITY_BADGES.reduce(content.to_s) do |processed, badge| next processed unless existing_workflows.include?(badge.fetch(:workflow_path)) next processed if processed.include?("[#{badge.fetch(:badge_label)}]:") next processed unless versioned_engine_badge_compatible?(badge, min_ruby) with_inline = add_versioned_engine_badge_to_row(processed, badge) next processed if with_inline == processed add_versioned_engine_badge_definitions(with_inline, badge) end end |
.add_versioned_engine_badge_definitions(content, badge) ⇒ Object
929 930 931 932 933 934 935 936 937 938 939 940 |
# File 'lib/kettle/jem.rb', line 929 def add_versioned_engine_badge_definitions(content, badge) processed = ensure_markdown_link_definition(content, badge.fetch(:badge_definition)) return processed if processed.include?("[#{badge.fetch(:workflow_label)}]:") workflow_base = workflow_definition_base_url(processed) return processed unless workflow_base ensure_markdown_link_definition( processed, "[#{badge.fetch(:workflow_label)}]: #{workflow_base}/actions/workflows/#{badge.fetch(:workflow_file)}" ) end |
.add_versioned_engine_badge_to_row(content, badge) ⇒ Object
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 |
# File 'lib/kettle/jem.rb', line 912 def add_versioned_engine_badge_to_row(content, badge) lines = content.lines row_index = lines.index { |line| line.lstrip.start_with?(badge.fetch(:row_prefix)) } return content unless row_index return content if lines.fetch(row_index).include?(badge.fetch(:badge_label)) line = lines.fetch(row_index) insert_before = badge.fetch(:insert_before) inline = badge.fetch(:inline) lines[row_index] = if line.include?("[#{insert_before}]") line.sub(/(?=\[!\[[^\]]+\]\[#{Regexp.escape(insert_before)}\])/, "#{inline} ") else line.sub(/\s*\|\s*$/, " #{inline}|") end lines.join end |
.compatibility_badge_min_mri(label) ⇒ Object
968 969 970 971 972 973 974 975 976 |
# File 'lib/kettle/jem.rb', line 968 def compatibility_badge_min_mri(label) if (match = label.match(/\A💎ruby-(?<version>\d+\.\d+)i\z/)) Gem::Version.new(match[:version]) elsif (match = label.match(/\A💎(?<engine>jruby|truby)-(?<version>\d+\.\d+)i\z/)) ENGINE_COMPATIBILITY_MRI_VERSION.dig(match[:engine], match[:version]) end rescue nil end |
.compatibility_row?(line) ⇒ Boolean
1073 1074 1075 1076 |
# File 'lib/kettle/jem.rb', line 1073 def compatibility_row?(line) text = line.to_s.lstrip text.start_with?("| Works with MRI Ruby", "| Works with JRuby", "| Works with Truffle Ruby") end |
.delete_markdown_link_definitions(content, labels) ⇒ Object
1078 1079 1080 1081 1082 1083 1084 1085 1086 |
# File 'lib/kettle/jem.rb', line 1078 def delete_markdown_link_definitions(content, labels) Kettle::Jem.ensure_runtime_dependencies! targets = labels.uniq.map do |label| Ast::Crispr::Markdown::Markly::Selectors.link_definition(label: label, limit: {at_least: 0}) end return content if targets.empty? Ast::Crispr::DeleteBatch.call(content: content.to_s, targets: targets, source_label: "README.md").updated_content end |
.ensure_markdown_link_definition(content, definition) ⇒ Object
942 943 944 945 946 947 948 |
# File 'lib/kettle/jem.rb', line 942 def ensure_markdown_link_definition(content, definition) label = definition[/\A(\[[^\]]+\]):/, 1] return content if label && content.include?("#{label}:") separator = content.end_with?("\n") ? "" : "\n" "#{content}#{separator}#{definition}\n" end |
.markdown_inline_reference_owners(content) ⇒ Object
1047 1048 1049 1050 1051 1052 1053 |
# File 'lib/kettle/jem.rb', line 1047 def markdown_inline_reference_owners(content) Kettle::Jem.ensure_runtime_dependencies! context = Ast::Crispr::Markdown::Markly.document_context(content: content.to_s, source_label: "README.md") context.structural_owners(owner_scope: :inline_references) rescue Ast::Crispr::Error [] end |
.markdown_link_definition_owners(content) ⇒ Object
1039 1040 1041 1042 1043 1044 1045 |
# File 'lib/kettle/jem.rb', line 1039 def markdown_link_definition_owners(content) Kettle::Jem.ensure_runtime_dependencies! context = Ast::Crispr::Markdown::Markly.document_context(content: content.to_s, source_label: "README.md") context.structural_owners(owner_scope: :link_definitions) rescue Ast::Crispr::Error [] end |
.markdown_structural_owners(content, *owner_scopes) ⇒ Object
1063 1064 1065 1066 1067 1068 1069 1070 1071 |
# File 'lib/kettle/jem.rb', line 1063 def markdown_structural_owners(content, *owner_scopes) Kettle::Jem.ensure_runtime_dependencies! context = Ast::Crispr::Markdown::Markly.document_context(content: content.to_s, source_label: "README.md") owner_scopes.each_with_object({}) do |owner_scope, owners| owners[owner_scope] = context.structural_owners(owner_scope: owner_scope) end rescue Ast::Crispr::Error owner_scopes.to_h { |owner_scope| [owner_scope, []] } end |
.markdown_table_row_owners(content) ⇒ Object
1055 1056 1057 1058 1059 1060 1061 |
# File 'lib/kettle/jem.rb', line 1055 def markdown_table_row_owners(content) Kettle::Jem.ensure_runtime_dependencies! context = Ast::Crispr::Markdown::Markly.document_context(content: content.to_s, source_label: "README.md") context.structural_owners(owner_scope: :table_rows) rescue Ast::Crispr::Error [] end |
.normalize_compatibility_badge_cell(cell) ⇒ Object
1006 1007 1008 1009 1010 1011 1012 1013 1014 |
# File 'lib/kettle/jem.rb', line 1006 def normalize_compatibility_badge_cell(cell) cell.to_s .split(/<br\/>/i) .filter_map do |segment| normalized = segment.gsub(/[ \t]+/, " ").strip normalized unless normalized.empty? end .join(" <br/> ") end |
.normalize_compatibility_rows(content) ⇒ Object
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 |
# File 'lib/kettle/jem.rb', line 987 def normalize_compatibility_rows(content) lines = content.lines markdown_table_row_owners(content).each do |owner| line = lines[owner.location.start_line - 1] next unless compatibility_row?(line) cells = line.split("|", -1) badge_cell = normalize_compatibility_badge_cell(cells[2]) if badge_cell.empty? lines[owner.location.start_line - 1] = nil next end cells[2] = " #{badge_cell}" lines[owner.location.start_line - 1] = cells.join("|") end lines.compact.join end |
.process(content:, min_ruby:, engines: nil, workflow_paths: nil) ⇒ Object
823 824 825 826 827 828 829 830 831 832 833 834 835 |
# File 'lib/kettle/jem.rb', line 823 def process(content:, min_ruby:, engines: nil, workflow_paths: nil) Kettle::Jem.ensure_runtime_dependencies! processed = content.to_s processed = remove_disabled_engine_content(processed, engines) if engines processed = remove_missing_workflow_badges(processed, workflow_paths) if workflow_paths return processed if min_ruby.to_s.empty? min_ruby_version = Gem::Version.new(min_ruby.to_s) processed = add_supported_versioned_engine_badges(processed, min_ruby_version, workflow_paths) if workflow_paths processed = remove_incompatible_compatibility_badges(processed, min_ruby_version) processed = normalize_compatibility_rows(processed) prune_unused_compatibility_reference_definitions(processed) end |
.prune_orphaned_workflow_inline_references(content) ⇒ Object
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 |
# File 'lib/kettle/jem.rb', line 1027 def prune_orphaned_workflow_inline_references(content) owners = markdown_structural_owners(content, :link_definitions, :inline_references) defined_labels = owners.fetch(:link_definitions).map { |owner| owner.label.to_s }.to_set orphaned_labels = owners.fetch(:inline_references).flat_map(&:labels).map(&:to_s).uniq.select do |label| label.start_with?("🚎") && !defined_labels.include?(label) end processed = orphaned_labels.reduce(content.to_s) do |memo, label| remove_markdown_inline_references(memo, label) end normalize_compatibility_rows(processed) end |
.prune_unused_compatibility_reference_definitions(content) ⇒ Object
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 |
# File 'lib/kettle/jem.rb', line 1016 def prune_unused_compatibility_reference_definitions(content) owners = markdown_structural_owners(content, :inline_references, :link_definitions) referenced_labels = owners.fetch(:inline_references).flat_map(&:labels).to_h { |label| [label, true] } labels = owners.fetch(:link_definitions).filter_map do |owner| label = owner.label.to_s label if COMPATIBILITY_REFERENCE_LABEL_RE.match?(label) && !referenced_labels[label] end delete_markdown_link_definitions(content, labels) end |
.remove_badge_occurrences(content, label) ⇒ Object
983 984 985 |
# File 'lib/kettle/jem.rb', line 983 def remove_badge_occurrences(content, label) remove_markdown_inline_references(content, label) end |
.remove_disabled_engine_content(content, engines) ⇒ Object
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 |
# File 'lib/kettle/jem.rb', line 837 def remove_disabled_engine_content(content, engines) enabled = Array(engines).map { |engine| engine.to_s.strip.downcase } processed = content.to_s ENGINE_ROW_PATTERNS.each do |engine, patterns| next if enabled.include?(engine) processed = remove_markdown_table_rows(processed) do |owner| owner.source.to_s.lstrip.start_with?(patterns.fetch(:row_prefix)) end labels = markdown_inline_reference_owners(processed).flat_map(&:labels).uniq labels.each do |label| next unless patterns.fetch(:badge_prefixes).any? { |prefix| label.start_with?(prefix) } processed = remove_badge_occurrences(processed, label) end ref_labels = markdown_link_definition_owners(processed).filter_map do |owner| label = owner.label.to_s label if patterns.fetch(:ref_prefixes).any? { |pattern| pattern.match?(label) } end ref_labels.each do |label| processed = remove_markdown_inline_references(processed, label) end processed = delete_markdown_link_definitions(processed, ref_labels) end processed end |
.remove_incompatible_compatibility_badges(content, min_ruby) ⇒ Object
957 958 959 960 961 962 963 964 965 966 |
# File 'lib/kettle/jem.rb', line 957 def remove_incompatible_compatibility_badges(content, min_ruby) markdown_inline_reference_owners(content).flat_map(&:labels).uniq.each do |label| badge_min_mri = compatibility_badge_min_mri(label) next unless badge_min_mri && ruby_minor_version(badge_min_mri) < ruby_minor_version(min_ruby) content = remove_badge_occurrences(content, label) end content end |
.remove_markdown_inline_references(content, label) ⇒ Object
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 |
# File 'lib/kettle/jem.rb', line 1088 def remove_markdown_inline_references(content, label) lines = content.to_s.lines markdown_inline_reference_owners(content).select { |owner| owner.labels.include?(label.to_s) } .group_by(&:line) .each do |line_number, owners| index = line_number - 1 line = lines[index].to_s owners.sort_by(&:start_column).reverse_each do |owner| start_column = owner.start_column end_column = owner.end_column if start_column.positive? && line[start_column - 1] == " " start_column -= 1 elsif line[end_column] == " " end_column += 1 end line = "#{line[0...start_column]}#{line[end_column..]}" end lines[index] = line end lines.join end |
.remove_markdown_table_rows(content) ⇒ Object
1110 1111 1112 1113 1114 1115 1116 |
# File 'lib/kettle/jem.rb', line 1110 def remove_markdown_table_rows(content) lines = content.to_s.lines markdown_table_row_owners(content).select { |owner| yield owner }.reverse_each do |owner| lines.delete_at(owner.location.start_line - 1) end lines.join end |
.remove_missing_workflow_badges(content, workflow_paths) ⇒ Object
866 867 868 869 870 871 872 873 874 875 |
# File 'lib/kettle/jem.rb', line 866 def remove_missing_workflow_badges(content, workflow_paths) existing = Array(workflow_paths).map { |path| path.to_s.delete_prefix("./") }.to_set workflow_references(content).each do |label, workflow_path| next if existing.include?(workflow_path) content = remove_workflow_badge_occurrences(content, label) end content end |
.remove_workflow_badge_occurrences(content, workflow_label) ⇒ Object
889 890 891 |
# File 'lib/kettle/jem.rb', line 889 def remove_workflow_badge_occurrences(content, workflow_label) remove_markdown_inline_references(content, workflow_label) end |
.ruby_minor_version(version) ⇒ Object
978 979 980 981 |
# File 'lib/kettle/jem.rb', line 978 def ruby_minor_version(version) segments = version.segments Gem::Version.new("#{segments[0]}.#{segments[1] || 0}") end |
.versioned_engine_badge_compatible?(badge, min_ruby) ⇒ Boolean
907 908 909 910 |
# File 'lib/kettle/jem.rb', line 907 def versioned_engine_badge_compatible?(badge, min_ruby) badge_min_mri = ENGINE_COMPATIBILITY_MRI_VERSION.dig(badge.fetch(:engine), badge.fetch(:version)) badge_min_mri && ruby_minor_version(badge_min_mri) >= ruby_minor_version(min_ruby) end |
.workflow_definition_base_url(content) ⇒ Object
950 951 952 953 954 955 |
# File 'lib/kettle/jem.rb', line 950 def workflow_definition_base_url(content) markdown_link_definition_owners(content).filter_map do |owner| url = owner.url.to_s url.split("/actions/workflows/", 2).first if url.include?("/actions/workflows/") end.first end |
.workflow_references(content) ⇒ Object
877 878 879 880 881 882 883 884 885 886 887 |
# File 'lib/kettle/jem.rb', line 877 def workflow_references(content) markdown_structural_owners(content, :link_definitions).fetch(:link_definitions).each_with_object({}) do |owner, references| workflow = URI(owner.url.to_s).path.to_s.split("/actions/workflows/", 2).last next unless workflow workflow = workflow.split("/", 2).first references[owner.label.to_s] = ".github/workflows/#{workflow}" end rescue URI::InvalidURIError {} end |