Седма задача
- Краен срок:
- 21.12.2015 17:30
- Точки:
- 6
Срокът за предаване на решения е отминал
Lazy Mode
Org-mode е plugin за emacs (да, все още има хора които го използват и не са пробвали vim ), позволяващ водене на бележки, правене на TODO списъци, планиране на проекти и още редица други странни употреби и всичко това в изцяло текстов формат.
Вашата задача
Задачата ви е да имплементирате своя опростена версия, която ще наричаме Lazy-mode. Тя служи за проследяване на задачи, които искаме да отложим или вече сме отложили. Преди да навлезем в детайли, следва кратко отклонение.
Времето
Няма как да следим изоставането си по дадена задача, без да имаме удобен начин за представяне на времето. За жалост, времето е сложно. Имаме високосни години, месеци с по-малко от 30 дни, различни часови зони и още ужаси, с които да се справяме. Дори Matz мрази времето. За да не се занимаваме с несъвършенствата на времето, ще направим собствен вариант (така и така точността не е толкова важна за нашата система). Ще дефинираме класа LazyMode::Date
, който ще използваме по следния начин:
date = LazyMode::Date.new('2012-10-03')
date.year # => 2012
date.month # => 10
date.day # => 3
LazyMode::Date
се инициализира от стринг със следния формат: 'yyyy-mm-dd'
. Това ще рече, че имаме годината, месеца и деня от месеца, разделени с тире. В случай, че:
- годината има по-малко от 4 цифри, тя се префиксва с нули (например
0987
) - месецът има по-малко от 2 цифри, той се префиксва с нули (например
09
) - денят от месеца има по-малко от 2 цифри, той се префиксва с нули (например
07
)
LazyMode::Date
трябва да има четири метода:
-
#year
- връща годината като число -
#month
- връща месеца като число -
#day
- връща деня от месеца като число -
#to_s
- връща текстовото представяне на датата като низ
От тук нататък, когато искаме нещо да работи с време, ще работим само с този обект. За удобство ще приемем, че в годината има 12 месеца и 30 дни във всеки месец.
Note
Основното нещо, с което ще работим в lazy-mode, е note, както е и в org-mode. Ще дефинираме класа LazyMode::Note
по следния начин:
-
#header
- връща низ - заглавие на бележката ни -
#file_name
- връща низ - име на файла, в който пазим бележката -
#body
- връща низ - текстово съдържание на нашата бележка -
#status
- връща един от следните два символа -:topostpone
,:postponed
-
#tags
- връща масив с всички тагове за бележката, а в случай че няма такива - връща празен масив
File
Създаването на файл ще правим посредством наш специален DSL, който изглежда по следния начин:
file = LazyMode.create_file('work') do
note 'sleep', :important, :wip do
scheduled '2012-08-07'
status :postponed
body 'Try sleeping more at work'
end
note 'useless activity' do
scheduled '2012-08-07'
end
end
file.name # => 'work'
file.notes.size # => 2
file.notes.first.file_name # => 'work'
file.notes.first.header # => 'sleep'
file.notes.first.tags # => [:important, :wip]
file.notes.first.status # => :postponed
file.notes.first.body # => 'Try sleeping more at work'
file.notes.last.file_name # => 'work'
file.notes.last.header # => 'useless activity'
file.notes.last.tags # => []
file.notes.last.status # => :topostpone
create_file
приема името на файла като аргумент и блок (няма да се извиква без блок). В блока трябва да имаме достъп до метода #note
, който създава нова бележка. Резултатът от #create_file
е инстанция на LazyMode::File
, който има дефинирани методите #name
- името на файла и #notes
- списък с бележките, които трябва да са инстанции на клас LazyMode::Note
. Списъкът може да е инстанция на класа Array
, или еквивалент. Редът на бележките, върнати от #notes
в списъка`, e без значение.
#note
Методът #note
, достъпен в блока на #create_file
, създава нова бележка с #header
, подаден като първи аргумент и приема след това произволен брой (нула или повече) тагове. Редът на таговете се пази както е подаден. Самият метод приема блок, в който трябва да имаме достъп до следните методи:
-
#status
- приема един от двата символа:postponed
или:topostpone
. Няма да се подават невалидни стойности. Ако#status
не бъде извикан, се приема, че стойността му е:topostpone
. -
#body
- приема произволен текст, който отговаря на съдържанието на бележката. Стойноста по подразбиране на#body
е празен низ. -
#scheduled
- текстов низ, съдържащ дата във формата, който е описан вLazyMode::Date
. Наличието му е задължително за създаване на бележки. Приемаме, че винаги имаме подаден такъв, тоест, че в блока, подаден на#note
, винаги ще бъде извикван методът#scheduled
.
#scheduled
file = LazyMode.create_file('hobby') do
note 'swim' do
scheduled '2012-08-07 +2w'
status :postponed
end
end
#scheduled
може да създава бележка, която се повтаря периодично, започвайки от посочената дата. Това става като към датата добавим повторител във формата +{n}{m,d,w}
, където n
е число и означава всеки n-пъти, m
- всеки месец, d
- всеки ден, w
- всяка седмица. В примера по-горе това означава, че сме насрочили плуване на всеки две седмици, започвайки от 2012-08-07
.
Влагане на бележки
Можем да влагаме бележките една в друга. Например:
file = LazyMode.create_file('nesting') do
note 'task' do
scheduled '2012-08-07'
note 'subtask' do
scheduled '2012-08-06'
end
end
end
Влагането става като дефинирането на нови бележки. За да дефинираме влагане, просто извикваме отново #note
в блока на бележката, за която искаме да направим подбележка.
Agenda
LazyMode::File
трябва да дефинира и метода #daily_agenda
. #daily_agenda
приема като аргумент дата (като инстанция на LazyMode::Date
) и ни връща обект, който има дефиниран метод #notes
. Методът #notes
трябва да ни върне спъсък от всички бележки, които са schedule
-нати за date
. Редът на бележките в списъка е без значение. Пример:
file = LazyMode.create_file('nesting') do
note 'task' do
scheduled '2012-08-07'
note 'subtask' do
scheduled '2012-08-06 +1w'
end
end
end
daily_agenda = file.daily_agenda(LazyMode::Date.new('2012-08-13'))
daily_agenda.notes.size # => 1
daily_agenda.notes.first.header # => 'subtask'
daily_agenda.notes.first.date.to_s # => '2012-08-13'
Всяка бележка от notes
трябва да има всички методи на LazyMode::Note
и допълнителен метод #date
- датата, за която е насрочено събитието. В случая на daily_agenda
, това винаги ще е датата, подадена като аргумент на #daily_agenda
.
Аналогично дефинираме и метода LazyMode::File#weekly_agenda(date)
. Трябва да върнем обект, който имплементира метод #notes
, връщащ всички бележки за следващите седем дни, считано от date
. Редът на бележките в списъка отново е без значение. Пример:
file = LazyMode.create_file('week') do
note 'task' do
scheduled '2012-08-07'
note 'subtask' do
scheduled '2012-08-06'
end
note 'subtask 2' do
scheduled '2012-08-05'
end
end
end
weekly_agenda = file.weekly_agenda(LazyMode::Date.new('2012-08-06'))
weekly_agenda.notes.size # => 2
weekly_agenda.notes.first.header # => 'task'
weekly_agenda.notes.first.date.to_s # => '2012-08-07'
weekly_agenda.notes.last.header # => 'subtask'
weekly_agenda.notes.last.date.to_s # => '2012-08-06'
#where
Обектите, връщани от #daily_agenda
и #weekly_agenda
трябва да имплементират и метода #where
. #where
позволява филтриране по status
, tag
и text
. #where
приема следните keyword аргументи - :tag
, :text
или :status
и връща нов обект, поддържащ същите методи като обектите, върнати от #daily_agenda
и #weekly_agenda
. Редът на бележките в резултата от филтрацията е без значение. Пример:
file = LazyMode.create_file('week with tags') do
note 'task', :important do
scheduled '2012-08-07'
note 'subtask', do
scheduled '2012-08-06'
end
note 'subtask 2', :important do
scheduled '2012-08-05'
end
end
end
weekly_agenda = file.weekly_agenda(LazyMode::Date.new('2012-08-05'))
important_tasks = weekly_agenda.where(tag: :important)
important_tasks.notes.size # => 2
important_tasks.notes.first.header # => 'task'
important_tasks.notes.last.header # => 'subtask 2'
important_subtasks = weekly_agenda.where(tag: :important, text: /sub/)
important_subtasks.notes.size # => 1
important_subtasks.notes.first.header # => 'subtask 2'
-
:tag
- приема произволен таг, по който се филтрира. Бележките, които се връщат, трябва да имат този таг. Забележка: таговете не се наследяват, т.е. влагането няма значение. Гледат се само таговете, подадени директно при създаване на бележката, а таговете на нейните родители - не. -
:text
- приема регулярен израз и филтрира бележките спрямо това дали регулярният израз "хваща"#header
или#body
на бележката. -
:status
- приема един от възможните статуси (:postponed
или:topostpone
) и филтрира бележките спрямо това дали#status
-ът им съвпада с подадения на филтъра.
От примера виждаме, че когато се подадат повече от един от филтрите, трябва всичките да са изпълнени за дадена бележка.
Примерен тест
Примерните тестове се намират в GitHub хранилището с домашните. За информация как да ги изпълните, погледнете README-то на хранилището.
Ограничения
Тази задача има следните ограничения:
- Най-много 80 символа на ред
- Най-много 6 реда на метод
- Най-много 1 нива на влагане
- Най-много 4 аргумента на метод
Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.
Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop
редовно, докато пишете кода. Ако смятате, че rubocop
греши по някакъв начин,
пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като
private gist. Ако пуснете кода си публично (например във форумите), ще смятаме
това за преписване.