students_list_yaml_ligostaev 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 13e5773e64d50df3e40fb1631027bed51c6874af405f4f51de6b3c99f67064f9
4
+ data.tar.gz: ef201b10620bbf2eb1e9538b76059f6e30c99cb196c8e6199ccb3db8fd295740
5
+ SHA512:
6
+ metadata.gz: 91e6ab9aaa6e7c49848ed7af32c87354efd91439b981c9bb2dd684698da7981c419d80d3f11db56af2ba617b717c070f528cc019a7a758fe865097428b113bb1
7
+ data.tar.gz: 1dc0e16f13553b1645eb318cb48326e2497898f906db72d68171c5a32ed3e846be6174985d8b09eeb04f0690bc8af3d2f87fc6f9ed2f4d4192dde7f9317cfb8c
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Laboratory Work 4
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # students_list_yaml
2
+
3
+ Простой гем для работы со списками студентов в YAML формате.
4
+
5
+ ## Установка
6
+
7
+ ```ruby
8
+ gem 'students_list_yaml', path: './students_list_yaml_gem'
9
+ ```
10
+
11
+ Или в Gemfile проекта:
12
+ ```ruby
13
+ gemspec path: './students_list_yaml_gem'
14
+ ```
15
+
16
+ ## Использование
17
+
18
+ ```ruby
19
+ require 'students_list_yaml'
20
+
21
+ # Создать объект для работы с YAML файлом
22
+ list = Students_list_YAML.new('students.yaml')
23
+
24
+ # Получить количество студентов
25
+ count = list.get_student_short_count
26
+
27
+ # Получить студента по ID
28
+ student = list.get_student_by_id(1)
29
+
30
+ # Добавить студента
31
+ new_student = Student.new(...)
32
+ list.add_student(new_student)
33
+
34
+ # Получить страницу (5 студентов, 1-я страница)
35
+ page = list.get_k_n_student_short_list(5, 1)
36
+
37
+ # Сортировать по фамилии и инициалам
38
+ list.sort_by_last_name_initials
39
+ ```
40
+
41
+ ## Методы
42
+
43
+ - `initialize(file_path)` - создать объект
44
+ - `load_file()` - загрузить студентов из файла
45
+ - `save_file()` - сохранить в файл
46
+ - `get_student_by_id(id)` - получить студента по ID
47
+ - `add_student(student)` - добавить студента
48
+ - `replace_student_by_id(id, new_student)` - заменить студента
49
+ - `remove_student_by_id(id)` - удалить студента
50
+ - `sort_by_last_name_initials()` - сортировать
51
+ - `get_student_short_count()` - количество студентов
52
+ - `get_k_n_student_short_list(k, n)` - получить k студентов со страницы n
53
+
54
+ ## Зависимости
55
+
56
+ Требует наличие классов:
57
+ - Student
58
+ - StudentShort
59
+ - DataList
60
+ - DataListStudentShort
61
+ - AbstractStudentList
62
+
63
+ Эти классы должны быть загружены перед использованием гема.
@@ -0,0 +1,227 @@
1
+ require_relative 'student'
2
+ require_relative 'student_short'
3
+ require_relative 'data_list_student_short'
4
+
5
+ # AbstractStudentList — базовый класс для конкретных реализаций хранения списка студентов (JSON, YAML).
6
+ #
7
+ # Содержит общую логику работы со списком студентов:
8
+ # - преобразование хэша в объект Student и наоборот,
9
+ # - добавление/удаление/замена записи по id,
10
+ # - получение страницы StudentShort в виде DataListStudentShort,
11
+ # - сортировка по фамилии + инициалам (last_name_initials),
12
+ # - универсальный read_all/write_all (вызывают реализацию подкласса для записи/чтения массивов).
13
+ #
14
+ # @abstract
15
+ # @example Создание подкласса
16
+ # class StudentListJSON < AbstractStudentList
17
+ # private
18
+ # def load_from_file(content)
19
+ # # реализация для JSON
20
+ # end
21
+ #
22
+ # def write_raw_array(array)
23
+ # # реализация для JSON
24
+ # end
25
+ # end
26
+ #
27
+ # @see StudentListJSON
28
+ # @see StudentListYAML
29
+ class AbstractStudentList
30
+ # Путь к файлу JSON/YAML
31
+ # @return [String]
32
+ attr_reader :path
33
+
34
+ # Открывает и при необходимости создает файл
35
+ #
36
+ # @param path [String] путь до файла
37
+ # @example
38
+ # list = StudentListJSON.new('students.json')
39
+ def initialize(path)
40
+ @path = path
41
+ @array_list = []
42
+ ensure_file
43
+ end
44
+
45
+ # Читает все записи с файла
46
+ #
47
+ # @return [Array<Student>] массив объектов Student
48
+ # @example
49
+ # students = list.read_all
50
+ # students.each { |s| puts s.last_name_initials }
51
+ def read_all
52
+ file_content = File.read(@path)
53
+ if file_content.strip.empty?
54
+ @array_list = []
55
+ return @array_list
56
+ end
57
+
58
+ parsed_array = load_from_file(file_content)
59
+ @array_list = parsed_array.map { |element| Student.new(**element) }
60
+ @array_list
61
+ end
62
+
63
+ # Записывает или перезаписывает всех студентов в файл
64
+ #
65
+ # @return [void]
66
+ # @example
67
+ # students = list.read_all
68
+ # list.write_all
69
+ def write_all
70
+ new_data = @array_list.map(&:to_hash)
71
+ write_raw_array(new_data)
72
+ end
73
+
74
+ # Получает объект класса Student по ID, или nil, если не найден
75
+ #
76
+ # @param id [Integer] идентификатор студента
77
+ # @return [Student, nil] объект Student или nil если не найден
78
+ # @example
79
+ # student = list.get_student_by_id(1)
80
+ # puts student.last_name if student
81
+ def get_student_by_id(id)
82
+ @array_list.find { |element| element.id == id }
83
+ end
84
+
85
+ # Получает список k по счету n объектов StudentShort в виде DataListStudentShort
86
+ #
87
+ # @param k [Integer] размер страницы
88
+ # @param n [Integer] номер страницы
89
+ # @param existing_data_list [DataListStudentShort, nil] если передан,
90
+ # то метод заполнит или заменит объекты в нем
91
+ # @return [DataListStudentShort] список студентов для отображения
92
+ # @example
93
+ # data_list = list.get_k_n_student_short_list(5, 1)
94
+ # data_list.elements.each { |s| puts s.last_name_initials }
95
+ def get_k_n_student_short_list(k, n, existing_data_list = nil)
96
+ sorted_students = @array_list.sort_by { |s| s.last_name_initials }
97
+ start_index = k * (n - 1)
98
+ selected_students = sorted_students[start_index, k] || []
99
+ student_shorts = selected_students.map { |student| StudentShort.from_student(student) }
100
+
101
+ if existing_data_list
102
+ existing_data_list.elements = student_shorts
103
+ existing_data_list
104
+ else
105
+ new_data_list = DataListStudentShort.new(student_shorts)
106
+ new_data_list
107
+ end
108
+ end
109
+
110
+ # Сортирует элементы по last_name_initials и сохраняет в файл
111
+ #
112
+ # @return [void]
113
+ # @example
114
+ # list.sort_by_last_name_initials
115
+ # # Теперь студенты отсортированы по ФИО
116
+ def sort_by_last_name_initials
117
+ @array_list.sort_by! { |student| student.last_name_initials }
118
+ write_all
119
+ end
120
+
121
+ # Добавляет объект класса Student в список, генерируя новый ID
122
+ #
123
+ # @param student [Student] студент
124
+ # @return [Student] добавленный объект с назначенным ID
125
+ # @raise [ArgumentError] если студент с такими данными уже существует
126
+ # @example
127
+ # student = Student.new(first_name: 'Иван', last_name: 'Петров', git: 'https://github.com/ivanov')
128
+ # added = list.add_student(student)
129
+ def add_student(student)
130
+ if duplicate?(student)
131
+ raise ArgumentError, "Студент с такими контактными данными уже существует"
132
+ end
133
+ existing_ids = @array_list.map(&:id).compact
134
+ new_id = existing_ids.empty? ? 1 : existing_ids.max + 1
135
+ student.instance_variable_set(:@id, new_id)
136
+ @array_list << student
137
+ write_all
138
+ student
139
+ end
140
+
141
+ # Заменяет элемент списка по ID на новый Student
142
+ #
143
+ # @param id [Integer] идентификатор заменяемого студента
144
+ # @param new_student [Student] новые данные студента
145
+ # @return [Boolean] true если заменено, false если id не найден
146
+ # @raise [ArgumentError] если новый студент дублирует существующего
147
+ # @example
148
+ # new_student = Student.new(first_name: 'Обновленное', last_name: 'Имя', git: 'https://github.com/updated')
149
+ # success = list.replace_by_id(1, new_student)
150
+ def replace_by_id(id, new_student)
151
+ index = @array_list.index { |student| student.id == id }
152
+ return false unless index
153
+
154
+ if duplicate?(new_student, exclude: id)
155
+ raise ArgumentError, "Студент с такими контактными данными уже существует"
156
+ end
157
+
158
+ new_student.instance_variable_set(:@id, id)
159
+ @array_list[index] = new_student
160
+ write_all
161
+ true
162
+ end
163
+
164
+ # Удаляет элемент по ID
165
+ #
166
+ # @param id [Integer] идентификатор удаляемого студента
167
+ # @return [Boolean] true если удалено, false если не найден
168
+ # @example
169
+ # if list.delete_by_id(5)
170
+ # puts "Студент с ID 5 удален"
171
+ # end
172
+ def delete_by_id(id)
173
+ deleted = @array_list.reject! { |student| student.id == id }
174
+ end
175
+
176
+ # Возвращает количество элементов
177
+ #
178
+ # @return [Integer] количество студентов в списке
179
+ # @example
180
+ # count = list.get_student_short_count
181
+ # puts "Всего студентов: #{count}"
182
+ def get_student_short_count
183
+ @array_list.length
184
+ end
185
+
186
+ private
187
+
188
+ # Загружает данные из файла
189
+ #
190
+ # @abstract
191
+ # @param content [String] содержимое файла
192
+ # @return [Array<Hash>] массив данных
193
+ # @raise [NotImplementedError] если метод не реализован в подклассе
194
+ def load_from_file(content)
195
+ raise NotImplementedError, "load_from_file должен быть реализован в подклассе"
196
+ end
197
+
198
+ # Записывает массив в файл
199
+ #
200
+ # @abstract
201
+ # @param array [Array<Hash>] массив данных для записи
202
+ # @return [void]
203
+ # @raise [NotImplementedError] если метод не реализован в подклассе
204
+ def write_raw_array(array)
205
+ raise NotImplementedError, "write_raw_array должен быть реализован в подклассе"
206
+ end
207
+
208
+ # Проверяет существует ли дубликат студента
209
+ #
210
+ # @param student [Student] объект класса Student для проверки
211
+ # @param exclude [Integer, nil] ID студента исключаемого из проверки (при замене)
212
+ # @return [Boolean] true если найден дубликат, false если нет
213
+ def duplicate?(student, exclude: nil)
214
+ @array_list.any? do |other|
215
+ next if exclude && other.id == exclude
216
+ student == other
217
+ end
218
+ end
219
+
220
+ # Проверяет существует ли файл, в противном случае создает его с пустым массивом
221
+ #
222
+ # @return [void]
223
+ def ensure_file
224
+ return if File.exist?(@path) && File.size?(@path)
225
+ File.write(@path, '[]')
226
+ end
227
+ end
data/lib/data_list.rb ADDED
@@ -0,0 +1,46 @@
1
+ require_relative 'data_table'
2
+ class DataList
3
+ def initialize(elements = [])
4
+ @elements = elements
5
+ @selected = []
6
+ end
7
+
8
+ attr_reader :elements
9
+
10
+ def elements=(new_elements)
11
+ @elements = new_elements
12
+ @selected.clear
13
+ end
14
+
15
+ def select(number)
16
+ if number >= 0 && number < @elements.size
17
+ @selected << number unless @selected.include?(number)
18
+ end
19
+ end
20
+
21
+ def get_selected
22
+ @selected.map { |index| @elements[index].id }
23
+ end
24
+
25
+ def clear_selected
26
+ @selected.clear
27
+ end
28
+
29
+ def get_names
30
+ ["№ по порядку"] + attribute_headers
31
+ end
32
+
33
+ def get_data
34
+ DataTable.new(student_arr)
35
+ end
36
+
37
+ private
38
+
39
+ def attribute_headers
40
+ raise NotImplementedError, "метод attribute_headers должен быть реализован в подклассе"
41
+ end
42
+
43
+ def student_arr
44
+ raise NotImplementedError, "метод student_arr должен быть реализован в подклассе"
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'data_list'
2
+ require_relative 'student_short'
3
+ require_relative 'data_table'
4
+
5
+ class DataListStudentShort < DataList
6
+ private
7
+ def attribute_headers
8
+ ["ФИО", "Контакт", "Git"]
9
+ end
10
+
11
+ def student_arr
12
+ @elements.each_with_index.map do |student, index|
13
+ [
14
+ index + 1,
15
+ student.last_name_initials,
16
+ student.contact || "",
17
+ student.git || ""
18
+ ]
19
+ end
20
+ end
21
+ end
data/lib/data_table.rb ADDED
@@ -0,0 +1,21 @@
1
+ class DataTable
2
+ def initialize(two_dimensional_array)
3
+ @data = two_dimensional_array
4
+ end
5
+
6
+ def get(row, col)
7
+ raise IndexError, "Индекс строки вне диапазона" if row < 0 || row >= rows_count
8
+ raise IndexError, "Индекс столбца вне диапазона" if col < 0 || col >= cols_count
9
+ @data[row][col]
10
+ end
11
+
12
+ def rows_count
13
+ @data.length
14
+ end
15
+
16
+ def cols_count
17
+ return 0 if @data.empty?
18
+ @data[0].length
19
+ end
20
+ end
21
+
data/lib/student.rb ADDED
@@ -0,0 +1,113 @@
1
+ require_relative 'super_student.rb'
2
+
3
+ class Student < SuperStudent
4
+ include Comparable
5
+ attr_reader :last_name, :first_name, :patronymic, :git
6
+
7
+ def initialize(id: nil, last_name:, first_name:, patronymic: nil, phone: nil, telegram: nil, email: nil, git: nil)
8
+ raise ArgumentError, "Неверный формат гита #{git}" unless self.class.valid_git?(git)
9
+ super(id:id, git: git)
10
+ self.last_name = last_name
11
+ self.first_name = first_name
12
+ self.patronymic = patronymic
13
+ self.contact = {phone: phone, telegram: telegram, email: email}
14
+ end
15
+
16
+ def <=>(other)
17
+ [first_name, last_name, patronymic || nil] <=> [other.first_name, other.last_name, other.patronymic || nil]
18
+ end
19
+
20
+ def contact
21
+ if @telegram && !@telegram.empty?
22
+ "telegram - #{@telegram}"
23
+ elsif @email && !@email.empty?
24
+ "email - #{@email}"
25
+ elsif @phone && !@phone.empty?
26
+ "phone - #{@phone}"
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def contact=(contacts)
33
+ contacts.each do |type, value|
34
+ validator = "valid_#{type}?".to_sym
35
+ raise ArgumentError, "Неверный формат #{type}" unless self.class.send(validator, value)
36
+ instance_variable_set("@#{type}", value)
37
+ end
38
+ end
39
+
40
+ def last_name_initials
41
+ initials = "#{last_name} #{first_name[0]}."
42
+ initials += " #{patronymic[0]}." if patronymic
43
+ initials
44
+ end
45
+
46
+ def self.validated_attr_writer(attribute, validation_method)
47
+ define_method("#{attribute}=") do |value|
48
+ raise ArgumentError unless self.class.send(validation_method, value)
49
+ instance_variable_set("@#{attribute}", value)
50
+ end
51
+ end
52
+
53
+ validated_attr_writer :first_name, :valid_name?
54
+ validated_attr_writer :last_name, :valid_name?
55
+ validated_attr_writer :patronymic, :valid_name?
56
+ validated_attr_writer :git, :valid_git?
57
+
58
+ def to_s
59
+ info = []
60
+ info << "ID: #{@id}" if @id
61
+ info << "Фамилия: #{@last_name}"
62
+ info << "Имя: #{@first_name}"
63
+ info << "Отчество: #{@patronymic}" if @patronymic
64
+ info << "Телефон: #{@phone}" if @phone
65
+ info << "Телеграм: #{@telegram}" if @telegram
66
+ info << "Почта: #{@email}" if @email
67
+ info << "Гит: #{@git}" if @git
68
+ info.join("\n")
69
+ end
70
+
71
+ def self.valid_name?(name)
72
+ name.nil? || name.is_a?(String) && name.match?(/^([А-ЯЁ][а-яё\-]+|[A-Z][a-z\-]+)$/)
73
+ end
74
+
75
+ def self.valid_phone?(phone)
76
+ phone.nil? || (phone.is_a?(String) && phone.match?(/^(\+7|8)?[\s\-\(]?(\d{3})[\s\-\)]?(\d{3})[\s\-]?(\d{2})[\s\-]?(\d{2})$/))
77
+ end
78
+
79
+ def self.valid_telegram?(telegram)
80
+ telegram.nil? || (telegram.is_a?(String) && telegram.match?(/^@[a-zA-Z0-9_]{5,32}$/))
81
+ end
82
+
83
+ def self.valid_email?(email)
84
+ email.nil? || (email.is_a?(String) && email.match?(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/))
85
+ end
86
+
87
+ def self.valid_git?(git)
88
+ git.nil? || (git.is_a?(String) && git.match?(/^https:\/\/(github|gitlab)\.com\/[a-zA-Z0-9_-]+\/?$/))
89
+ end
90
+
91
+
92
+ def to_hash
93
+ hash = {
94
+ id: @id,
95
+ first_name: @first_name,
96
+ last_name: @last_name
97
+ }
98
+ hash[:patronymic] = @patronymic if @patronymic
99
+ hash[:phone] = @phone if @phone && !@phone.empty?
100
+ hash[:telegram] = @telegram if @telegram && !@telegram.empty?
101
+ hash[:email] = @email if @email && !@email.empty?
102
+ hash[:git] = @git if @git
103
+ hash
104
+ end
105
+
106
+ def ==(other)
107
+ return false unless other.is_a?(Student)
108
+ @phone == other.instance_variable_get(:@phone) &&
109
+ @telegram == other.instance_variable_get(:@telegram) &&
110
+ @email == other.instance_variable_get(:@email) &&
111
+ @git == other.git
112
+ end
113
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'student.rb'
2
+ require_relative 'super_student.rb'
3
+
4
+ class StudentShort < SuperStudent
5
+ attr_reader :id, :last_name_initials, :contact, :git
6
+
7
+ def initialize(id:, last_name_initials:, contact: nil, git: nil)
8
+ super(id: id, git: git)
9
+ @last_name_initials = last_name_initials
10
+ @contact = contact
11
+ end
12
+
13
+ def self.from_student(student)
14
+ raise ArgumentError, "Отсутствует ID" unless student.id
15
+ new(
16
+ id: student.id,
17
+ last_name_initials: student.last_name_initials,
18
+ contact: student.contact,
19
+ git: student.git
20
+ )
21
+ end
22
+ def to_s
23
+ short_info
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ require 'yaml'
2
+
3
+ module StudentsListYaml
4
+ VERSION = "1.0.0"
5
+ end
6
+
7
+ require_relative 'super_student'
8
+ require_relative 'student'
9
+ require_relative 'student_short'
10
+ require_relative 'data_table'
11
+ require_relative 'data_list'
12
+ require_relative 'data_list_student_short'
13
+ require_relative 'abstract_student_list'
14
+
15
+ class Students_list_YAML < AbstractStudentList
16
+ def initialize(path)
17
+ super(path)
18
+ end
19
+
20
+ private
21
+
22
+ def load_from_file(content)
23
+ array = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
24
+ raise RuntimeError, "Файл должен содержать YAML массив" unless array.is_a?(Array)
25
+ array
26
+ end
27
+
28
+ def write_raw_array(array)
29
+ File.write(@path, array.to_yaml)
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ class SuperStudent
2
+
3
+ attr_reader :id, :git
4
+
5
+ def initialize(id: nil, git: nil)
6
+ @id = id
7
+ @git = git
8
+ end
9
+
10
+ def short_info
11
+ info = []
12
+ info << "ID: #{id}" if id
13
+ info << "ФИО: #{last_name_initials}"
14
+ info << "Контакт: #{contact}" if contact
15
+ info << "Гит: #{git}" if git
16
+ info.join("\n")
17
+ end
18
+
19
+ def has_git?
20
+ !@git.nil? && !@git.empty?
21
+ end
22
+
23
+ def has_contact?
24
+ !contact.nil? && !contact.empty?
25
+ end
26
+
27
+ protected
28
+
29
+ def last_name_initials
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def contact
34
+ raise NotImplementedError
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: students_list_yaml_ligostaev
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ligostaev
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Provides Students_list_YAML class for managing student data with YAML
13
+ persistence. Includes support for student records with validation, sorting, and
14
+ pagination.
15
+ email:
16
+ - contact@example.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - lib/abstract_student_list.rb
24
+ - lib/data_list.rb
25
+ - lib/data_list_student_short.rb
26
+ - lib/data_table.rb
27
+ - lib/student.rb
28
+ - lib/student_short.rb
29
+ - lib/students_list_yaml.rb
30
+ - lib/super_student.rb
31
+ homepage: https://github.com/ligostaev/students_list_yaml_gem
32
+ licenses:
33
+ - MIT
34
+ metadata: {}
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '2.6'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.7.1
50
+ specification_version: 4
51
+ summary: YAML-based storage for student lists
52
+ test_files: []