#!/usr/bin/env ruby require 'fileutils' require 'tmpdir' HAML_REPO = 'haml/haml' HAML_VERSION = 'v5.2.0' module GitHubFetcher def self.fetch(repo, tag:, path:) Dir.mktmpdir do |dir| Dir.chdir(dir) do url = "https://github.com/#{repo}/archive/#{tag}.tar.gz" system("curl -L --fail --retry 3 --retry-delay 1 #{url} -o - | tar zxf -") FileUtils.mv("#{File.basename(repo)}-#{tag.sub(/\Av/, '')}", path) end end end end class LicenseBuilder DELIMITER = "\n===" def initialize(haml_license:, hamlit_license:) @haml_license = haml_license @hamlit_license = hamlit_license end def build license = [ File.read(@haml_license), File.read(@hamlit_license).split(DELIMITER, 2).last, ].join(DELIMITER) File.write(@hamlit_license, license) end end # Generate lib/hamlit/parser from haml class HamlitParserBuilder TARGET_FILES = [ 'attribute_builder.rb', 'buffer.rb', 'error.rb', 'helpers.rb', 'options.rb', 'temple_engine.rb', # TODO: make the upstream sharable first # 'parser.rb', 'util.rb', 'helpers/xss_mods.rb', ] # Classes which are just referenced by Options and not really used by the parser DUMMY_CLASSES = { 'compiler.rb' => 'Compiler', 'escapable.rb' => 'Escapable', 'generator.rb' => 'Generator', } def initialize(haml:, hamlit_parser:) @haml = haml @hamlit_parser = hamlit_parser end def build TARGET_FILES.each do |file| src_path = File.join(@haml, file) dest_path = File.join(@hamlit_parser, "haml_#{file}") FileUtils.mkdir_p(File.dirname(dest_path)) FileUtils.cp(src_path, dest_path) src = File.read(dest_path) patch_source!(src, file: file) File.write(dest_path, src) end DUMMY_CLASSES.each do |file, klass| dest_path = File.join(@hamlit_parser, "haml_#{file}") src = "class Hamlit::Haml#{klass}; end" File.write(dest_path, src) end end private def patch_source!(src, file:) # Use Hamlit::HamlFoo instead of Haml::Foo src.gsub!(/^module Haml\n((?: #[^\n]+\n)*) (module|class) ([^ ]+)/, "module Hamlit\n\\1 \\2 Haml\\3") src.gsub!(/\bHaml::/, 'Hamlit::Haml') # Prefix Haml to references without Haml:: src.gsub!(/\b(AttributeBuilder|Error|InvalidAttributeNameError|Options|Parser|SyntaxError)\./, 'Haml\0') src.gsub!(/\brescue SyntaxError /, 'rescue HamlSyntaxError ') # Hamlit should not rely on Haml src.gsub!(/^require 'haml\/([^']+)'/, "require 'hamlit/parser/haml_\\1'") case file when 'error.rb' src.gsub!(/^ class ([^ ]+) < ([^ ]+);/, ' class Haml\1 < Haml\2;') when 'helpers.rb' src.gsub!(/^ def is_haml\?\n false\n end/m) { |str| str.gsub(/^ /, ' # ') } # not needed for the parser when 'options.rb' src.gsub!(/\.is_a\?\(Options\)/, '.is_a?(HamlOptions)') when 'temple_engine.rb' src.gsub!(/\buse (Generator|Escapable)$/, 'use Haml\1') end end end FileUtils.rm_rf(haml = File.expand_path('../haml', __dir__)) GitHubFetcher.fetch(HAML_REPO, tag: HAML_VERSION, path: haml) hamlit = File.expand_path('..', __dir__) LicenseBuilder.new( haml_license: File.join(haml, 'MIT-LICENSE'), hamlit_license: File.join(hamlit, 'LICENSE.txt'), ).build hamlit_parser = File.join(hamlit, 'lib/hamlit/parser') # TODO: FileUtils.rm_rf(hamlit_parser = File.join(hamlit, 'lib/hamlit/parser')) HamlitParserBuilder.new( haml: File.join(haml, 'lib/haml'), hamlit_parser: hamlit_parser, ).build