Четвърта задача

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

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

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

Command Line Toolkit

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

Задачата ви този път е да имплементирате framework за изграждане на текстов интерфейс, който би могъл да работи в конзола. Тъй като това е сложна задача, ще се ограничим само до най-базовата функционалност. Това, което трябва да реализирате е частта за "подредба" на компонентите на екрана. Ще изтестваме тази функционалност, създавайки примерен компонент - текстови етикет.

TextScreen

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

UI::TextScreen.draw do
  # ...
end

На мястото на коментара задаваме интерфейса на приложението, използвайки методите, описани в следващите секции. Резултатът от TextScreen.draw е String, съдържащ текстовото представяне на дефинираните компоненти.

label

Текстовият етикет е компонентът, с който ще тестваме подредбата на нашия UI. Пример:

screen = UI::TextScreen.draw do
  label text: 'Something'
end
puts screen

Горното парче код принтира Something на стандартния изход.

Подредба на компонентите

По подразбиране TextScreen подрежда компонентите си хоризонтално.

screen = UI::TextScreen.draw do
  label text: 'Something'
  label text: 'else'
end
puts screen

Резултатът от изпълнение на кода горе е принтиране на Somethingelse на стандартния изход.

Можем да променяме това поведение на TextScreen използвайки групи.

Вертикална група

Вертикална група създаваме с метода vertical. Всички компоненти, дефинирани в групата, се подреждат вертикално, един под друг. Пример:

screen = UI::TextScreen.draw do
  vertical do
    label text: '1'
    label text: '2'
    label text: '3'
  end
end

Резултатът от screen.to_s e 1\n2\n3\n, което принтирано на стандартния изход визуализира етикетите един под друг.

Хоризонтална група

Хоризонтална група създаваме с метода horizontal. Всички компоненти, дефинирани в групата, се подреждат хоризонтално, един след друг. Пример:

screen = UI::TextScreen.draw do
  horizontal do
    label text: '1'
    label text: '2'
    label text: '3'
  end
end

Резултатът от screen.to_s e 123, което принтирано на стандартния изход визуализира етикетите един до друг.

Комбиниране на групи

Можем да комбинираме групи, за да създаваме по-сложни изгледи. Кодът по-долу описва табличен интерфейс:

screen = UI::TextScreen.draw do
  vertical do
    horizontal do
      label text: '1'
      label text: '2'
      label text: '3'
    end
    horizontal do
      label text: '4'
      label text: '5'
      label text: '6'
    end
    horizontal do
      label text: '7'
      label text: '8'
      label text: '9'
    end
  end
end

Резултатът след принтиране на screen e:

123
456
789

Стилизиране на компоненти

Да организираме компоненти по екрана е важно, но не достатъчно. Искаме възможност за стилизиране на компонентите, преди те да бъдат нарисувани. Въвеждаме следните две опции за избиране на стил на компонент:

Рамка

Методът label приема няколко незадължителни именувани аргумента (keyword arguments). Един от тях е border. По подразбиране стойността на border за всеки компонент е nil, което символизира липсата на рамка. Можем да зададем произволен низ, който да бъде използван за рамка на компонента. Рамката чертаем в ляво и дясно от съдържанието на компонента.

screen = UI::TextScreen.draw do
  label text: 'bordered', border: '|'
end

Резултатът в screen е низът "|bordered|".

Рамки можем да слагаме и на групи от компоненти:

screen = UI::TextScreen.draw do
  horizontal border: '#' do
    label text: '1'
    label text: '2'
    label text: '3'
  end
end

Резултатът в screen е низът "#123#".

При вертикални групи е важно да съобразим рамката с най-широкия елемент, за да не я начертаем накриво :)

screen = UI::TextScreen.draw do
  vertical border: '|' do
    label text: 'ha'
    label text: 'haha'
    label text: 'hahahaha'
  end
end

Резултатът след принтиране на screen е:

|ha      |
|haha    |
|hahahaha|

Стилове

Освен възможността за поставяне на рамка около компонентите, добавяме и изискването за избиране на стил. Това става чрез задаване на стойност на параметъра style, като валидни стойности са :upcase, :downcase и nil (стойността по подразбиране). :upcase и :downcase променят визуализацията на компонент, превръщайки буквите му съответно в главни и малки. nil символизира липса на указан стил, при което компонентът се "рендерира" без някаква промяна.

screen = UI::TextScreen.draw do
  horizontal style: :upcase do
    label text: 'ha'
    label text: 'HA', style: :downcase
    label text: 'ha'
  end
end

Резултатът в screen е низът HAhaHA.

Дизайн на кода

Както и в предната задача, ще спазим добрата практика целият код на нашата малка библиотечка да се намира в модул, с цел по-добра капсулация. Модулът е UI. Извън този модул не трябва да имате нищо друго.

От друга страна, имате пълна свобода да слагате в UI колкото искате други класове и модули и да съставите такава архитектура, каквато ви харесва, стига да спазите описания по-горе публичен интерфейс.

DSL

Методите, използвани за описание на екраните, са тясно свързани с проблемната област. Имената им и начинът, по който ги ползваме, моделират тази проблемна област. Тази концепция се нарича domain-specific language. Съкращава се като DSL. Това е задачата ви – да имплементирате един DSL.

Тези методи трябва да са налични само в блока, подаван на draw. Извън него те не трябва да работят. Това не трябва да са глобални методи. Не трябва да ги има в Object. Ще има тестове, които ще проверяват това. Ако видим такива методи в Object, ще ви възнаградим и с наказателни точки.

Бележка

Ще тестваме само с латиница и специални символи от сорта на |~!@#$%^&*. Други азбуки към момента не ни вълнуват.

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

Примерните тестове се намират в хранилището ни в GitHub!

Ограничения

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

  • Най-много 80 символа на ред
  • Най-много 6 реда на метод
  • Най-много 1 нива на влагане
  • Най-много 3 аргумента на метод

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

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