Трета задача

Предадени решения

Краен срок:
10.11.2014 17:00
Точки:
6

Срокът за предаване на решения е отминал

Ruby файлова система

Файлова система наричаме дървовидна структура, състояща се от директории и файлове. Вашата задача е да симулирате проста файлова система, чрез Ruby класове.

Ще я наречем RBFS (Ruby File System).

Когато създаваме библиотеки е добра идея всички класове да се намират в модул. Така се постига по-добро "групиране" на класовете, според тяхната функционалност. Освен това, тъй като ще създаваме класове с имена, които вече съществуват в стандартната библиотека на Ruby, групирането им в модул е наложително. В противен случай, вместо да създадем нови класове, ще модифицираме съществуващите, което не е нашата цел.

Всеки клас, който ще създадете, трябва да се намира в модул с име RBFS!

Файлове

Файловете са структурите, които съхраняват данните в една файлова система. Всеки един от файловете в RBFS може да съхранява един от следните типове информация:

  • текст (string)
  • Ruby символ
  • число (цяло или с плаваща запетая)
  • булева стойност (true или false)
  • nil

Създайте клас RBFS::File със следните методи:

  • Конструктор - приема обекта, който файлът ще съхранява (един от горните типове). Ако не се подадат данни да се съхрани nil.
  • Поле за четене data_type, връщащо типа на обекта, който се съхранява - eдин от следните символи:
    • :nil
    • :string
    • :symbol
    • :number
    • :boolean
  • Поле за четене/писане - data, връщащо/променящо данните на файла. Допустимо е да се смени и типът на файла чрез подмяна на съхранявания обект (file.data = 'other type of data').
  • Метод serialize - връщащ низ - текстовото представяне на файла. Как точно трябва да става преобразуването ще обясним в секцията Сериализация.
  • Класов метод parse(string_data), приемащ текстово представяне на файл (отново, goto Сериализация) и връщащ Ruby обект от класа File.

Директории

Директориите могат да съхраняват други директории и файлове. Можете да считате една директория за структура, подобна на хеш (речник), която съпоставя име на друга директория или файл.

Създайте клас RBFS::Directory със следните методи:

  • add_file(name, file) - Добавя файл с име name. file е обект от класа RBFS::File, но не е нужно да проверявате дали наистина подаденият обект е от този клас. Името на файл не може да съдържа :. Причината за това ограничение ще разберете от секцията Сериализация.
  • add_directory(name, directory) - Добавя директория с име name. directory (обект от класа RBFS::Directory) може да се изпусне, като, в такъв случай, се създава празна директория. Името на директория не може да съдържа :.
  • [](name) - Файловете и поддиректориите в конкретна директория трябва да могат да се достъпват чрез оператора []. Например, directory['home'] трябва да връща поддиректорията с име home, намираща се в directory. По този начин ще бъде възможно класът да се използва по следния начин - directory['home']['user']['homework.rb'] #=> обект от класа File. Ако не съществува такъв файл или директория, да се върне nil. Ако съществуват директория и файл с едно и също име, резултатът трябва да е директорията.
  • serialize - връща String - текстовото представяне на директорията и всички нейни файлове и поддиректории.
  • Класов метод parse(string_data) - приема низ във вид, в какъвто е резултатът от serialize, и връща обект от класа Directory.
  • Поле за четене files, връщащо хеш с ключове - имената на файловете в директорията, и стойности - съответните им обекти от тип RBFS::File. Ако няма файлове да се върне празен хеш.
  • Поле за четене directories, връщащо хеш от имената и обектите на поддиректориите. Отново, ако няма такива - да се върне празен хеш.

Сериализация

Една файлова система не е особено полезна, ако всеки път, когато си спрете компютъра (в нашия случай - програмата), файловете ви изчезваха. Затова, ще трябва да създадете механизъм за съхранение на йерархия от файлове и директории, като ги преобразувате в низове.

Сериализацията (според Wikipedia) е процесът на преработване на структура от данни във формат, подходящ за съхранение. От този формат, след време, може да бъде възстановена оригиналната структура.

Съхранението се състои в това една директория, заедно с всички нейни поддиректории и файлове, да бъде "превърната" в низ. Този низ, в последствие, може да бъде записан във файл. След време този файл може да бъде зареден, и низът в него да бъде "превърнат" отново в същата йерархия от директории и файлове.

За да е възможно различаването на видовете информация в този низ, той трябва да е в предварително уточнен формат, чрез който можем да разграничаваме различните компоненти (например имената на файловете от техните данни).

За да ви улесним (или объркаме), сме измислили формат, в който трябва да стане това. Тук може би трябва да споменем, че, в 'реалния живот'™, не е добра идея да си измисляте собствени формати за подобни неща.

Сериализация на файлове

Методът serialize на класа File трябва да връща string, в следния формат:

<data_type>:<data>

Тук <data_type> трябва да се замени с типа на информацията, която се съхранява във файла (nil, string, symbol, number или boolean). <data> е текстовото представяне на самата информация (това, което връща to_s върху обекта, който съхраняваме).

Примери:

nil:
string:Hay :)
symbol:yo
number:42.666
boolean:true

Задачата на класовия метод parse е да дава обратната функционалност - при подаден такъв низ, да се създаде обект с тези данни.

Сериализация на директории

Това е малко по-сложната част, но ние вярваме в способностите ви. :)

Най-простият начин да съхраняваме структурирана информация е да използваме специални символи, с които да разграничаваме данните (частите от текста). Това обаче ни ограничава, защото не можем да използваме тези символи в данните. В противен случай ще объркаме програмата, която ги чете, защото тя няма да знае дали те са разграничителите, или са част от информацията. Разбира се, има начини да се приложи този подход (чрез екраниране на специалните символи), но за "обучителни" цели, ще използваме комбинация от този и друг подход.

Преди данните, които са с променлива дължина и предварително неизвестно съдържание, ще слагаме число, отговарящо на тяхната дължина. Това обозначение ще ни позволи да разберем точно коя част от низа са такива данни и, съответно, къде свършват. Тази техника най-често се използва в двоични формати.

Понеже нещата по-добре се разбират в примери, ще покажем един такъв и ще му направим "дисекция":

2:README:19:string:Hello world!spec.rb:20:string:describe RBFS1:rbfs:4:0:0:

Това е текстовият вид на директория с два файла и една поддиректория. На пръв поглед изглежда плашещо и сложно, но не е. Отделете няколко минути да разгледате хубаво този низ и ще започнете да забелязвате определени смислови елементи в него.

Нека започнем по обратния път, като заместваме части от примера с placeholder-и. Забелязвате ли къде се намират файловете?

2:README:19:<file1>spec.rb:20:<file2>1:rbfs:4:0:0:

<file1> и <file2> са нещата, които връща метода serialize на File.

Забелязвате ли числата 19 и 20 преди данните за файловете? Това са просто дължините съответно на низовете string:Hello world! и string:describe RBFS. Те са необходими, защото данните на файла могат да са с променлива дължина, и в противен случай няма да знаем къде свършва единия файл и къде започва другия. От друга страна файловете могат да съдържат :, затова не можем просто да използваме символа : за обозначение на край на съдържанието на файл.

2:README:<file1_size>:<file1>spec.rb:<file2_size>:<file2>1:rbfs:4:0:0:

Сигурно вече сте забелязали къде са имената на файловете.

2:<file1_name>:<file1_size>:<file1><file2_name>:<file2_size>:<file2>1:rbfs:4:0:0:

Въпреки че имената са също с променлива дължина, пред тях не слагаме дължината им, защото сме забранили те да съдържат символа :, чрез който ги разделяме от другите части на низа.

Дължината <file1_size> е равна точно на дължината (в брой символи) на текста, който в примера е заместен с <file1>.

Нека групираме частите, описващи файловете, за да се съсредоточим върху останалото:

2:<file1_data><file2_data>1:rbfs:4:0:0:

Може би вече сте се досетили какво означава числото 2 в началото. Това е броят на файловете в директорията. Те са изредени един след друг, без разделител между тях. Тъй като в текста на всеки файл се съдържа дължината му, ние знаем точно коя част от низа описва единия файл и, съответно, къде започва другият.

<file_count>:<files>1:rbfs:4:0:0:

Вече разбрахме какво означава първата половина от този "грозен" текст. Другата половина е много подобна. Тя представя поддиректориите на текущата директория.

<file_count>:<files><dir_count>:rbfs:4:0:0:

В конкретния случай имаме само една поддиректория. Името ѝ е rbfs, а данните в нея са с дължина 4 символа.

<file_count>:<files><dir_count>:<dir1_name>:<dir1_size>:<dir1>

0:0: е текстовото представяне на празна директория. Ако не ви е ясно защо - съпоставете двата реда:

0:0:
<file_count>:<files><dir_count>:<dirs>

Броят на файловете и поддиректориите е 0, затова и след двоеточията няма изредени такива.

Чрез този формат може да се влагат директории неограничено една в друга. Ето друг пример с две вложени директории:

0:1:directory1:40:0:1:directory2:22:1:README:9:number:420:

Горният примерен низ е еквивалентен на следната структура:

.
`-- directory1
    `-- directory2
        `-- README

Файлът README съдържа числото 42.

Отново, нека раздробим и този пример, стъпка по стъпка:

0:1:directory1:40:0:1:directory2:22:<dir2_file_count>:<dir3_files>0:
0:1:directory1:40:0:1:directory2:22:<dir2_data>
0:1:directory1:40:0:1:<dir2_name>:<dir2_data_size>:<dir2_data>
0:1:directory1:40:0:1:<dir2>
0:1:directory1:40:<dir1_file_count>:<dir1_dir_count>:<dir2>
0:1:directory1:40:<dir1_data>
0:1:<dir1>
<dir0_file_count>:<dir0_dir_count>:<dir1>
<dir0>

Методът serialize на класа Directory, трябва да генерира стринг в гореописания формат.

Ваша задача, също така, е класовия метод parse да може да извършва обратната операция - при подаден низ в този формат, да го преобразува в обект със същите данни (съответните файлове, поддиректории, техните поддиректории и файлове и т.н.).

Отново, serialize трябва да връща string. Не очакваме да записвате нищо във файлове.

Помощ?

Ако имате въпроси по която и да е част на условието, не се колебайте да ги зададете във форума на курса! Ако нямате идея как да реализирате някой метод, например parse, също питайте. Ще се опитаме да ви помогнем доколкото е възможно, без да ви пишем кода. :)

Подсказка - рекурсия.

Освен това, добра идея е първо да решите задачата, без да се притеснявате от skeptic ограниченията, след което да отделите и промените по-сложните части от кода. Може да е полезно да си създадете и други, помощни, класове (освен File и Directory).

Think with portals objects!

Примерни тестове

Ако не си пускате примерните тестове, е 99.9% сигурно, че нещо ще се обърка. Лесно е да пропуснете някое двоеточие, или да добавите излишно. Проверявайте си задачата с нашите тестове!

Освен това е много, много полезно да си пишете и ваши такива в процеса на решаване на задачата. :)

Както обикновено, примерните тестове се намират в хранилището ни в GitHub!

Ограничения

Тази задача има следните ограничения:

  • Най-много 90 символа на ред
  • Най-много 8 реда на метод
  • Най-много 1 нива на влагане

Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.

Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop редовно, докато пишете кода. Ако смятате, че rubocop греши по някакъв начин, пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като private gist. Ако пуснете кода си публично (например във форумите), ще смятаме това за преписване.