diff --git a/Gemfile b/Gemfile index 06bf9a1..4b06d65 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,10 @@ gem "ferrum" # Math markup conversion (OMML ↔ MathML ↔ LaTeX ↔ AsciiMath) gem "plurimath", github: "jcasimir/plurimath", branch: "main" +# Pin plurimath's own markup dependencies: newer releases restructure APIs +# (e.g. mml drops `mml/configuration`) that this plurimath revision requires. +gem "mml", "= 2.0.3" +gem "lutaml-model", "= 0.7.7" group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/Gemfile.lock b/Gemfile.lock index 455c9b7..942c879 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,7 +15,7 @@ GIT GEM remote: https://rubygems.org/ specs: - action_text-trix (2.1.17) + action_text-trix (2.1.19) railties actioncable (8.1.3) actionpack (= 8.1.3) @@ -89,7 +89,7 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.9) + addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) base64 (0.3.0) @@ -105,7 +105,7 @@ GEM bundler-audit (0.9.3) bundler (>= 1.2.0) thor (~> 1.0) - concurrent-ruby (1.3.7) + concurrent-ruby (1.3.8) connection_pool (3.0.2) crass (1.0.7) date (3.5.1) @@ -122,7 +122,7 @@ GEM sxp (~> 2.0) unicode-types (~> 1.8) ed25519 (1.4.0) - erb (6.0.5) + erb (6.0.6) erubi (1.13.1) et-orbi (1.4.0) tzinfo @@ -203,8 +203,8 @@ GEM lutaml-model moxml moxml (0.1.10) - msgpack (1.8.1) - net-imap (0.6.3) + msgpack (1.8.3) + net-imap (0.6.4.1) date net-protocol net-pop (0.1.2) @@ -256,7 +256,7 @@ GEM activesupport (>= 7.0.0) rack public_suffix (7.0.5) - puma (7.2.0) + puma (8.0.2) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) @@ -428,7 +428,7 @@ GEM bindex (>= 0.4.0) railties (>= 8.0.0) webrick (1.9.2) - websocket-driver (0.8.0) + websocket-driver (0.8.2) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -455,6 +455,8 @@ DEPENDENCIES importmap-rails jbuilder kamal + lutaml-model (= 0.7.7) + mml (= 2.0.3) pg (~> 1.1) plurimath! propshaft diff --git a/config/routes.rb b/config/routes.rb index 85e0c7b..00ef916 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,10 +3,10 @@ root "grades#index" - resources :grades, only: [:show] do - resources :content_modules, only: [:show], path: "modules" do - resources :topics, only: [:show] do - resources :lessons, only: [:show] + resources :grades, only: [ :show ] do + resources :content_modules, only: [ :show ], path: "modules" do + resources :topics, only: [ :show ] do + resources :lessons, only: [ :show ] end end end diff --git a/db/migrate/20260324160002_create_modules.rb b/db/migrate/20260324160002_create_modules.rb index 47c5d7c..3a39c4f 100644 --- a/db/migrate/20260324160002_create_modules.rb +++ b/db/migrate/20260324160002_create_modules.rb @@ -10,7 +10,7 @@ def change t.timestamps end - add_index :content_modules, [:grade_id, :number], unique: true - add_index :content_modules, [:grade_id, :position] + add_index :content_modules, [ :grade_id, :number ], unique: true + add_index :content_modules, [ :grade_id, :position ] end end diff --git a/db/migrate/20260324160003_create_topics.rb b/db/migrate/20260324160003_create_topics.rb index 32eb0e8..3cd3a82 100644 --- a/db/migrate/20260324160003_create_topics.rb +++ b/db/migrate/20260324160003_create_topics.rb @@ -10,7 +10,7 @@ def change t.timestamps end - add_index :topics, [:content_module_id, :letter], unique: true - add_index :topics, [:content_module_id, :position] + add_index :topics, [ :content_module_id, :letter ], unique: true + add_index :topics, [ :content_module_id, :position ] end end diff --git a/db/migrate/20260324160004_create_lessons.rb b/db/migrate/20260324160004_create_lessons.rb index 4df5aec..8df15c2 100644 --- a/db/migrate/20260324160004_create_lessons.rb +++ b/db/migrate/20260324160004_create_lessons.rb @@ -9,7 +9,7 @@ def change t.timestamps end - add_index :lessons, [:topic_id, :number], unique: true - add_index :lessons, [:topic_id, :position] + add_index :lessons, [ :topic_id, :number ], unique: true + add_index :lessons, [ :topic_id, :position ] end end diff --git a/db/migrate/20260324160009_create_assessments.rb b/db/migrate/20260324160009_create_assessments.rb index d8761ca..9a51936 100644 --- a/db/migrate/20260324160009_create_assessments.rb +++ b/db/migrate/20260324160009_create_assessments.rb @@ -10,6 +10,6 @@ def change t.timestamps end - add_index :assessments, [:content_module_id, :assessment_type], unique: true + add_index :assessments, [ :content_module_id, :assessment_type ], unique: true end end diff --git a/db/migrate/20260324160010_create_problems.rb b/db/migrate/20260324160010_create_problems.rb index 044cce4..21ffba8 100644 --- a/db/migrate/20260324160010_create_problems.rb +++ b/db/migrate/20260324160010_create_problems.rb @@ -10,6 +10,6 @@ def change t.timestamps end - add_index :problems, [:source_type, :source_id, :position], unique: true + add_index :problems, [ :source_type, :source_id, :position ], unique: true end end diff --git a/db/migrate/20260324160012_create_standard_taggings.rb b/db/migrate/20260324160012_create_standard_taggings.rb index 2d1e005..5ab52de 100644 --- a/db/migrate/20260324160012_create_standard_taggings.rb +++ b/db/migrate/20260324160012_create_standard_taggings.rb @@ -7,7 +7,7 @@ def change t.timestamps end - add_index :standard_taggings, [:standard_id, :taggable_type, :taggable_id], + add_index :standard_taggings, [ :standard_id, :taggable_type, :taggable_id ], unique: true, name: "idx_standard_taggings_uniqueness" end end diff --git a/db/migrate/20260324223010_create_supplemental_resources.rb b/db/migrate/20260324223010_create_supplemental_resources.rb index ef76598..fd81960 100644 --- a/db/migrate/20260324223010_create_supplemental_resources.rb +++ b/db/migrate/20260324223010_create_supplemental_resources.rb @@ -14,7 +14,7 @@ def change end add_index :supplemental_resources, - [:resourceable_type, :resourceable_id, :resource_type], + [ :resourceable_type, :resourceable_id, :resource_type ], name: "idx_supplemental_resources_lookup" add_index :supplemental_resources, :resource_type add_index :supplemental_resources, :source diff --git a/db/migrate/20260325120001_create_expressions.rb b/db/migrate/20260325120001_create_expressions.rb index 277f786..720020e 100644 --- a/db/migrate/20260325120001_create_expressions.rb +++ b/db/migrate/20260325120001_create_expressions.rb @@ -15,7 +15,7 @@ def change t.timestamps end - add_index :expressions, [:source_type, :source_id, :position], unique: true, + add_index :expressions, [ :source_type, :source_id, :position ], unique: true, name: "idx_expressions_source_position" add_index :expressions, :conversion_status end diff --git a/lib/engageny/html_parser.rb b/lib/engageny/html_parser.rb index a15e9d5..a25713d 100644 --- a/lib/engageny/html_parser.rb +++ b/lib/engageny/html_parser.rb @@ -62,7 +62,7 @@ def self.wrap_section(head_html, section_html) def split_document # Find the boundary between head and body content body_match = @html.match(/\A(.*?]*>)(.*)\z/m) - return ["", [], ""] unless body_match + return [ "", [], "" ] unless body_match head = body_match[1] body = body_match[2] @@ -77,7 +77,7 @@ def split_document body_close = body.index("") || body.length if section_starts.empty? - return [head + body[0...body_close], [], body[body_close..]] + return [ head + body[0...body_close], [], body[body_close..] ] end # Head includes everything up to and including the first section's start @@ -92,7 +92,7 @@ def split_document tail = body[body_close..] - [full_head, sections, tail] + [ full_head, sections, tail ] end def find_student_section_indices diff --git a/lib/engageny/math_converter.rb b/lib/engageny/math_converter.rb index 682e58a..e217453 100644 --- a/lib/engageny/math_converter.rb +++ b/lib/engageny/math_converter.rb @@ -101,7 +101,7 @@ def manifest_summary # We keep only the final (inserted) content and discard deletions. def try_strip_track_changes(xml) # Check for ins/del in either namespace - return [xml, nil] unless xml.match?(/(?:<[mw]:ins\b|<[mw]:del\b)/) + return [ xml, nil ] unless xml.match?(/(?:<[mw]:ins\b|<[mw]:del\b)/) doc = Nokogiri::XML(xml) word_ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" @@ -110,7 +110,7 @@ def try_strip_track_changes(xml) changed = false # Remove and elements entirely (deleted content) - [["w", word_ns], ["m", math_ns]].each do |prefix, ns| + [ [ "w", word_ns ], [ "m", math_ns ] ].each do |prefix, ns| doc.xpath("//#{prefix}:del", prefix => ns).each do |del| del.remove changed = true @@ -118,7 +118,7 @@ def try_strip_track_changes(xml) end # Unwrap and elements (keep children, remove wrapper) - [["w", word_ns], ["m", math_ns]].each do |prefix, ns| + [ [ "w", word_ns ], [ "m", math_ns ] ].each do |prefix, ns| doc.xpath("//#{prefix}:ins", prefix => ns).each do |ins| ins.children.each { |child| ins.add_previous_sibling(child) } ins.remove @@ -126,7 +126,7 @@ def try_strip_track_changes(xml) end end - [doc.root.to_xml, changed ? FIXES[:strip_track_changes] : nil] + [ doc.root.to_xml, changed ? FIXES[:strip_track_changes] : nil ] end # Fix: Convert special symbol references to Unicode. @@ -148,12 +148,12 @@ def try_strip_track_changes(xml) "Symbol:F070" => "\u03C0", # pi π "Symbol:F071" => "\u03B8", # theta θ "Symbol:F044" => "\u0394", # Delta Δ - "Symbol:F053" => "\u03A3", # Sigma Σ + "Symbol:F053" => "\u03A3" # Sigma Σ }.freeze def try_sym_to_unicode(xml) # lives in the Word namespace, not math namespace - return [xml, nil] unless xml.match?(/(?:<[mw]:sym\b)/) + return [ xml, nil ] unless xml.match?(/(?:<[mw]:sym\b)/) doc = Nokogiri::XML(xml) changed = false @@ -184,7 +184,7 @@ def try_sym_to_unicode(xml) end end - [doc.root.to_xml, changed ? FIXES[:sym_to_unicode] : nil] + [ doc.root.to_xml, changed ? FIXES[:sym_to_unicode] : nil ] end def extract_text(omml_xml) diff --git a/lib/engageny/page_comparator.rb b/lib/engageny/page_comparator.rb index 8fa0a92..f4f6df2 100644 --- a/lib/engageny/page_comparator.rb +++ b/lib/engageny/page_comparator.rb @@ -1,5 +1,6 @@ require "ferrum" require "fileutils" +require "zip" module Engageny class PageComparator @@ -37,7 +38,7 @@ def screenshot_html browser_path: CHROME_PATH, headless: "new", timeout: 30, - window_size: [900, 1200] + window_size: [ 900, 1200 ] ) begin @@ -92,18 +93,14 @@ def find_original_pdf return nil unless zip_path.exist? # Find the PDF inside the ZIP - output = `unzip -l "#{zip_path}" 2>/dev/null | grep "#{pdf_name}" | grep -v Archived` - return nil if output.strip.empty? - - # Extract the path from the unzip listing - zip_entry = output.strip.split(/\s+/, 4).last + zip_entry = Zip::File.open(zip_path.to_s) do |zip| + zip.glob("**/*#{pdf_name}").reject { |e| e.name.include?("Archived") }.first&.name + end return nil unless zip_entry dest = OUTPUT_DIR.join("original.pdf") - system("unzip", "-o", zip_path.to_s, zip_entry, "-d", OUTPUT_DIR.to_s) + Zip::File.open(zip_path.to_s) { |zip| zip.extract(zip_entry, dest.to_s) { true } } - extracted = OUTPUT_DIR.join(zip_entry) - FileUtils.cp(extracted, dest) if extracted.exist? dest.exist? ? dest : nil end diff --git a/lib/tasks/engageny.rake b/lib/tasks/engageny.rake index 268d3d8..22984c2 100644 --- a/lib/tasks/engageny.rake +++ b/lib/tasks/engageny.rake @@ -70,8 +70,8 @@ namespace :engageny do original.chars.each_with_index do |c, i| if reconstructed[i] != c puts " First diff at byte #{i}" - puts " Original: #{original[[i - 30, 0].max..i + 30].inspect}" - puts " Reconstructed: #{reconstructed[[i - 30, 0].max..i + 30].inspect}" + puts " Original: #{original[[ i - 30, 0 ].max..i + 30].inspect}" + puts " Reconstructed: #{reconstructed[[ i - 30, 0 ].max..i + 30].inspect}" break end end @@ -82,7 +82,7 @@ namespace :engageny do end desc "Visual comparison: screenshot HTML rendering and compare against original PDF" - task :compare, [:lesson_id] => :environment do |_t, args| + task :compare, [ :lesson_id ] => :environment do |_t, args| require_relative "../engageny/html_parser" require_relative "../engageny/html_normalizer" require "ferrum" @@ -126,7 +126,7 @@ namespace :engageny do browser_path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", headless: "new", timeout: 30, - window_size: [1000, 1400] + window_size: [ 1000, 1400 ] ) browser.goto("data:text/html;charset=utf-8,#{ERB::Util.url_encode(html)}") diff --git a/scripts/fetch_ccss_standards.rb b/scripts/fetch_ccss_standards.rb index 672c31b..a3cb7f9 100644 --- a/scripts/fetch_ccss_standards.rb +++ b/scripts/fetch_ccss_standards.rb @@ -28,7 +28,7 @@ }.freeze TARGET_GRADES = %w[04 05 06].freeze -TARGET_GRADE_INTS = [4, 5, 6].freeze +TARGET_GRADE_INTS = [ 4, 5, 6 ].freeze $stderr.puts "Fetching CCSS data from #{SOURCE_URL}..." raw = URI.open(SOURCE_URL).read @@ -104,7 +104,7 @@ end # Sort by grade, domain, then code -standards.sort_by! { |s| [s[:grade_level], s[:domain_code], s[:code]] } +standards.sort_by! { |s| [ s[:grade_level], s[:domain_code], s[:code] ] } output = { source: "Common Core State Standards via SirFizX/standards-data (GitHub)",