From 2be7baa3c9bd982f85ff3c20bd3deeeea3bc26ab Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Tue, 24 Dec 2024 20:38:14 -0300 Subject: [PATCH 01/12] Add pdf reader gem --- Gemfile | 1 + Gemfile.lock | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Gemfile b/Gemfile index 0dcf9b94..b45255ed 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ gem 'importmap-rails', '~> 2.0' gem "omniauth", "~> 2.1" gem "omniauth-google-oauth2", '~> 1.2' gem "omniauth-rails_csrf_protection", '~> 1.0' +gem 'pdf-reader' gem 'pg', '~> 1.5' gem 'puma', '~> 6.4' gem 'rollbar', '~> 3.6' diff --git a/Gemfile.lock b/Gemfile.lock index fb71e12d..264782b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: https://rubygems.org/ specs: + Ascii85 (2.0.1) actioncable (8.0.0) actionpack (= 8.0.0) activesupport (= 8.0.0) @@ -74,6 +75,7 @@ GEM uri (>= 0.13.1) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) + afm (0.2.2) ast (2.4.2) base64 (0.2.0) bcrypt (3.1.20) @@ -126,6 +128,7 @@ GEM ffi (1.15.5) globalid (1.2.1) activesupport (>= 6.1) + hashery (2.1.2) hashie (5.0.0) i18n (1.14.6) concurrent-ruby (~> 1.0) @@ -222,6 +225,12 @@ GEM parser (3.3.6.0) ast (~> 2.4.1) racc + pdf-reader (2.13.0) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk pg (1.5.9) psych (5.2.0) stringio @@ -318,6 +327,7 @@ GEM rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (1.13.0) + ruby-rc4 (0.1.5) rubyzip (2.3.2) sassc (2.4.0) ffi (~> 1.9) @@ -356,6 +366,8 @@ GEM thor (1.3.2) tilt (2.0.10) timeout (0.4.2) + ttfunk (1.8.0) + bigdecimal (~> 3.1) turbo-rails (2.0.11) actionpack (>= 6.0.0) railties (>= 6.0.0) @@ -396,6 +408,7 @@ DEPENDENCIES omniauth (~> 2.1) omniauth-google-oauth2 (~> 1.2) omniauth-rails_csrf_protection (~> 1.0) + pdf-reader pg (~> 1.5) puma (~> 6.4) rails (~> 8.0.0) From 6d018fa28736982429ae6cb99e73c8f7529104d3 Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Tue, 21 Jan 2025 21:31:09 -0300 Subject: [PATCH 02/12] Add new styles --- app/assets/stylesheets/_utility.scss | 29 +++++++++++++++++++++++++ app/assets/stylesheets/application.scss | 5 +++++ 2 files changed, 34 insertions(+) diff --git a/app/assets/stylesheets/_utility.scss b/app/assets/stylesheets/_utility.scss index 22b595fc..b06c3107 100644 --- a/app/assets/stylesheets/_utility.scss +++ b/app/assets/stylesheets/_utility.scss @@ -6,6 +6,35 @@ color: #19a819 !important; } +.color-red { + color: #ff0000 !important; +} + .d-none { display: none !important; } + +@mixin generate-margin-classes($direction, $property) { + @for $i from 0 through 6 { + .m#{$direction}-#{$i} { + margin-#{$property}: #{$i * 0.5}rem !important; + } + } +} + +@include generate-margin-classes(t, top); +@include generate-margin-classes(r, right); +@include generate-margin-classes(b, bottom); +@include generate-margin-classes(l, left); + +@mixin generate-margin-axis-classes($axis, $property1, $property2) { + @for $i from 0 through 6 { + .m#{$axis}-#{$i} { + margin-#{$property1}: #{$i * 0.5}rem !important; + margin-#{$property2}: #{$i * 0.5}rem !important; + } + } +} + +@include generate-margin-axis-classes(x, left, right); +@include generate-margin-axis-classes(y, top, bottom); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 721f6682..5c6eb654 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -221,3 +221,8 @@ body { #butInstall { padding-left: 0px; } + +.subtle-text { + font-size: 0.7em; + color: #666; +} From 9144e46ae73581fb614f2622f604b931af11182f Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Tue, 21 Jan 2025 21:54:58 -0300 Subject: [PATCH 03/12] Add force_add --- app/models/base_student.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/base_student.rb b/app/models/base_student.rb index 86a3382c..56598f09 100644 --- a/app/models/base_student.rb +++ b/app/models/base_student.rb @@ -11,6 +11,12 @@ def add(approvable) end end + def force_add(subject) + ids << subject.exam.id if subject.exam + ids << subject.course.id if subject.course + save! + end + def remove(approvable) ids.delete(approvable.id) if !approvable.is_exam? && approvable.subject.exam.present? && ids.include?(approvable.subject.exam.id) From 627d6a5cb16b4992649687846ccd7cd9b1ffb59f Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Tue, 21 Jan 2025 21:55:19 -0300 Subject: [PATCH 04/12] Add new routes and button --- app/views/shared/_user_menu.html.erb | 6 ++++++ config/routes.rb | 2 ++ 2 files changed, 8 insertions(+) diff --git a/app/views/shared/_user_menu.html.erb b/app/views/shared/_user_menu.html.erb index 0a607bba..512096ab 100644 --- a/app/views/shared/_user_menu.html.erb +++ b/app/views/shared/_user_menu.html.erb @@ -29,6 +29,12 @@ Editar Perfil <% end %> + <%= link_to new_academic_history_path, class: "mdc-deprecated-list-item", tabindex: 0 do %> + + + Importar Materias + + <% end %> <%= link_to destroy_user_session_path, method: :delete, class: "mdc-deprecated-list-item", tabindex: 0 do %> diff --git a/config/routes.rb b/config/routes.rb index 669cd17c..18e809fd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -29,4 +29,6 @@ resource :user_onboardings, only: :update resources :current_optional_subjects, only: :index + + resources :academic_histories, only: [:new, :create, :index] end From 4badc00ccf42de2d17e1219b3b936f94c5f45cdb Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Tue, 21 Jan 2025 21:55:49 -0300 Subject: [PATCH 05/12] Add controller and view --- .../academic_histories_controller.rb | 82 +++++++++++++++++++ app/views/academic_histories/new.html.erb | 35 ++++++++ 2 files changed, 117 insertions(+) create mode 100644 app/controllers/academic_histories_controller.rb create mode 100644 app/views/academic_histories/new.html.erb diff --git a/app/controllers/academic_histories_controller.rb b/app/controllers/academic_histories_controller.rb new file mode 100644 index 00000000..92ad7ee0 --- /dev/null +++ b/app/controllers/academic_histories_controller.rb @@ -0,0 +1,82 @@ +require 'pdf-reader' + +class AcademicHistoriesController < ApplicationController + AcademicEntry = Struct.new(:name, :credits, :number_of_failures, :date_of_completion, :grade) do + def approved? + grade != '***' + end + end + + def index + redirect_to new_academic_history_path + end + + def new + end + + def create + file = params[:file] + @failed_entries = [] + @successful_entries = [] + + if file && file.content_type == 'application/pdf' + @academic_entries = academic_entries(file) + @academic_entries.each do |entry| + save_academic_entry(entry) if entry.approved? + end + else + flash[:error] = 'Please upload a valid PDF file' + end + render :new + end + + private + + def save_academic_entry(entry) + subject_match = Subject.where("lower(unaccent(name)) = lower(unaccent(?))", entry.name) + subject_match = subject_match.select { |subject| subject.credits == entry.credits.to_i } + active_subjects = subject_match.select { |subject| !subject.inactive? } + if subject_match.length == 1 + save_subject(subject_match.first) + elsif active_subjects.length == 1 + save_subject(active_subjects.first) + else + @failed_entries << entry + end + end + + def save_subject(subject) + current_student.force_add(subject) + @successful_entries << subject + end + + def academic_entries(file) + reader = PDF::Reader.new(file.path) + academic_entries = [] + + reader.pages.each do |page| + page.text.split("\n").each do |line| + line.match(subject_regex) do |match| + academic_entries << AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) + end + end + end + + academic_entries + end + + # SubjectName Credits NumberOfFailures DateOfCompletion Grade + def subject_regex + /\s*(.*?)\s+(\d+)\s+(\d+)\s+(#{date_regex.source})\s+(#{grade_regex.source})/ + end + + # The date can be either a date in the format DD/MM/YYYY or ********** + def date_regex + /\*{10}|\d\d\/\d\d\/\d\d\d\d/ + end + + # The grade can be either a number from 0 to 12, S/N or *** + def grade_regex + /\d+|S\/N|\*{3}/ + end +end diff --git a/app/views/academic_histories/new.html.erb b/app/views/academic_histories/new.html.erb new file mode 100644 index 00000000..14338f26 --- /dev/null +++ b/app/views/academic_histories/new.html.erb @@ -0,0 +1,35 @@ + +

Carga tu escolaridad para marcar automaticamente las materias que ya cursaste.

+

1) Entrar a Bedelias e iniciar session

+

2) Entrar a "Estudiante" -> "Escolaridad" -> "Solicitar Escolaridad"

+

3) Seleccionar tu carrera y "No" en "Resultados Intermedios"

+<%= form_with url: academic_histories_path, method: :post, local: true, html: { multipart: true } do |form| %> + +<% end %> + + From 2cb0d5ad0c717003fc55f599fa901a5f00645f02 Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 19:20:08 -0300 Subject: [PATCH 06/12] Move pdf parsing logic to lib --- .../academic_histories_controller.rb | 40 +----------------- app/models/base_student.rb | 2 +- lib/academic_history/pdf_processor.rb | 42 +++++++++++++++++++ 3 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 lib/academic_history/pdf_processor.rb diff --git a/app/controllers/academic_histories_controller.rb b/app/controllers/academic_histories_controller.rb index 92ad7ee0..6fb2717b 100644 --- a/app/controllers/academic_histories_controller.rb +++ b/app/controllers/academic_histories_controller.rb @@ -1,12 +1,6 @@ require 'pdf-reader' class AcademicHistoriesController < ApplicationController - AcademicEntry = Struct.new(:name, :credits, :number_of_failures, :date_of_completion, :grade) do - def approved? - grade != '***' - end - end - def index redirect_to new_academic_history_path end @@ -20,7 +14,7 @@ def create @successful_entries = [] if file && file.content_type == 'application/pdf' - @academic_entries = academic_entries(file) + @academic_entries = AcademicHistory::PdfProcessor.process(file) @academic_entries.each do |entry| save_academic_entry(entry) if entry.approved? end @@ -46,37 +40,7 @@ def save_academic_entry(entry) end def save_subject(subject) - current_student.force_add(subject) + current_student.force_add_subject(subject) @successful_entries << subject end - - def academic_entries(file) - reader = PDF::Reader.new(file.path) - academic_entries = [] - - reader.pages.each do |page| - page.text.split("\n").each do |line| - line.match(subject_regex) do |match| - academic_entries << AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) - end - end - end - - academic_entries - end - - # SubjectName Credits NumberOfFailures DateOfCompletion Grade - def subject_regex - /\s*(.*?)\s+(\d+)\s+(\d+)\s+(#{date_regex.source})\s+(#{grade_regex.source})/ - end - - # The date can be either a date in the format DD/MM/YYYY or ********** - def date_regex - /\*{10}|\d\d\/\d\d\/\d\d\d\d/ - end - - # The grade can be either a number from 0 to 12, S/N or *** - def grade_regex - /\d+|S\/N|\*{3}/ - end end diff --git a/app/models/base_student.rb b/app/models/base_student.rb index 56598f09..b3d7a371 100644 --- a/app/models/base_student.rb +++ b/app/models/base_student.rb @@ -11,7 +11,7 @@ def add(approvable) end end - def force_add(subject) + def force_add_subject(subject) ids << subject.exam.id if subject.exam ids << subject.course.id if subject.course save! diff --git a/lib/academic_history/pdf_processor.rb b/lib/academic_history/pdf_processor.rb new file mode 100644 index 00000000..f349df18 --- /dev/null +++ b/lib/academic_history/pdf_processor.rb @@ -0,0 +1,42 @@ +require 'pdf-reader' + +module AcademicHistory + module PdfProcessor + AcademicEntry = Struct.new(:name, :credits, :number_of_failures, :date_of_completion, :grade) do + def approved? + grade != '***' + end + end + extend self + + def process(file) + reader = PDF::Reader.new(file.path) + academic_entries = [] + + reader.pages.each do |page| + page.text.split("\n").each do |line| + line.match(subject_regex) do |match| + academic_entries << AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) + end + end + end + + academic_entries + end + + # SubjectName Credits NumberOfFailures DateOfCompletion Concept + def subject_regex + /\s*(.*?)\s+(\d+)\s+(\d+)\s+(#{date_regex.source})\s+(#{concept_regex.source})/ + end + + # The date can be either a date in the format DD/MM/YYYY or ********** + def date_regex + /\*{10}|\d\d\/\d\d\/\d\d\d\d/ + end + + # The concept can be either a number from 0 to 12, S/N or *** + def concept_regex + /Aceptable|Bueno|Muy Bueno|Excelente|S\/C|\*{3}/ + end + end +end From 35f51c071505a4e958a6e50e5f66a6bd46a27456 Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 20:10:30 -0300 Subject: [PATCH 07/12] Add pdf processor test --- .../academic_history/pdf_processor_spec.rb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 spec/lib/academic_history/pdf_processor_spec.rb diff --git a/spec/lib/academic_history/pdf_processor_spec.rb b/spec/lib/academic_history/pdf_processor_spec.rb new file mode 100644 index 00000000..38c0ea1c --- /dev/null +++ b/spec/lib/academic_history/pdf_processor_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' + +RSpec.describe AcademicHistory::PdfProcessor, type: :lib do + let(:pdf_mock) {[ + double('page', text: " Test Subject 1 10 0 20/02/2024 Aceptable"), + double('page', text: " Test Subject 2 9 0 20/02/2024 Bueno"), + double('page', text: " Test Subject 3 8 0 20/02/2024 Muy Bueno"), + double('page', text: " Test Subject 4 7 0 20/02/2024 Excelente"), + double('page', text: " Test Subject 5 6 0 20/02/2024 S/C"), + double('page', text: " Failed Subject 1 8 1 ********** ***") + ]} + + let(:academic_entries) { described_class::AcademicEntry } + + describe '.process' do + let(:file) { double('file', path: 'path') } + let(:reader) { double('reader') } + let(:academic_entries_list) {[ + academic_entries.new('Test Subject 1', '10', '0', '20/02/2024', 'Aceptable'), + academic_entries.new('Test Subject 2', '9', '0', '20/02/2024', 'Bueno'), + academic_entries.new('Test Subject 3', '8', '0', '20/02/2024', 'Muy Bueno'), + academic_entries.new('Test Subject 4', '7', '0', '20/02/2024', 'Excelente'), + academic_entries.new('Test Subject 5', '6', '0', '20/02/2024', 'S/C'), + academic_entries.new('Failed Subject 1', '8', '1', '**********', '***') + ]} + + before do + allow(PDF::Reader).to receive(:new).with('path').and_return(reader) + allow(reader).to receive(:pages).and_return(pdf_mock) + end + + it 'returns a list of academic entries' do + expect(described_class.process(file)).to all(be_an(academic_entries)) + end + + it 'parses the text from the pdf file' do + expect(described_class.process(file)).to eq(academic_entries_list) + end + end + + describe 'academic entry' do + let(:entry) { academic_entries.new('Test Subject', '10', '0', '20/02/2024', 'Aceptable') } + let(:failed_entry) { academic_entries.new('Failed Subject', '10', '1', '**********', '***') } + + it 'has a name' do + expect(entry.name).to eq('Test Subject') + end + + it 'has a number of credits' do + expect(entry.credits).to eq('10') + end + + it 'has a number of failures' do + expect(entry.number_of_failures).to eq('0') + end + + it 'has a date of completion' do + expect(entry.date_of_completion).to eq('20/02/2024') + end + + it 'has a grade' do + expect(entry.grade).to eq('Aceptable') + end + + it 'is approved if the grade is not ***' do + expect(entry).to be_approved + end + + it 'is not approved if the grade is ***' do + expect(failed_entry).not_to be_approved + end + end +end From ebc0c711cb65ad248abd21f93fc349daf3b1f05c Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 20:17:48 -0300 Subject: [PATCH 08/12] Rubocop fixes --- .../academic_history/pdf_processor_spec.rb | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/spec/lib/academic_history/pdf_processor_spec.rb b/spec/lib/academic_history/pdf_processor_spec.rb index 38c0ea1c..bcc4ab85 100644 --- a/spec/lib/academic_history/pdf_processor_spec.rb +++ b/spec/lib/academic_history/pdf_processor_spec.rb @@ -1,28 +1,34 @@ require 'rails_helper' RSpec.describe AcademicHistory::PdfProcessor, type: :lib do - let(:pdf_mock) {[ - double('page', text: " Test Subject 1 10 0 20/02/2024 Aceptable"), - double('page', text: " Test Subject 2 9 0 20/02/2024 Bueno"), - double('page', text: " Test Subject 3 8 0 20/02/2024 Muy Bueno"), - double('page', text: " Test Subject 4 7 0 20/02/2024 Excelente"), - double('page', text: " Test Subject 5 6 0 20/02/2024 S/C"), - double('page', text: " Failed Subject 1 8 1 ********** ***") - ]} + # rubocop:disable Layout/LineLength + let(:pdf_mock) { + [ + double('page', text: " Test Subject 1 10 0 20/02/2024 Aceptable"), + double('page', text: " Test Subject 2 9 0 20/02/2024 Bueno"), + double('page', text: " Test Subject 3 8 0 20/02/2024 Muy Bueno"), + double('page', text: " Test Subject 4 7 0 20/02/2024 Excelente"), + double('page', text: " Test Subject 5 6 0 20/02/2024 S/C"), + double('page', text: " Failed Subject 1 8 1 ********** ***") + ] + } + # rubocop:enable Layout/LineLength let(:academic_entries) { described_class::AcademicEntry } describe '.process' do let(:file) { double('file', path: 'path') } let(:reader) { double('reader') } - let(:academic_entries_list) {[ - academic_entries.new('Test Subject 1', '10', '0', '20/02/2024', 'Aceptable'), - academic_entries.new('Test Subject 2', '9', '0', '20/02/2024', 'Bueno'), - academic_entries.new('Test Subject 3', '8', '0', '20/02/2024', 'Muy Bueno'), - academic_entries.new('Test Subject 4', '7', '0', '20/02/2024', 'Excelente'), - academic_entries.new('Test Subject 5', '6', '0', '20/02/2024', 'S/C'), - academic_entries.new('Failed Subject 1', '8', '1', '**********', '***') - ]} + let(:academic_entries_list) { + [ + academic_entries.new('Test Subject 1', '10', '0', '20/02/2024', 'Aceptable'), + academic_entries.new('Test Subject 2', '9', '0', '20/02/2024', 'Bueno'), + academic_entries.new('Test Subject 3', '8', '0', '20/02/2024', 'Muy Bueno'), + academic_entries.new('Test Subject 4', '7', '0', '20/02/2024', 'Excelente'), + academic_entries.new('Test Subject 5', '6', '0', '20/02/2024', 'S/C'), + academic_entries.new('Failed Subject 1', '8', '1', '**********', '***') + ] + } before do allow(PDF::Reader).to receive(:new).with('path').and_return(reader) From 86aadbf76bd752b4a6d91ab928fdcda9b016710a Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 20:58:22 -0300 Subject: [PATCH 09/12] Change process to be an enumerator --- app/controllers/academic_histories_controller.rb | 4 ++-- lib/academic_history/pdf_processor.rb | 15 +++++++-------- spec/lib/academic_history/pdf_processor_spec.rb | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/controllers/academic_histories_controller.rb b/app/controllers/academic_histories_controller.rb index 6fb2717b..2da76352 100644 --- a/app/controllers/academic_histories_controller.rb +++ b/app/controllers/academic_histories_controller.rb @@ -14,8 +14,8 @@ def create @successful_entries = [] if file && file.content_type == 'application/pdf' - @academic_entries = AcademicHistory::PdfProcessor.process(file) - @academic_entries.each do |entry| + academic_entries = AcademicHistory::PdfProcessor.process(file) + academic_entries.each do |entry| save_academic_entry(entry) if entry.approved? end else diff --git a/lib/academic_history/pdf_processor.rb b/lib/academic_history/pdf_processor.rb index f349df18..7762fb5a 100644 --- a/lib/academic_history/pdf_processor.rb +++ b/lib/academic_history/pdf_processor.rb @@ -10,18 +10,17 @@ def approved? extend self def process(file) - reader = PDF::Reader.new(file.path) - academic_entries = [] + Enumerator.new do |yielder| + reader = PDF::Reader.new(file.path) - reader.pages.each do |page| - page.text.split("\n").each do |line| - line.match(subject_regex) do |match| - academic_entries << AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) + reader.pages.each do |page| + page.text.split("\n").each do |line| + line.match(subject_regex) do |match| + yielder << AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) + end end end end - - academic_entries end # SubjectName Credits NumberOfFailures DateOfCompletion Concept diff --git a/spec/lib/academic_history/pdf_processor_spec.rb b/spec/lib/academic_history/pdf_processor_spec.rb index bcc4ab85..e5b69dd5 100644 --- a/spec/lib/academic_history/pdf_processor_spec.rb +++ b/spec/lib/academic_history/pdf_processor_spec.rb @@ -36,11 +36,11 @@ end it 'returns a list of academic entries' do - expect(described_class.process(file)).to all(be_an(academic_entries)) + expect(described_class.process(file).to_a).to all(be_an(academic_entries)) end it 'parses the text from the pdf file' do - expect(described_class.process(file)).to eq(academic_entries_list) + expect(described_class.process(file).to_a).to eq(academic_entries_list) end end From ba09deb2674fdf6026cbaec5bc7c896ed1792768 Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 21:05:02 -0300 Subject: [PATCH 10/12] Remove scss --- app/assets/stylesheets/_utility.scss | 113 +++++++++++++++++++++------ 1 file changed, 89 insertions(+), 24 deletions(-) diff --git a/app/assets/stylesheets/_utility.scss b/app/assets/stylesheets/_utility.scss index b06c3107..f70dea16 100644 --- a/app/assets/stylesheets/_utility.scss +++ b/app/assets/stylesheets/_utility.scss @@ -14,27 +14,92 @@ display: none !important; } -@mixin generate-margin-classes($direction, $property) { - @for $i from 0 through 6 { - .m#{$direction}-#{$i} { - margin-#{$property}: #{$i * 0.5}rem !important; - } - } -} - -@include generate-margin-classes(t, top); -@include generate-margin-classes(r, right); -@include generate-margin-classes(b, bottom); -@include generate-margin-classes(l, left); - -@mixin generate-margin-axis-classes($axis, $property1, $property2) { - @for $i from 0 through 6 { - .m#{$axis}-#{$i} { - margin-#{$property1}: #{$i * 0.5}rem !important; - margin-#{$property2}: #{$i * 0.5}rem !important; - } - } -} - -@include generate-margin-axis-classes(x, left, right); -@include generate-margin-axis-classes(y, top, bottom); +.mt-0 { margin-top: 0rem !important; } +.mt-1 { margin-top: 0.5rem !important; } +.mt-2 { margin-top: 1rem !important; } +.mt-3 { margin-top: 1.5rem !important; } +.mt-4 { margin-top: 2rem !important; } +.mt-5 { margin-top: 2.5rem !important; } +.mt-6 { margin-top: 3rem !important; } + +.mr-0 { margin-right: 0rem !important; } +.mr-1 { margin-right: 0.5rem !important; } +.mr-2 { margin-right: 1rem !important; } +.mr-3 { margin-right: 1.5rem !important; } +.mr-4 { margin-right: 2rem !important; } +.mr-5 { margin-right: 2.5rem !important; } +.mr-6 { margin-right: 3rem !important; } + +.mb-0 { margin-bottom: 0rem !important; } +.mb-1 { margin-bottom: 0.5rem !important; } +.mb-2 { margin-bottom: 1rem !important; } +.mb-3 { margin-bottom: 1.5rem !important; } +.mb-4 { margin-bottom: 2rem !important; } +.mb-5 { margin-bottom: 2.5rem !important; } +.mb-6 { margin-bottom: 3rem !important; } + +.ml-0 { margin-left: 0rem !important; } +.ml-1 { margin-left: 0.5rem !important; } +.ml-2 { margin-left: 1rem !important; } +.ml-3 { margin-left: 1.5rem !important; } +.ml-4 { margin-left: 2rem !important; } +.ml-5 { margin-left: 2.5rem !important; } +.ml-6 { margin-left: 3rem !important; } + +.mx-0 { + margin-left: 0rem !important; + margin-right: 0rem !important; +} +.mx-1 { + margin-left: 0.5rem !important; + margin-right: 0.5rem !important; +} +.mx-2 { + margin-left: 1rem !important; + margin-right: 1rem !important; +} +.mx-3 { + margin-left: 1.5rem !important; + margin-right: 1.5rem !important; +} +.mx-4 { + margin-left: 2rem !important; + margin-right: 2rem !important; +} +.mx-5 { + margin-left: 2.5rem !important; + margin-right: 2.5rem !important; +} +.mx-6 { + margin-left: 3rem !important; + margin-right: 3rem !important; +} + +.my-0 { + margin-top: 0rem !important; + margin-bottom: 0rem !important; +} +.my-1 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} +.my-2 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} +.my-3 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} +.my-4 { + margin-top: 2rem !important; + margin-bottom: 2rem !important; +} +.my-5 { + margin-top: 2.5rem !important; + margin-bottom: 2.5rem !important; +} +.my-6 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} From 1439e3d5915aeba53aed8057d02114a3d56afa66 Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 21:20:22 -0300 Subject: [PATCH 11/12] Make PdfProcessor an enum --- .../academic_histories_controller.rb | 3 +-- lib/academic_history/pdf_processor.rb | 25 +++++++++++-------- .../academic_history/pdf_processor_spec.rb | 4 +-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/controllers/academic_histories_controller.rb b/app/controllers/academic_histories_controller.rb index 2da76352..04d8156f 100644 --- a/app/controllers/academic_histories_controller.rb +++ b/app/controllers/academic_histories_controller.rb @@ -14,8 +14,7 @@ def create @successful_entries = [] if file && file.content_type == 'application/pdf' - academic_entries = AcademicHistory::PdfProcessor.process(file) - academic_entries.each do |entry| + AcademicHistory::PdfProcessor.new(file).each do |entry| save_academic_entry(entry) if entry.approved? end else diff --git a/lib/academic_history/pdf_processor.rb b/lib/academic_history/pdf_processor.rb index 7762fb5a..4eb232cb 100644 --- a/lib/academic_history/pdf_processor.rb +++ b/lib/academic_history/pdf_processor.rb @@ -1,23 +1,24 @@ require 'pdf-reader' module AcademicHistory - module PdfProcessor + class PdfProcessor + include Enumerable + AcademicEntry = Struct.new(:name, :credits, :number_of_failures, :date_of_completion, :grade) do def approved? grade != '***' end end - extend self - def process(file) - Enumerator.new do |yielder| - reader = PDF::Reader.new(file.path) + def initialize(file) + @reader = PDF::Reader.new(file.path) + end - reader.pages.each do |page| - page.text.split("\n").each do |line| - line.match(subject_regex) do |match| - yielder << AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) - end + def each(&block) + reader.pages.each do |page| + page.text.split("\n").each do |line| + line.match(subject_regex) do |match| + block.call(AcademicEntry.new(match[1], match[2], match[3], match[4], match[5])) end end end @@ -37,5 +38,9 @@ def date_regex def concept_regex /Aceptable|Bueno|Muy Bueno|Excelente|S\/C|\*{3}/ end + + private + + attr_reader :reader end end diff --git a/spec/lib/academic_history/pdf_processor_spec.rb b/spec/lib/academic_history/pdf_processor_spec.rb index e5b69dd5..20896668 100644 --- a/spec/lib/academic_history/pdf_processor_spec.rb +++ b/spec/lib/academic_history/pdf_processor_spec.rb @@ -36,11 +36,11 @@ end it 'returns a list of academic entries' do - expect(described_class.process(file).to_a).to all(be_an(academic_entries)) + expect(described_class.new(file).to_a).to all(be_an(academic_entries)) end it 'parses the text from the pdf file' do - expect(described_class.process(file).to_a).to eq(academic_entries_list) + expect(described_class.new(file).to_a).to eq(academic_entries_list) end end From f49b2fcf6fed66d4fa05dae2760522cb68f87039 Mon Sep 17 00:00:00 2001 From: Franco Olivera Date: Thu, 13 Feb 2025 21:27:08 -0300 Subject: [PATCH 12/12] Fix lint --- lib/academic_history/pdf_processor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/academic_history/pdf_processor.rb b/lib/academic_history/pdf_processor.rb index 4eb232cb..2d179a31 100644 --- a/lib/academic_history/pdf_processor.rb +++ b/lib/academic_history/pdf_processor.rb @@ -14,11 +14,11 @@ def initialize(file) @reader = PDF::Reader.new(file.path) end - def each(&block) + def each reader.pages.each do |page| page.text.split("\n").each do |line| line.match(subject_regex) do |match| - block.call(AcademicEntry.new(match[1], match[2], match[3], match[4], match[5])) + yield AcademicEntry.new(match[1], match[2], match[3], match[4], match[5]) end end end