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 diff --git a/lab2/contact.rb b/lab2/contact.rb new file mode 100644 index 0000000..89cb1d5 --- /dev/null +++ b/lab2/contact.rb @@ -0,0 +1,67 @@ +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 + + 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 diff --git a/lab2/main.rb b/lab2/main.rb index c662531..d439c1f 100644 --- a/lab2/main.rb +++ b/lab2/main.rb @@ -1,65 +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 -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', - # Передаём контакты сразу, чтобы не было ошибок при инициализации - phone: '+79891242223' - ) - - # Можно обновить контакты при необходимости - 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: добавляем git и контакт - student2 = Student.new( - surname: 'Норакет', - name: 'Норакет', - patronymic: 'Фамилия', - git: 'https://github.com/noraket', - phone: '+70000000001' - ) +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}" - # Можно обновить контакты, если нужно другой номер - student2.set_contacts( - phone: '+70000000000' - ) + 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 - 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}" +if __FILE__ == $0 + puts "=== Testing Classes ===" + test_classes - 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/person.rb b/lab2/person.rb index b3eab75..3a6fdad 100644 --- a/lab2/person.rb +++ b/lab2/person.rb @@ -1,72 +1,42 @@ -class Person - attr_accessor :id, :git, :name - - def initialize(args = {}) - @id = args[:id] - @git = args[:git] - @name = args[:name] - - raise ArgumentError, "ID должен быть числом" if @id && !@id.is_a?(Integer) - - raise ArgumentError, "Требуется ссылка на GitHub" unless git_present? - raise ArgumentError, "Некорректная ссылка на GitHub: #{@git}" unless self.class.valid_git?(@git) - - raise ArgumentError, "Требуется имя" if @name.nil? || @name.strip.empty? - raise ArgumentError, "Некорректный формат имени: #{@name}" unless self.class.valid_name?(@name) - end +require_relative 'contact' - def set_contacts(phone: nil, telegram: nil, email: nil) - @phone = phone - raise ArgumentError, "Некорректный формат номера телефона: #{@phone}" if @phone && !self.class.valid_phone_number?(@phone) - - @telegram = telegram - raise ArgumentError, "Некорректный формат Telegram: #{@telegram}" if @telegram && !self.class.valid_telegram?(@telegram) - - @email = email - raise ArgumentError, "Некорректный формат email: #{@email}" if @email && !self.class.valid_email?(@email) +class Person + attr_accessor :id, :git + attr_reader :contact - raise ArgumentError, "Требуется хотя бы один контакт (телефон, телеграм или email)" unless contact_present? - end + def initialize(id:, git:, contact: Contact.new) + @id = id + @git = git + @contact = contact - def self.valid_phone_number?(phone) - phone.match?(/\A\+?[0-9]{10,15}\z/) + validate_person 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/) + /\Ahttps:\/\/github\.com\/[A-Za-z0-9_\-]+\z/.match?(git) end - def git_present? - !@git.nil? && !@git.empty? - 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 - 'Контактная информация отсутствует' + def self.valid_id?(id) + id.is_a?(String) && !id.strip.empty? end private - attr_reader :phone, :telegram, :email - attr_writer :phone, :telegram, :email + 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 diff --git a/lab2/student.rb b/lab2/student.rb index 2f725e6..c9b1145 100644 --- a/lab2/student.rb +++ b/lab2/student.rb @@ -1,132 +1,81 @@ require_relative 'person' +require_relative 'contact' class Student < Person attr_accessor :surname, :name, :patronymic - def initialize(args = {}) - super(args) + NAME_REGEX = /\A[А-Яа-яЁёA-Za-z\-]+\z/ - @surname = args.fetch(:surname) { raise ArgumentError, "Surname is required" } - raise ArgumentError, "Invalid surname format: #{@surname}" unless self.class.valid_name?(@surname) + def initialize(id:, git:, contact:, surname:, name:, patronymic:) + super(id: id, git: git, contact: contact) + @surname = surname + @name = name + @patronymic = patronymic - @name = args.fetch(:name) { raise ArgumentError, "Name is required" } - raise ArgumentError, "Invalid name format: #{@name}" unless self.class.valid_name?(@name) - - @patronymic = args.fetch(:patronymic) { raise ArgumentError, "Patronymic is required" } - raise ArgumentError, "Invalid patronymic format: #{@patronymic}" unless self.class.valid_name?(@patronymic) - - set_contacts( - phone: args[:phone], - telegram: args[:telegram], - email: args[:email] - ) + validate_student end def self.from_string(student_string) - raise ArgumentError, "Input string cannot be nil or empty" if student_string.nil? || student_string.strip.empty? - parts = student_string.split('|').map(&:strip) - # Ожидается формат: "Фамилия Имя Отчество | ID: X | Phone: ... | Telegram: ... | Email: ... | Git: ..." - # Проверяем, что частей как минимум 6: ФИО, ID, Phone, Telegram, Email, Git - unless parts.size == 6 - raise ArgumentError, "Expected 6 parts separated by '|', got #{parts.size} in '#{student_string}'" - end - - fio_part = parts[0] - raise ArgumentError, "FIO part is empty" if fio_part.nil? || fio_part.strip.empty? - - fio_elements = fio_part.split(' ') - raise ArgumentError, "Expected Surname Name Patronymic in '#{fio_part}'" unless fio_elements.size == 3 - surname, name, patronymic = fio_elements - - id_part = parts[1] - raise ArgumentError, "ID part is empty or invalid" if id_part.nil? || !id_part.include?('ID:') - id_str = id_part.split(': ').last - raise ArgumentError, "ID value is missing in '#{id_part}'" if id_str.nil? || id_str.strip.empty? - id = id_str.to_i - raise ArgumentError, "ID must be a valid integer: '#{id_str}'" if id == 0 && id_str != "0" - - phone_part = parts[2] - raise ArgumentError, "Phone part is invalid or missing" if phone_part.nil? || !phone_part.include?('Phone:') - phone = phone_part.split(': ').last - phone = nil if phone == 'N/A' - - telegram_part = parts[3] - raise ArgumentError, "Telegram part is invalid or missing" if telegram_part.nil? || !telegram_part.include?('Telegram:') - telegram = telegram_part.split(': ').last - telegram = nil if telegram == 'N/A' - - email_part = parts[4] - raise ArgumentError, "Email part is invalid or missing" if email_part.nil? || !email_part.include?('Email:') - email = email_part.split(': ').last - email = nil if email == 'N/A' - - git_part = parts[5] - raise ArgumentError, "Git part is invalid or missing" if git_part.nil? || !git_part.include?('Git:') - git = git_part.split(': ').last - git = nil if git == 'N/A' + 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, - id: id, - phone: phone, - telegram: telegram, - email: email, - git: git + patronymic: patronymic ) 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? + def surname_and_initials + "#{@surname} #{name_initial(@name)}.#{patronymic_initial(@patronymic)}." + end - begin - student = from_string(line) - students << student - rescue ArgumentError => e - puts "Error processing line: '#{line}'. Reason: #{e.message}" - rescue StandardError => e - puts "An unexpected error occurred while processing line: '#{line}'. Reason: #{e.message}" - end - 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 - students + def get_info + "#{surname_and_initials}, Git: #{@git}, Contact: #{contact_info}" 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) } + private - File.open(file_path, 'w') do |file| - students.each do |student| - file.puts student.to_s - end - end + 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? - puts "Data successfully written to #{file_path}" - rescue IOError => e - puts "An error occurred while writing to the file: #{e.message}" - rescue StandardError => e - puts "An unexpected error occurred while writing to the file: #{e.message}" + 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 surname_and_initials - "#{@surname} #{name[0]}.#{patronymic[0]}." + def valid_name?(name) + NAME_REGEX.match?(name) 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'}" + def name_initial(name) + name[0].upcase end - def get_info - "#{surname_and_initials}, Git: #{git_info}, Contact: #{contact_info}" + 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 diff --git a/lab2/student_short.rb b/lab2/student_short.rb index 327ef3d..3596d24 100644 --- a/lab2/student_short.rb +++ b/lab2/student_short.rb @@ -3,84 +3,39 @@ require_relative 'person' class StudentShort < Person attr_reader :surname_initials, :contact - def initialize(student) - # Проверяем наличие необходимых методов и данных у student - raise ArgumentError, "Объект student не имеет метод id" unless student.respond_to?(:id) - raise ArgumentError, "Объект student не имеет метод git_info" unless student.respond_to?(:git_info) - raise ArgumentError, "Объект student не имеет метод surname_and_initials" unless student.respond_to?(:surname_and_initials) - raise ArgumentError, "Объект student не имеет метод contact_info" unless student.respond_to?(:contact_info) - - super(id: student.id, git: student.git_info) - - @surname_initials = student.surname_and_initials - raise ArgumentError, "Поле surname_initials не может быть пустым" if @surname_initials.nil? || @surname_initials.strip.empty? - # Дополнительная проверка на формат имен, если требуется - # Если формат @surname_initials может быть сложным (например, "Иванов И.И."), - # то можно добавить отдельную проверку формата: - unless valid_surname_initials?(@surname_initials) - raise ArgumentError, "Некорректный формат фамилии и инициалов: #{@surname_initials}" - end + def initialize(id:, git:, surname_initials:, contact:) + super(id: id, git: git, contact: contact) + @surname_initials = surname_initials + end - @contact = student.contact_info - raise ArgumentError, "Контактная информация не может быть пустой" if @contact.nil? || @contact.strip.empty? + 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) - raise ArgumentError, "info_string не может быть пустой" if info_string.nil? || info_string.strip.empty? - - parts = info_string.split(', ').map(&:strip) - # Ожидается три части: Фамилия и инициалы, Git, Контакт - unless parts.size == 3 - raise ArgumentError, "Строка info_string должна содержать три части разделённые запятой: '#{info_string}'" - end - + parts = info_string.split(',').map(&:strip) + raise ArgumentError, 'Invalid info string format' if parts.size < 3 + surname_initials = parts[0] - raise ArgumentError, "Не удалось извлечь фамилию и инициалы" if surname_initials.nil? || surname_initials.empty? - - git_part = parts[1] - raise ArgumentError, "Не удалось извлечь Git-данные" if git_part.nil? || !git_part.include?(': ') - git = git_part.split(': ').last - raise ArgumentError, "Некорректная строка для Git: '#{git_part}'" if git.nil? || git.empty? - - contact_part = parts[2] - raise ArgumentError, "Не удалось извлечь контактные данные" if contact_part.nil? || !contact_part.include?(': ') - contact = contact_part.split(': ').last - raise ArgumentError, "Некорректная строка для контакта: '#{contact_part}'" if contact.nil? || contact.empty? - - new_instance = allocate - new_instance.send(:initialize_from_data, id, surname_initials, git, contact) - new_instance - end - - private - - def initialize_from_data(id, surname_initials, git, contact) - raise ArgumentError, "ID должен быть числом" if id && !id.is_a?(Integer) - @id = id - - @surname_initials = surname_initials - raise ArgumentError, "Поле surname_initials не может быть пустым" if @surname_initials.nil? || @surname_initials.strip.empty? - unless valid_surname_initials?(@surname_initials) - raise ArgumentError, "Некорректный формат фамилии и инициалов: #{@surname_initials}" - end - - @git = git - raise ArgumentError, "Git ссылка не может быть пустой" if @git.nil? || @git.strip.empty? - raise ArgumentError, "Некорректная ссылка на GitHub: #{@git}" unless self.class.valid_git?(@git) - - @contact = contact - raise ArgumentError, "Контактная информация не может быть пустой" if @contact.nil? || @contact.strip.empty? - - # Инициализация родительского класса для валидации полей - # Если необходимо, можно повторно вызвать super или же доверять валидации из Person при отдельном создании - # В данном случае super уже вызывается в инициализаторе, а тут мы лишь готовим данные. + git = parts[1].split(': ').last.strip + contact_string = parts[2].split(': ', 2).last.strip + + contact = Contact.new_from_info(contact_string) + + new( + id: id, + git: git, + surname_initials: surname_initials, + contact: contact.info + ) end - def valid_surname_initials?(string) - # Пример простой проверки: фамилия и инициалы могут содержать буквы и точки. - # Формат: "Иванов И.И." или "Petrov P.P." - # Условно: первая часть - фамилия, затем инициалы через пробел. - # Можно настроить по необходимости. - string.match?(/\A[А-Яа-яЁёA-Za-z]+(\s+[А-Яа-яЁёA-Za-z]\.[А-Яа-яЁёA-Za-z]\.)?\z/) + def to_s + "#{@surname_initials}, Git: #{@git}, Contact: #{@contact}" end 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