From 5550b9563b9bf8113522231648234818cfc1e4f5 Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:15:06 +0300 Subject: [PATCH 1/7] feat: add Contact class with validation for phone, telegram, and email --- lab2/contact.rb | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 lab2/contact.rb diff --git a/lab2/contact.rb b/lab2/contact.rb new file mode 100644 index 0000000..05d8df6 --- /dev/null +++ b/lab2/contact.rb @@ -0,0 +1,52 @@ +class Contact + attr_reader :phone, :telegram, :email + + def initialize(phone: nil, telegram: nil, email: nil) + @phone = phone + @telegram = telegram + @email = email + validate_contacts + end + + def valid_phone_number? + return true if @phone.nil? + + /\A\+?[0-9]{10,15}\z/.match?(@phone) + end + + def valid_telegram? + return true if @telegram.nil? + + /\A@[A-Za-z0-9_]{5,32}\z/.match?(@telegram) + end + + def valid_email? + return true if @email.nil? + + /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/.match?(@email) + end + + def present? + @phone || @telegram || @email + end + + def info + return "Phone: #{@phone}" if @phone + return "Telegram: #{@telegram}" if @telegram + return "Email: #{@email}" if @email + + 'No contact available' + end + + private + + def validate_contacts + if !present? + raise ArgumentError, 'At least one contact (phone, telegram, or email) is required' + end + + raise ArgumentError, "Invalid phone number format: #{@phone}" if @phone && !valid_phone_number? + raise ArgumentError, "Invalid telegram format: #{@telegram}" if @telegram && !valid_telegram? + raise ArgumentError, "Invalid email format: #{@email}" if @email && !valid_email? + end +end From 5cd9be99e33749328e300c3e73734cc14ad804f4 Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:15:54 +0300 Subject: [PATCH 2/7] refactor: simplify Person class initialization and validation logic --- lab2/person.rb | 66 ++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/lab2/person.rb b/lab2/person.rb index 7fc8dad..3a6fdad 100644 --- a/lab2/person.rb +++ b/lab2/person.rb @@ -1,62 +1,42 @@ +require_relative 'contact' + class Person attr_accessor :id, :git + attr_reader :contact - def initialize(args = {}) - @id = args[:id] || nil - @git = args[:git] - - raise ArgumentError, "Git link is required" unless git_present? - raise ArgumentError, "At least one contact (phone, telegram, or email) is required" unless contact_present? - end - - def set_contacts(phone: nil, telegram: nil, email: nil) - @phone = phone - raise ArgumentError, "Invalid phone number format: #{@phone}" if @phone && !self.class.valid_phone_number?(@phone) - - @telegram = telegram - raise ArgumentError, "Invalid telegram format: #{@telegram}" if @telegram && !self.class.valid_telegram?(@telegram) + def initialize(id:, git:, contact: Contact.new) + @id = id + @git = git + @contact = contact - @email = email - raise ArgumentError, "Invalid email format: #{@email}" if @email && !self.class.valid_email?(@email) + validate_person end - def self.valid_phone_number?(phone) - phone.match?(/\A\+?[0-9]{10,15}\z/) - end - - def self.valid_name?(name) - name.match?(/\A[А-Яа-яЁёA-Za-z\-]+\z/) + def git_present? + !@git.nil? && !@git.empty? end - def self.valid_telegram?(telegram) - telegram.nil? || telegram.match?(/\A@[A-Za-z0-9_]{5,32}\z/) + def contact_present? + @contact.present? end - def self.valid_email?(email) - email.nil? || email.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/) + def contact_info + @contact.info end def self.valid_git?(git) - git.nil? || git.match?(/\Ahttps:\/\/github\.com\/[A-Za-z0-9_\-]+\z/) - end - - def git_present? - !@git.nil? && !@git.empty? + /\Ahttps:\/\/github\.com\/[A-Za-z0-9_\-]+\z/.match?(git) end - def contact_present? - !(@phone.nil? || @phone.empty?) || !(@telegram.nil? || @telegram.empty?) || !(@email.nil? || @email.empty?) - end - - def contact_info - return "Phone: #{@phone}" if @phone - return "Telegram: #{@telegram}" if @telegram - return "Email: #{@email}" if @email - 'No contact available' + def self.valid_id?(id) + id.is_a?(String) && !id.strip.empty? end private - attr_reader :phone, :telegram, :email - attr_writer :phone, :telegram, :email -end \ No newline at end of file + def validate_person + raise ArgumentError, 'ID is required and must be a non-empty string' unless self.class.valid_id?(@id) + raise ArgumentError, 'Git link is required' unless git_present? + raise ArgumentError, 'Invalid Git link format' unless self.class.valid_git?(@git) + end +end From 72fa11dbfaa06d3f9d6af4d8097d716cc803b05c Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:31:12 +0300 Subject: [PATCH 3/7] refactor: enhance StudentShort initialization and add from_student method --- lab2/student_short.rb | 60 +++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/lab2/student_short.rb b/lab2/student_short.rb index 1bff260..3596d24 100644 --- a/lab2/student_short.rb +++ b/lab2/student_short.rb @@ -1,31 +1,41 @@ require_relative 'person' class StudentShort < Person - attr_reader :surname_initials, :contact - - def initialize(student) - super(id: student.id, git: student.git_info) - @surname_initials = student.surname_and_initials - @contact = student.contact_info - end - - def self.from_string(id, info_string) - parts = info_string.split(', ').map(&:strip) - surname_initials = parts[0] - git = parts[1].split(': ').last - contact = parts[2].split(': ').last + attr_reader :surname_initials, :contact + + def initialize(id:, git:, surname_initials:, contact:) + super(id: id, git: git, contact: contact) + @surname_initials = surname_initials + end + + def self.from_student(student) + new( + id: student.id, + git: student.git, + surname_initials: student.surname_and_initials, + contact: student.contact_info + ) + end + + def self.from_string(id, info_string) + parts = info_string.split(',').map(&:strip) + raise ArgumentError, 'Invalid info string format' if parts.size < 3 - new_instance = allocate - new_instance.send(:initialize_from_data, id, surname_initials, git, contact) - new_instance - end + surname_initials = parts[0] + git = parts[1].split(': ').last.strip + contact_string = parts[2].split(': ', 2).last.strip - private + contact = Contact.new_from_info(contact_string) - def initialize_from_data(id, surname_initials, git, contact) - @id = id - @surname_initials = surname_initials - @git = git - @contact = contact - end -end \ No newline at end of file + new( + id: id, + git: git, + surname_initials: surname_initials, + contact: contact.info + ) + end + + def to_s + "#{@surname_initials}, Git: #{@git}, Contact: #{@contact}" + end +end From d392be8d5d212c9c27c834a1af516dd84f54e4ed Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:31:30 +0300 Subject: [PATCH 4/7] refactor: update Student class initialization and validation; add StudentRepository for file operations --- lab2/student.rb | 167 +++++++++++++++++-------------------- lab2/student_repository.rb | 39 +++++++++ 2 files changed, 117 insertions(+), 89 deletions(-) create mode 100644 lab2/student_repository.rb diff --git a/lab2/student.rb b/lab2/student.rb index 57d26a7..c9b1145 100644 --- a/lab2/student.rb +++ b/lab2/student.rb @@ -1,92 +1,81 @@ require_relative 'person' +require_relative 'contact' class Student < Person - attr_accessor :surname, :name, :patronymic - - def initialize(args = {}) - super(args) - @surname = args.fetch(:surname) - raise ArgumentError, "Invalid surname format: #{@surname}" unless self.class.valid_name?(@surname) - - @name = args.fetch(:name) - raise ArgumentError, "Invalid name format: #{@name}" unless self.class.valid_name?(@name) - - @patronymic = args.fetch(:patronymic) - raise ArgumentError, "Invalid patronymic format: #{@patronymic}" unless self.class.valid_name?(@patronymic) - - set_contacts( - phone: args[:phone], - telegram: args[:telegram], - email: args[:email] - ) - end - - def self.from_string(student_string) - parts = student_string.split('|').map(&:strip) - surname, name, patronymic = parts[0].split(' ') - id = parts[1].split(': ').last.to_i - phone = parts[2].split(': ').last - telegram = parts[3].split(': ').last - email = parts[4].split(': ').last - git = parts[5].split(': ').last - - new( - surname: surname, - name: name, - patronymic: patronymic, - id: id, - phone: phone, - telegram: telegram, - email: email, - git: git - ) - end - - def self.read_from_txt(file_path) - raise IOError, "File path is invalid or file does not exist: #{file_path}" unless File.exist?(file_path) - - students = [] - - File.foreach(file_path) do |line| - line.strip! - next if line.empty? - - begin - student = from_string(line) - students << student - rescue ArgumentError => e - puts "Error processing line: '#{line}'. Reason: #{e.message}" - end - end - - students - end - - def self.write_to_txt(file_path, students) - raise ArgumentError, "Expected an array of Student objects" unless students.is_a?(Array) && students.all? { |s| s.is_a?(Student) } - - File.open(file_path, 'w') do |file| - students.each do |student| - file.puts student.to_s - end - end - - puts "Data successfully written to #{file_path}" - rescue IOError => e - puts "An error occurred while writing to the file: #{e.message}" - end - - def surname_and_initials - "#{@surname} #{name[0]}.#{patronymic[0]}." - end - - def to_s - "#{@surname} #{@name} #{@patronymic} | ID: #{@id || 'N/A'} | " \ - "Phone: #{@phone || 'N/A'} | Telegram: #{@telegram || 'N/A'} | " \ - "Email: #{@email || 'N/A'} | Git: #{@git || 'N/A'}" - end - - def get_info - "#{surname_and_initials}, Git: #{git_info}, Contact: #{contact_info}" - end -end \ No newline at end of file + attr_accessor :surname, :name, :patronymic + + NAME_REGEX = /\A[А-Яа-яЁёA-Za-z\-]+\z/ + + def initialize(id:, git:, contact:, surname:, name:, patronymic:) + super(id: id, git: git, contact: contact) + @surname = surname + @name = name + @patronymic = patronymic + + validate_student + end + + def self.from_string(student_string) + parts = student_string.split('|').map(&:strip) + raise ArgumentError, 'Invalid student string format' if parts.size < 6 + + name_parts = parts[0].split(' ') + raise ArgumentError, 'Invalid name format' if name_parts.size != 3 + + surname, name, patronymic = name_parts + id = parts[1].split(': ').last + phone = parts[2].split(': ').last + telegram = parts[3].split(': ').last + email = parts[4].split(': ').last + git = parts[5].split(': ').last + + contact = Contact.new(phone: phone, telegram: telegram, email: email) + + new( + id: id, + git: git, + contact: contact, + surname: surname, + name: name, + patronymic: patronymic + ) + end + + def surname_and_initials + "#{@surname} #{name_initial(@name)}.#{patronymic_initial(@patronymic)}." + end + + def to_s + "#{@surname} #{@name} #{@patronymic} | ID: #{@id} | " \ + "Phone: #{@contact.phone || 'N/A'} | Telegram: #{@contact.telegram || 'N/A'} | " \ + "Email: #{@contact.email || 'N/A'} | Git: #{@git}" + end + + def get_info + "#{surname_and_initials}, Git: #{@git}, Contact: #{contact_info}" + end + + private + + def validate_student + raise ArgumentError, 'Surname is required' if @surname.nil? || @surname.strip.empty? + raise ArgumentError, 'Name is required' if @name.nil? || @name.strip.empty? + raise ArgumentError, 'Patronymic is required' if @patronymic.nil? || @patronymic.strip.empty? + + raise ArgumentError, "Invalid surname format: #{@surname}" unless valid_name?(@surname) + raise ArgumentError, "Invalid name format: #{@name}" unless valid_name?(@name) + raise ArgumentError, "Invalid patronymic format: #{@patronymic}" unless valid_name?(@patronymic) + end + + def valid_name?(name) + NAME_REGEX.match?(name) + end + + def name_initial(name) + name[0].upcase + end + + def patronymic_initial(patronymic) + patronymic[0].upcase + end +end diff --git a/lab2/student_repository.rb b/lab2/student_repository.rb new file mode 100644 index 0000000..9679409 --- /dev/null +++ b/lab2/student_repository.rb @@ -0,0 +1,39 @@ +require_relative 'student' + +class StudentRepository + def self.read_from_txt(file_path) + raise IOError, "File does not exist: #{file_path}" unless File.exist?(file_path) + + students = [] + + File.foreach(file_path) do |line| + line.strip! + next if line.empty? + + begin + student = Student.from_string(line) + students << student + rescue ArgumentError => e + puts "Error processing line: '#{line}'. Reason: #{e.message}" + end + end + + students + end + + def self.write_to_txt(file_path, students) + unless students.is_a?(Array) && students.all? { |s| s.is_a?(Student) } + raise ArgumentError, 'Expected an array of Student objects' + end + + File.open(file_path, 'w') do |file| + students.each do |student| + file.puts student.to_s + end + end + + puts "Data successfully written to #{file_path}" + rescue IOError => e + puts "An error occurred while writing to the file: #{e.message}" + end +end From a62281362110dae9e8d04140a4ae41e59e24a96e Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:32:06 +0300 Subject: [PATCH 5/7] feat: add new_from_info method to Contact class for creating instances from formatted strings --- lab2/contact.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lab2/contact.rb b/lab2/contact.rb index 05d8df6..89cb1d5 100644 --- a/lab2/contact.rb +++ b/lab2/contact.rb @@ -49,4 +49,19 @@ class Contact raise ArgumentError, "Invalid telegram format: #{@telegram}" if @telegram && !valid_telegram? raise ArgumentError, "Invalid email format: #{@email}" if @email && !valid_email? end + + def self.new_from_info(info_string) + info_string = info_string.sub(/^Contact: /, '').strip + + case info_string + when /\APhone: (.+)\z/i + new(phone: Regexp.last_match(1).strip) + when /\ATelegram: (.+)\z/i + new(telegram: Regexp.last_match(1).strip) + when /\AEmail: (.+)\z/i + new(email: Regexp.last_match(1).strip) + else + raise ArgumentError, "Invalid contact info format: #{info_string}" + end + end end From 80e86e0f991854ba5efa92bfab3c9aded0be3b3e Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:32:15 +0300 Subject: [PATCH 6/7] feat: update class diagram in README.md to include Contact class and its relationships --- lab2/README.md | 77 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/lab2/README.md b/lab2/README.md index da36500..381e8dd 100644 --- a/lab2/README.md +++ b/lab2/README.md @@ -6,46 +6,63 @@ ```mermaid classDiagram + class Contact { + - phone : String? + - telegram : String? + - email : String? + + Contact(phone : String?, telegram : String?, email : String?) + + valid_phone_number() : Boolean + + valid_telegram() : Boolean + + valid_email() : Boolean + + present() : Boolean + + info() : String + - validate_contacts() + + new_from_info(info_string : String) : Contact + } + class Person { - +id: String - +git: String - -phone: String - -telegram: String - -email: String - +initialize(args: Hash) - +set_contacts(phone: String, telegram: String, email: String) - +git_present(): Boolean - +contact_present(): Boolean - +contact_info(): String - +valid_phone_number?(phone: String): Boolean - +valid_name?(name: String): Boolean - +valid_telegram?(telegram: String): Boolean - +valid_email?(email: String): Boolean - +valid_git?(git: String): Boolean + - id : String + - git : String + - contact : Contact + + Person(id : String, git : String, contact : Contact) + + git_present() : Boolean + + contact_present() : Boolean + + contact_info() : String + + valid_git(git : String) : Boolean + + valid_id(id : String) : Boolean + - validate_person() } - class Student { - +surname: String - +name: String - +patronymic: String - +initialize(args: Hash) - +from_string(student_string: String): Student - +read_from_txt(file_path: String): List~Student~ - +write_to_txt(file_path: String, students: List~Student~) - +surname_and_initials(): String - +to_s(): String - +get_info(): String + class StudentRepository { + + read_from_txt(file_path : String) : List~Student~ + + write_to_txt(file_path : String, students : List~Student~) } class StudentShort { - +surname_initials: String - +contact: String - +initialize(student: Student) - +from_string(id: String, info_string: String): StudentShort + - surname_initials : String + - contact : String + + StudentShort(id : String, git : String, surname_initials : String, contact : Contact) + + from_student(student : Student) : StudentShort + + from_string(id : String, info_string : String) : StudentShort + + to_s() : String + } + + class Student { + - surname : String + - name : String + - patronymic : String + + Student(id : String, git : String, contact : Contact, surname : String, name : String, patronymic : String) + + from_string(student_string : String) : Student + + surname_and_initials() : String + + to_s() : String + + get_info() : String + - validate_student() } + Contact <|-- Person Person <|-- Student Person <|-- StudentShort + Student <.. StudentRepository ``` где + - public, - - private From be544ae87dd5a36392253bec805d09bd44a32b6f Mon Sep 17 00:00:00 2001 From: Artem Darius Weber Date: Sat, 14 Dec 2024 13:32:21 +0300 Subject: [PATCH 7/7] feat: implement testing functions for Contact, Person, Student, and StudentShort classes; add sample student data --- lab2/main.rb | 112 +++++++++++++++++++++++----------------------- lab2/students.txt | 3 ++ 2 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 lab2/students.txt diff --git a/lab2/main.rb b/lab2/main.rb index e714667..d439c1f 100644 --- a/lab2/main.rb +++ b/lab2/main.rb @@ -1,59 +1,61 @@ +require_relative 'contact' +require_relative 'person' require_relative 'student' require_relative 'student_short' +require_relative 'student_repository' + +def test_classes + contact = Contact.new(phone: '+798912465', telegram: '@example_user', email: 'test@example.com') + puts "Contact Info: #{contact.info}" + + person = Person.new(id: '1', git: 'https://github.com/example', contact: contact) + puts "Person Contact Info: #{person.contact_info}" + + + student = Student.new( + id: '2', + git: 'https://github.com/student_example', + contact: contact, + surname: 'Иванов', + name: 'Иван', + patronymic: 'Иванович' + ) + puts "Student Full Info: #{student.to_s}" + puts "Student Initials: #{student.surname_and_initials}" + puts "Student Contact Info: #{student.contact_info}" + + + student_short = StudentShort.from_student(student) + puts "Student Short Info: #{student_short.to_s}" + + + file_path = 'students.txt' + students = StudentRepository.read_from_txt(file_path) + puts "Read Students from File:" + students.each { |s| puts s.to_s } + + + output_path = 'output_students.txt' + StudentRepository.write_to_txt(output_path, students) + puts "Students written to file: #{output_path}" +end + + +def test_parsing + student_string = 'Иванов Иван Иванович | ID: 3 | Phone: +79876543210 | Telegram: @ivan_user | Email: ivan@example.com | Git: https://github.com/ivanov' + student = Student.from_string(student_string) + puts "Parsed Student: #{student.to_s}" + + short_string = 'Иванов И.И., Git: https://github.com/ivanov, Contact: Phone: +79876543210' + student_short = StudentShort.from_string('4', short_string) + puts "Parsed StudentShort: #{student_short.to_s}" +end + + +if __FILE__ == $0 + puts "=== Testing Classes ===" + test_classes -begin - student_string = "Норакет Норакет Норакет | ID: 2 | Phone: +1234567890 | Telegram: @noracat | Email: nora@example.com | Git: https://github.com/nora" - student_from_string = Student.from_string(student_string) - puts "Создан объект из строки:\n#{student_from_string}" - - - student1 = Student.new( - surname: 'Алексеевич', - name: 'Артем-Дариус', - patronymic: 'Вебер', - id: 1, - git: 'https://github.com/space-creator' - ) - - student1.set_contacts( - phone: '+79891242223', - telegram: '@alstroemeria22', - email: 'no-replay@djft.ru' - ) - - puts student1.get_info - puts "Surname and Initials: #{student1.surname_and_initials}" - puts "Git Info: #{student1.git_info}" - puts "Contact Info: #{student1.contact_info}" - - student2 = Student.new( - surname: 'Норакет', - name: 'Норакет', - patronymic: 'Фамилия' - ) - - student2.set_contacts( - phone: '+70000000000' - ) - - puts student1 - puts '-' * 40 - puts student2 - - - student_short_from_student = StudentShort.new(student1) - puts "StudentShort from Student object:" - puts "ID: #{student_short_from_student.id}" - puts "Surname and Initials: #{student_short_from_student.surname_initials}" - puts "Git: #{student_short_from_student.git}" - puts "Contact: #{student_short_from_student.contact}" - - student_short_from_string = StudentShort.from_string(5, 'Skye A.A., Git: https://github.com/skye, Contact: Phone: +4923467890') - puts "StudentShort from string:" - puts "ID: #{student_short_from_string.id}" - puts "Surname and Initials: #{student_short_from_string.surname_initials}" - puts "Git: #{student_short_from_string.git}" - puts "Contact: #{student_short_from_string.contact}" -rescue ArgumentError => e - puts "Ошибка: #{e.message}" + puts "\n=== Testing Parsing ===" + test_parsing end diff --git a/lab2/students.txt b/lab2/students.txt new file mode 100644 index 0000000..8713015 --- /dev/null +++ b/lab2/students.txt @@ -0,0 +1,3 @@ +Иванов Иван Иванович | ID: 1 | Phone: +12345678901 | Telegram: @ivanov_user | Email: ivanov@example.com | Git: https://github.com/ivanov +Петров Петр Петрович | ID: 2 | Phone: +98765432101 | Telegram: @petrov_user | Email: petrov@example.com | Git: https://github.com/petrov +Сидоров Сидор Сидорович | ID: 3 | Phone: +56789012345 | Telegram: @sidorov_user | Email: sidorov@example.com | Git: https://github.com/sidorov