#import "lib.typ": *

#set raw(tab-size: 8)
#show raw.where(block: true): code => {
  // set text(11pt, font: "Caladea", top-edge: 1em, bottom-edge: 0em)
  set text(11pt, top-edge: 1em, bottom-edge: 0em)
  set par(leading: 0.17em)

  grid(
    columns: (auto, auto),
    column-gutter: 1em,
    row-gutter: 0.17em,
    align: (right, raw.align),
    ..for line in code.lines {
      (
        text(fill: gray)[#line.number],
        {
          set text(weight: "semibold")
          line.body
        },
      )
    },
  )
}

#show: lab-pz-template.with(
  doctype: "ПЗ",
  title: "Розробка інженерного рішення для друкування ялинки мовою Bash (Bourne again shell) з використанням циклів та галужень",
  subject_shorthand: "СМП",
  department_gen: "Програмної інженерії",
  authors: (
    (
      name: "Ситник Є. С.",
      full_name_gen: "Ситника Єгора Сергійовича",
      group: "ПЗПІ-23-2",
      gender: "m",
      variant: none,
    ),
  ),
  mentor: (
    name: "Сокорчук І. П.",
    gender: "m",
    degree: "ст. викл. каф. ПІ",
  ),
  worknumber: 1,
)

#v(-spacing)

== Мета роботи
Мета даної практичної роботи - здобути навички створення сценаріїв автоматизації засобами Bash (Bourne again shell).


== Хід роботи

Метою роботи є створення сценарію мовою Bash, що друкуватиме на екрані ялинку із двох ярусів гілок, стовбура та шару снігу.

=== До сценарію є наступні вимоги:
+ ялинка повинна бути симетричною;
+ яруси гілок повинні утворювати правильні рівнобедрені трикутники у яких сусідні рядки відрізняються на два символи та складаються почергово або з символів "\*" або з символів "\#";
+ ширина усіх ярусів гілок повинна бути на 2 символи вужча ніж ширина снігу;
+ висота стовбура повинна бути 2 рядки, а ширина 3 стовбці;
+ висота шару снігу повинна бути 1 рядок;
+ висота ялинки у рядках разом з шаром снігу та ширина шару снігу в символах вказується сценарію в його параметрах при запуску;
+ параметри сценарію повинні мати додатнє значення;
+ вказані значення повинні округлятися до потрібних у меншу сторону;
+ якщо за вказаними при запуску сценарія значеннями зобразити ялинку на екрані неможливо, скрипт повинен вивести у потік помилок сповіщення про неправильне значення аргумента і повернути відповідний результат у батьківський процес;
+ у сценарії потрібно обов'язково використати функцію та такі конструкції;
  + `if ... then ... fi`
  + `while ... do ... done`
  + `until ... do ... done`
  + `for ... in .. do ... done`
  + `for ((...)); do ... done`
+ файл сценарію повинен бути виконуваним файлом для усіх користувачів системи;
+ право редагувати файл сценарію повинен мати лише власник.

=== Створимо сценарій дотримуючись вимог

Сценарій створений мовою Bash для більшої зручності використання повинен містити Шебанг (від англійського "shebang") -- послідовність із двох символів: «решітки» та знака оклику (\#!), за якими слідує шлях, до програми інтерпретатору.
Така послідовність використовується виконувачем програм (зазвичай командною оболонкою на кшталт Bash) для ідентифікації та виконання сценарію як програми.
Виконувач програм запустить програму, шлях до якої вказано в першому рядку, передавши в якості аргументу шлях до файлу сценарію.
Почнемо сценарій із Шебангу, в якості шляху до інтерпретатора вкажемо "/bin/bash"

```
#!/bin/bash
```

Перш за все сценарій має виконати валідацію та нормалізацію аргументів. Серед вимог до сценарію можемо виділити такі обмеження аргументів:
+ кількість аргументів має дорівнювати 2;
+ мінімальна висота ялинки складає 8 рядків. А саме -- 1 рядок для шару снігу, 2 рядки для стовбура, 1 рядок для верхівки та 4 рядків для гілок (по 2 на кожен з ярусів);
+ мінімальна ширина шару снігу складає 7 символів -- це на 2 більше, ніж ширина гілок найменшої можливої ялинки.
+ ширина снігу після нормалізації значень аргументів повинна бути більшою за ширину гілок ялинки рівно на 2 символи.

Виконаємо перевірку кількості аргументів за допомогою змінної "\$\#". Висота має передаватись в якості першого аргументу, а ширина -- другого, перевіримо значення цих аргументів за допомогою змінних "\$1" та "\$2". Нормалізуємо отримані значення. Значення висоти повинно бути парним, тому якщо користувач передав непарне значення його необхідно нормалізувати віднявши 1. Значення ширини повинно бути непарним, його так само необхідно зменшити на 1 в іншому випадку. Щоб перевірити чи відповідає ширина шару снігу допустимій за вказаної висоти нам необхідно знати ширину гілок ялинки, її можна обрахувати за формулою "2 \* висота ярусу - 1", дізнатись висоту одного ярусу ми можемо розділивши загальну висоту на 2 та віднявши від результату одиницю, оскільки ширина гілок знадобиться нам під час малювання ялинки збережемо її у змінну.

Якщо під час перевірок було визначено, що аргументи не задовольняють умовам сценарій має завершитись із не нульовим кодом та повідомити про помилки у стандартний потік помилок. Для завершення сценарію використаємо ключове слово "exit", а для друку помилок використаємо команду "echo", перенаправивши її вивід за допомогою оператору перенаправлення.

```
if (( $# != 2 )); then echo "Not enough/Too many arguments" >&2; exit 1; fi
if (( $1 < 8 )); then echo "Height must be > 7" >&2; exit 2; fi
if (( $2 < 7 )); then echo "Width must be > 6" >&2; exit 3; fi

h=$(( ($1 % 2 != 0) ? $1 - 1 : $1 ))
w=$(( ($2 % 2 == 0) ? $2 - 1 : $2 ))
lh=$(( $h/2 - 1 ))

if (( ($w-(2*$lh-1) != 2) )); then echo "Can't draw the tree with provided arguments" >&2; exit 4; fi
```
// #figure(image("img/1.png"), caption: "Валідація та нормалізація аргументів") <1>

Гілки ялинки мають складатись з 2 символів, тому створимо змінну в якій будемо зберігати поточний символ.

Оголосимо функцію для малювання ярусу гілок ялинки. Ця функція буде малювати ярус ялинки рядок за рядком чергуючи символи. Створимо цикл який буде виконуватись стільки разів, скільки рядків необхідно для малювання одного ярусу ялинки, починаючи з 1 або 2 рядка в залежності від ярусу, що малюється. Використаємо раніше оголошену змінну, що зберігає висоту ярусу. Кожен рядок повинен починатись із відступу, для обчислення цього відступу необхідно від ширини шару снігу відняти ширину поточного рядка, та поділити результат на 2. Скористаємося можливістю команди "printf" друкувати текст вміщуючи його в вікно з пробілів визначеної довжини. Рядок із символів ми можемо надрукувати так само використавши "printf", але замінивши символи пробілів, на символи, що відповідають поточному шару. Для цього скористаємося командою "tr". Наприкінці кола циклу змінимо символ на протилежний.

```
ch='*'

draw_layer() {
	for ((i=$1; i <= lh; i++)); do
		printf "%*s" "$(( (w - (2*i-1)) / 2 ))"
		printf "%*s\n" "$(( 2*i-1 ))" | tr ' ' "$ch"

		ch=$( [ "$ch" = '*' ] && echo '#' || echo '*' )
	done
}
```
// #figure(image("img/2.png"), caption: "Функція друку ярусу ялинки") <2>

Стовбур ялинки завжди має однаковий вигляд, відрізняється лише відступ від початку рядку. Оголосимо функцію друку стовбура ялинки, скориставшись методами, що були використані для друку ярусів гілок.

```
draw_stem() {
	for i in {0..1}; do
		printf "%$(( (h - 3) / 2 ))s"
		printf "%3s\n" | tr ' ' "#"
	done
}
```
// #figure(image("img/3.png"), caption: "Функція друку стовбуру ялинки") <3>

Шар снігу не має відступу, надрукувати його найпростіше. Оголосимо функцію друку шару снігу, скориставшись методами, що були використані в попередніх функціях.

```
draw_snow() {
	printf "%${w}s\n" | tr ' ' "*"
}
```
// #figure(image("img/4.png"), caption: "Функція друку шару снігу") <4>

Викличемо створені функції для друку ялинки.

```
draw_layer 1
draw_layer 2
draw_stem
draw_snow
```
// #figure(image("img/5.png"), caption: "Виклик створених функцій для друку ялинки") <5>

Перевіримо створений сценарій за допомогою заготованої програми перевірки. Перед цим додавши дозвіл на виконання розробленому сценарію за допомогою команди "chmod".

```
Обліковий запис: pzpi-23-2-sytnyk-yehor
Скрипт: ./pzpi-23-2-sytnyk-yehor-task1
---
Перевірка встановлених прав доступу до файла скрипта (-rwxr-xr-x див. завдання):
ПЕРЕВІРЕНО!
---
Перевірка розміру файла скрипта:
ПЕРЕВІРЕНО!
---
Перевірка першого рядка Bash скрипта:
ПЕРЕВІРЕНО! Перший рядок скрипта: #!/bin/bash
---
Перевірка загального синтаксису скрипта:
ПЕРЕВІРЕНО!
---
Перевірка використаних синтаксичних констркцій Bash:
ПЕРЕВІРЕНО! Конструкція: if ...; then ...; fi
ПОМИЛКА! Немає: while ...; do ...; done
ПОМИЛКА! Немає: until ...; do ...; done
ПЕРЕВІРЕНО! Конструкція: for ... in ...; do ...; done
ПЕРЕВІРЕНО! Конструкція: for ((...)); do ... done
ПЕРЕВІРЕНО! Конструкція: function ...() { ... }
---
Перевірка скрипта статичним аналізатором коду:
ПЕРЕВІРЕНО!
---
Перевірка роботи скрипта (приклад див.: pzpi23-task1_example):
---
РЕЗУЛЬТАТИ ПЕРЕВІРКИ
УСПІШНИХ ТЕСТІВ: 12
НЕВДАЛИХ ТЕСТІВ: 2
ПРАВИЛЬНИЙ STDOUT:      625
НЕПРАВИЛЬНИЙ STDOUT:    0
ПРАВИЛЬНИЙ EXIT_CODE:   625
НЕПРАВИЛЬНИЙ EXIT_CODE: 0
ПРАВИЛЬНИЙ STDERR:      625
НЕПРАВИЛЬНИЙ STDERR:    0
---
ОЦІНКА ЗА КОД: 94 ( ./pzpi-23-2-sytnyk-yehor-task1 без перевірки на плагіат )
---
```
// #figure(image("img/6.png"), caption: "Зміна дозволів файлу сценарію") <6>
// #figure(image("img/7.png", width: 80%), caption: "Перевірки роботи створеного сценарію") <7>

Можемо побачити, що створений сценарій проходить усі тести, однак містить не всі конструкції циклів, необхідні за умовою. Додамо ці конструкції окремо від друку ялинки, щоб система перевірки змогла їх розпізнати.

```
i=0
while [[ $i -ne 1 ]]; do i=$((i+1)); done
until [[ $i -eq 2 ]]; do i=$((i+1)); done
```
// #figure(image("img/8.png", width: 80%), caption: "Кунструкції циклів, що не були використані під час друку ялинки") <8>

Проведемо повторні тести після додавання конструкцій циклів.

```
Обліковий запис: pzpi-23-2-sytnyk-yehor
Скрипт: ./pzpi-23-2-sytnyk-yehor-task1
---
Перевірка встановлених прав доступу до файла скрипта (-rwxr-xr-x див. завдання):
ПЕРЕВІРЕНО!
---
Перевірка розміру файла скрипта:
ПЕРЕВІРЕНО!
---
Перевірка першого рядка Bash скрипта:
ПЕРЕВІРЕНО! Перший рядок скрипта: #!/bin/bash
---
Перевірка загального синтаксису скрипта:
ПЕРЕВІРЕНО!
---
Перевірка використаних синтаксичних констркцій Bash:
ПЕРЕВІРЕНО! Конструкція: if ...; then ...; fi
ПЕРЕВІРЕНО! Конструкція: while ...; do ...; done
ПЕРЕВІРЕНО! Конструкція: until ...; do ...; done
ПЕРЕВІРЕНО! Конструкція: for ... in ...; do ...; done
ПЕРЕВІРЕНО! Конструкція: for ((...)); do ... done
ПЕРЕВІРЕНО! Конструкція: function ...() { ... }
---
Перевірка скрипта статичним аналізатором коду:
ПЕРЕВІРЕНО!
---
Перевірка роботи скрипта (приклад див.: pzpi23-task1_example):
---
РЕЗУЛЬТАТИ ПЕРЕВІРКИ
УСПІШНИХ ТЕСТІВ: 14
НЕВДАЛИХ ТЕСТІВ: 0
ПРАВИЛЬНИЙ STDOUT:      625
НЕПРАВИЛЬНИЙ STDOUT:    0
ПРАВИЛЬНИЙ EXIT_CODE:   625
НЕПРАВИЛЬНИЙ EXIT_CODE: 0
ПРАВИЛЬНИЙ STDERR:      625
НЕПРАВИЛЬНИЙ STDERR:    0
---
ОЦІНКА ЗА КОД: 100 ( ./pzpi-23-2-sytnyk-yehor-task1 без перевірки на плагіат )
---
```
// #figure(image("img/9.png", width: 80%), caption: "Повторна перевірка роботи створеного цсенарію") <9>

== Висновки
Під час виконання даної практичної роботи я навчився створювати сценарії автоматизації засобами Bash (Bourne again shell).

#counter(heading).update(0)

#set heading(
  numbering: (i, ..nums) => {
    let char = upper(ua_alpha_numbering.at(i))
    if nums.pos().len() == 0 { char } else {
      char + "." + nums.pos().map(str).join(".")
    }
  },
)

#show heading.where(level: 1): it => {
  set align(center)
  set text(size: 14pt, weight: "regular")

  pagebreak(weak: true)
  bold[ДОДАТОК #counter(heading).display(it.numbering)]
  linebreak()
  it.body
  v(spacing * 2, weak: true)
}

= Повний текст розробленого сценарію

```
#!/bin/bash

if (( $# != 2 )); then echo "Not enough/Too many arguments" >&2; exit 1; fi
if (( $1 < 8 )); then echo "Height must be > 7" >&2; exit 2; fi
if (( $2 < 7 )); then echo "Width must be > 6" >&2; exit 3; fi

h=$(( ($1 % 2 != 0) ? $1 - 1 : $1 ))
w=$(( ($2 % 2 == 0) ? $2 - 1 : $2 ))
lh=$(( $h/2 - 1 ))

if (( ($w-(2*$lh-1) != 2) )); then echo "Can't draw the tree with provided arguments" >&2; exit 4; fi


ch='*'

draw_layer() {
	for ((i=$1; i <= lh; i++)); do
		printf "%*s" "$(( (w - (2*i-1)) / 2 ))"
		printf "%*s\n" "$(( 2*i-1 ))" | tr ' ' "$ch"

		ch=$( [ "$ch" = '*' ] && echo '#' || echo '*' )
	done
}

draw_stem() {
	for i in {0..1}; do
		printf "%$(( (h - 3) / 2 ))s"
		printf "%3s\n" | tr ' ' "#"
	done
}

draw_snow() {
	printf "%${w}s\n" | tr ' ' "*"
}

draw_layer 1
draw_layer 2
draw_stem
draw_snow

i=0
while [[ $i -ne 1 ]]; do i=$((i+1)); done
until [[ $i -eq 2 ]]; do i=$((i+1)); done

```
