Русский
Русский
English
Статистика
Реклама

Posix

Перевод Почему интернационализация и локализация имеют значение

12.10.2020 16:11:28 | Автор: admin

Хабр, отличного всем времени суток! Скоро в OTUS стартует курс Python Web-Developer: мы приглашаем на бесплатный Demo-урок Паттерны Page Controller и Front Controller: реализация в Django и публикуем перевод статьи Nicolle Cysneiros Full Stack Developer (Labcodes).


Согласно всегда правдивой информации на Википедии, в мире насчитывается около 360 миллионов носителей английского языка. Мы, как разработчики, настолько привыкли писать код и документацию на английском языке, что не осознаем, что это число это всего. 4,67% населения всего мира. Единый язык общения между разработчиками это, конечно, хорошо, но это не значит, что пользователь должен чувствовать дискомфорт при использовании вашего продукта.

В этой статье мы начнем говорить о понятиях интернационализации и локализации, а также обозначим их важность для вашего приложения. Затем рассмотрим некоторые элементы интернационализации, доступные разработчикам для работы над проектами на Python и Django. Под конец расскажем о том, как мы изменяли свой процесс разработки, чтобы добавить интернационализацию.

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

Локализация процесс адаптации приложения, продукта или документа для удобства пользователей разных стран и культур.

Тогда как интернационализация процесс обеспечения возможности локализации продукта. То есть реализация программного обеспечения таким образом чтобы оно знало, когда и как показывать различный контент в зависимости от культурной или языковой принадлежности (локали) пользователя.

Как гласит документация Django: локализацию делают переводчики, а интернационализацию разработчики.

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

  • Формат даты и валюты;

  • Конвертация валюты;

  • Преобразование единиц измерения;

  • Символы юникода и двунаправленны текст (см. ниже);

  • Часовые пояса, календарь и особые праздники.

Домашняя страница Википедии на английскомДомашняя страница Википедии на английскомДомашняя страница Википедии на арабскомДомашняя страница Википедии на арабском

С помощью таких адаптаций мы можем улучшить пользовательский опыт нашего приложения.

Как это делается в Python?

GNU gettext

Есть несколько инструментов, которые могут помочь локализовать ваше приложения на Python. Начнем с пакета GNU gettext, который является частью Translation Project. В этом пакете есть:

  • библиотека, которая в рантайме поддерживает извлечение переведенных сообщений;

  • набор соглашений о том, как нужно писать код для поддержки каталогов сообщений;

  • библиотека, поддерживающая синтаксический анализ и создание файлов, содержащих переведенные сообщения.

Следующий фрагмент кода это просто Hello World в файле app.py, где используется модуль gettext в Python для создания объекта перевода (gettext.translation) в домене приложения с указанием директории локали и языка, на который мы хотим перевести строки. Затем мы присваиваем функцию gettext символу нижнего подчеркивания (обычная практика для уменьшения накладных расходов на ввод gettext для каждой переводимой строки), и, наконец, ставим флаг строке Hello World!, чтобы она была переведена.

import gettextgettext.bindtextdomain("app", "/locale")gettext.textdomain("app")t = gettext.translation("app", localedir="locale", languages=['en_US'])t.install()_ = t.gettextgreeting = _("Hello, world!")print(greeting)

После пометки переводимых строк в коде, мы можем собрать их с помощью инструмента командной строки GNU xgettext. Этот инструмент сгенерирует PO-файл, который будет содержать все отмеченные нами строки.

xgettext -d app app.py

PO-файл (или файл Portable Object) содержит список записей, а структура записи выглядит следующим образом:

#  translator-comments#. extracted-comments#: reference#, flag#| msgid previous-untranslated-stringmsgid untranslated-stringmsgstr translated-string

Мы можем добавить для строки комментарий для переводчиков, ссылки и флаги. После этого мы обращаемся к ID записи (msgid), который представляет из себя непереведенную строку, помеченную в коде и строку записи (msgstr) переведенную версию этой строки.

Когда мы запускаем xgettext в командной строке, передавая app.py в качестве входного файла, получается такой PO-файл:

"Project-Id-Version: PACKAGE VERSION\n""Report-Msgid-Bugs-To: \n""POT-Creation-Date: 2019-05-03 13:23-0300\n""PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n""Last-Translator: FULL NAME <EMAIL@ADDRESS>\n""Language-Team: LANGUAGE <LL@li.org>\n""Language: \n""MIME-Version: 1.0\n""Content-Type: text/plain; charset=UTF-8\n""Content-Transfer-Encoding: 8bit\n"#: app.py:7msgid "Hello, world!"msgstr ""

В начале файла у нас есть метаданные о файле, проекте и процессе перевода. Потом стоит непереведенная строка Hello World! в качестве ID записи и пустая строка для строки записи. Если для записи не указан перевод, то при переводе будет использоваться ID записи.

После генерации PO-файла можно начинать переводить термины на разные языки. Важно отметить, что библиотека GNU gettext будет искать переведенные PO-файлы в пути к папке определенного вида (<localedir>/<languagecode>/LCMESSAGES/<domain>.po), то есть для каждого языка, который вы хотите поддерживать, должен быть один PO-файл.

|-- app.py|-- locale   |-- en_US   |   |-- LC_MESSAGES   |       |-- app.po   |-- pt_BR       |-- LC_MESSAGES       |   |-- app.po

Вот пример PO-файла с переводом на португальский:

"Project-Id-Version: PACKAGE VERSION\n""Report-Msgid-Bugs-To: \n""POT-Creation-Date: 2019-05-03 13:23-0300\n""PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n""Last-Translator: FULL NAME <EMAIL@ADDRESS>\n""Language-Team: LANGUAGE <LL@li.org>\n""Language: \n""MIME-Version: 1.0\n""Content-Type: text/plain; charset=UTF-8\n""Content-Transfer-Encoding: 8bit\n"#: app.py:7msgid "Hello, world!"msgstr "Ol, mundo!"

Чтобы использовать переведенные строки в коде, нужно скомпилировать PO-файл в MO-файл с помощью команды msgfmt.

msgfmt -o app.mo app.po

Когда MO-файл готов, можно изменить язык программы на португальский, подав его на вход функции перевода. Если мы запустим следующий код, отмеченная строка будет переведена как Ol, mundo!:

import gettextgettext.bindtextdomain("app", "/locale")gettext.textdomain("app")t = gettext.translation("app", localedir="locale", languages=['pt_BR'])t.install()_ = t.gettextgreeting = _("Hello, world!")print(greeting)

Модуль locale

У этого модуля есть доступ к базе данных локалей (locale) POSIX, и он особенно полезен для обработки форматов дат, чисел и валют. В примере ниже показано как использовать библиотеку locale:

import datetimeimport localelocale.setlocale(locale.LC_ALL, locale='en_US')local_conv = locale.localeconv()now = datetime.datetime.now()some_price = 1234567.89formatted_price = locale.format('%1.2f', some_price, grouping=True)currency_symbol = local_conv['currency_symbol']print(now.strftime('%x'))print(f'{currency_symbol}{formatted_price}')

В данном примере мы импортируем модуль, меняем все настройки локалей на US English и извлекаем соглашения локали. С помощью метода locale.format мы можем отформатировать число и не беспокоиться о разделителях в разрядах десятков и тысяч. С помощью директивы %x для форматирования даты день, месяц и год будут стоять в правильном порядке для текущей локали. Из соглашений локали мы получим и корректный символ для обозначения валюты.

Ниже вы видите выходные данные того кода на Python. Мы видим, что дата соответствует формату Month/Day/Year, десятичный разделитель это точка, а разделитель разряда тысяч запятая, а также есть знак доллара для валюты США.

$ python format_example.py05/03/2019$1,234,567.89

Теперь с тем же кодом, но изменив локаль на Portuguese Brazil, мы получим другой вывод, основанный на бразильских соглашениях форматирования: дата будет отображаться в формате Month/Day/Year, запятая будет разделителем для десятков, а точка для тысяч, символ R$ будет говорить о том, что сумма указана в бразильских реалах.

import datetimeimport localelocale.setlocale(locale.LC_ALL, locale='pt_BR')local_conv = locale.localeconv()now = datetime.datetime.now()some_price = 1234567.89formatted_price = locale.format('%1.2f', some_price, grouping=True)currency_symbol = local_conv['currency_symbol']print(now.strftime('%x'))print(f'{currency_symbol}{formatted_price}')

Легче ли дела обстоят в Django?

Переводы и форматирование

Интернационализация включается по умолчанию при создании проекта на Django. Модуль перевода инкапсулирует библиотеку GNU и предоставляет функционал gettext с настройками перевода на основе языка, полученного из заголовка Accept-Language, который браузер передает в объекте запроса. Итак, весь тот код на Python, который мы видели раньше, оказывается инкапсулирован в модуль перевода из django utils, так что мы можем перепрыгнуть далеко вперед и просто использовать функцию gettext:

from django.http import HttpResponsefrom django.utils.translation import gettext as _def my_view(request):    greetings = _('Hello, World!')    return HttpResponse(greetings)

Для переводов, мы можем помечать переводимые строки в коде Python и в шаблоне (после загрузки тегов интернационализации). Тег trans template переводит одну строку, тогда как тег blocktrans может пометить как переводимый целый блок строк, включая переменный контент.

<p>{% trans "Hello, World!" %}</p><p>{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}</p>

Помимо стандартной функции gettext в Django есть ленивые переводы: помеченная строка будет переведена только тогда, когда значение используется в контексте строки, например, при рендеринге шаблона. Особенно полезно это бывает для перевода атрибутов help_text и verbose_name в моделях Django.

Аналогично интерфейсу командной строки GNU, django admin предоставляет команды эквивалентные тем, которые часто используются в процессе разработки. Чтобы собрать все строки, помеченные как переводимые в коде, вам просто нужно выполнить команды django admin makemessages для каждой локали, которую вы хотите поддерживать в своей системе. Как только вы создадите папку locale в рабочей области проекта, эта команда автоматически создаст правильную структуру папок для PO-файла для каждого языка.

Чтобы скомпилировать все PO-файлы, вам просто нужно выполнить django admin compilemessages. Если вам нужно скопировать PO-файл для конкретной локали, вы можете передать его в качестве аргумента django-admin compilemessages --locale=pt_BR. Чтобы получить более полное представление о том, как работают переводы в Django, вы можете ознакомиться с документацией.

Django также использует заголовок Accept-Language для определения локали пользователя и правильного форматирования дат, времени и чисел. В примере ниже мы видим простую форму с DateField и DecimalField. Чтобы указать, что мы хотим получить эти входные данные в формате, согласующимся с локалью пользователя, нам просто нужно передать параметр localize со значением True в экземпляр поля формы.

from django import formsclass DatePriceForm(forms.Form):    date = forms.DateField(localize=True)    price = forms.DecimalField(max_digits=10, decimal_places=2, localize=True)

Как меняется процесс разработки?

После интернационализации приложения процесс развертывания должен быть адаптирован к процессу перевода. В нашем проекте мы отправляем любые новые термины в перевод сразу после развертывания в промежуточной среде. Развертывание на продакшн будет утверждено сразу после перевода всех терминов и компиляции PO-файлов.

Еще одно важное изменение в потоке разработки в нашем случае затронуло добавление интеграционных тестов для различных локалей на этапе QA. Команда QA будет симулировать локали, поддерживаемые нашим приложением, и проверять, все ли тексты были корректно переведены, валюты и единицы измерения преобразованы.

Наш главный вывод из всего этого процесса интернационализации заключается в том, что все эти шаги должны были быть заложены на этапе проектирования в самом начале проекта. Остановить всю разработку, чтобы провести интернационализацию не лучшее решение. Если ваш проект уже не на ранней стадии, то я рекомендую следовать правилу бойскаута: начните помечать строки, которые необходимо перевести, когда внедряете новую функцию или исправляете некритический баг. Таким образом, мы все равно будете поставлять новые функции вместе с постепенным процессом интернационализации.


Интересно развиваться в данном направлении? Узнайте больше о курсе Python Web-Developer и записывайтесь на бесплатные Demo-уроки в OTUS!

Подробнее..

Давайте напишем Linux терминал

13.01.2021 14:06:06 | Автор: admin

Приветствие

Всем привет! Хочу поделиться своим опытом написания собственного терминала Linux используя Posix API, усаживайтесь поудобнее.

Итоговый результатИтоговый результат

Что должен уметь наш терминал

  1. Запуск процессов в foreground и background режиме

  2. Завершение background процессов из терминала

  3. Поддержка перемещения по директориям

Как устроена работа терминала

  1. Считывание строки из стандартного потока ввода

  2. Разбиение строки на токены

  3. Создание дочернего процесса с помощью системного вызова fork

  4. Замена дочернего процесса на необходимый с помощью системного вызова exec

  5. Ожидание завершения дочернего процесса (в случае foreground процесса)

Немного про системный вызов fork()

Простыми словами системный вызов fork создает полный клон текущего процесса, отличаются они лишь своим идентификатором, т. е. pid.

Рассмотрим пример:

#include <stdio.h>#include <unistd.h>#include <wait.h>int main() {    pid_t pid = fork();        if (pid == 0) {        printf("I'm child process!\n");        } else {        printf("I'm parent process!\n");        wait(NULL);    }        return 0;}
Что выведет данная программа:

I'mparentprocess!
I'mchildprocess!

Что же произошло? Системный вызов fork создал клон процесса, т. е. теперь мы имеем родительский и дочерний процесс.

Чтобы отличить дочерний процесс от родительского в коде достаточно сделать проверку. Если результат функции fork равен 0 - мы имеем дело с дочерним процессом, если нет - с родительским. Это не означает, что в операционной системе id дочернего процесса равен 0.

Причем, порядок выполнения дочернего и родительского процесса ничем не задекларирован. Все будет зависеть от планировщика операционной системы. Поэтому в конце блока родительского процесса добавлена строчка wait(NULL), которая дожидается окончания дочернего процесса.

Подробнее про exec()

В документации есть различные вариации системного вызова exec, но одни отличаются только способом передачи токенов в параметры функции, смысл от этого не изменяется.

Системный вызов exec заменяет текущий процесс сторонним. Естественно, сторонний процесс задается через параметры функции.

Рассмотрим пример:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <wait.h>int main() {    pid_t pid = fork();        if (pid == 0) {        execlp("ls", "ls", "-l", NULL);        exit(1);    } else {        waitpid(pid, NULL, 0);    }        return 0;}
Что выведет данная программа

total 16
-rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main
-rw-r--r-- 1 runner runner 267 Jan 13 07:33 main.c

Что произошло?

Родительский процесс как обычно ожидает завершения дочернего процесса. В это время после системного вызова exec происходит замена дочернего процесса на консольную утилиту ls, она была взята для примера.

Можно сказать мы реализовали простой терминал, вся логика заключается именно в этом.

Перейдем к полноценной реализации

Часть 1. Чтение строки с консоли.

Изначально нам надо уметь считывать строку из командной строки. Думаю, с этим не возникнет сложностей.

char* readline() {    char*   line = NULL;    size_t  size = 0;    ssize_t str_len;    // Reading line from stdin    if ((str_len = getline(&line, &size, stdin)) == -1) {        // Logging all errors except Ctrl-D - terminal shutdown        if (errno != 0) {            printf("[ERROR] Couldn't read from stdin\n");        }        free(line);        printf("\n");        return NULL;    }    // Remove useless \n symbol if exists    if (line[str_len - 1] == '\n') {        line[str_len - 1] = '\0';    }    return line;}

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

Обратите внимание, что при чтении могут возникнуть ошибки, одна из них - нажатие сочетания клавиш ctrl-D. Однако это штатный случай завершения работы терминала, следовательно в данном случае не должна выводиться ошибка.

Часть 2. Разбиение строки на токены.

В данной части будет представлена реализация разбиения строки на массив токенов.

#define DEFAULT_BUFF_SIZE 16#define TOKENS_DELIMITERS " \t"

Определения начальной длины массива и разделителей строки.

char** split(char* line) {    size_t position  = 0;    size_t buff_size = DEFAULT_BUFF_SIZE;    char* token;    // Allocate memory for tokens array    char** tokens = (char**)malloc(sizeof(char*) * buff_size);    if (tokens == NULL) {        printf("[ERROR] Couldn't allocate buffer for splitting!\n");        return NULL;    }    // Tokenize process    token = strtok(line, TOKENS_DELIMITERS);    while (token != NULL) {        // Emplace token to array        tokens[position++] = token;        // If array free space ended - increase array        if (position >= buff_size) {            buff_size *= 2;            tokens = (char**)realloc(tokens, buff_size * sizeof(char*));            if (tokens == NULL) {                printf("[ERROR] Couldn't reallocate buffer for tokens!\n");                return NULL;            }        }        // Getting next token        token = strtok(NULL, TOKENS_DELIMITERS);    }    // Place NULL to the end of tokens array    tokens[position] = NULL;    return tokens;}

Код выглядит довольно громоздким, однако в нем нет ничего сложного.

Очередной токен получается с использованием функции strtok. После чего данный токен копируется в массив токенов. Если в массиве токенов не достаточно места, массив увеличивается в 2 раза.

Завершается всё добавлением завершающего токена равного NULL, т. к. функция exec() ожидает наличие данного завершающего токена.

Часть 3. Выполнение процессов.

Структура хранения списка запущенных процессов.

Напишем определения структур для foreground и background процессов, fg_task и bg_task. А также определение структуры для хранения всех процессов tasks.

// Struct of background taskstruct bg_task_t {    pid_t  pid;           // Process id    bool   finished;      // Process state    char*  timestamp;     // Process state    char*  cmd;           // Command cmd};typedef struct bg_task_t bg_task;// Struct of foreground taskstruct fg_task_t {    pid_t pid;     // Process id    bool finished; // Process state};typedef struct fg_task_t fg_task;// Struct of all tasksstruct tasks_t {    fg_task  foreground; // Process id of foreground bg_task    bg_task* background; // Background task list    size_t   cursor;     // Cursor of background tasks    size_t   capacity;   // Background array capacity};typedef struct tasks_t tasks;

Создадим в коде глобальную переменную типа tasks, которая и будет хранить все наши запущенные процессы.

// Global variable for storing active taskstasks t = {    .foreground = {        .pid = -1,        .finished = true    },    .background = NULL,    .cursor = 0,    .capacity = 0};

Вспомогательные функции добавления процессов.

Установка foreground процесса выглядит банально и не нуждается в комментировании.

void set_foreground(pid_t pid) {    t.foreground.pid = pid;    t.foreground.finished = 0;}

Добавление background процесса выглядит посложнее.

int add_background(pid_t pid, char* name) {    // Temp background task variable    bg_task* bt;    // If end of free space in background array - increase size    if (t.cursor >= t.capacity) {        t.capacity = t.capacity * 2 + 1;        t.background = (bg_task*)realloc(t.background, sizeof(bg_task) * t.capacity);        if (t.background == NULL) {            printf("[ERROR] Couldn't reallocate buffer for background tasks!\n");            return -1;        }    }    // Print info about process start    printf("[%zu] started.\n", t.cursor);    // Save task in temp variable    bt = &t.background[t.cursor];    // Save process info in array    bt->pid = pid;    bt->finished = false;    time_t timestamp = time(NULL);    bt->timestamp = ctime(&timestamp);    bt->cmd = strdup(name);    // Move cursor right    t.cursor += 1;    return 0;}

На деле же все проще. Смотрим есть ли место в массиве для хранения информации о процессе. Если его недостаточно - увеличиваем длину массива в 2 раза. После чего происходит добавление структуры bg_task в массив и последующее заполнение полей структуры информацией о процессе.

Данная функция возвращает -1 в случае неудачи.

Вспомогательные функции для завершения процессов.

Добавим функцию экстренного завершения foreground процесса. Данная функция с помощью системного вызова kill с параметром SIGTERM завершает процесс по id процесса.

void kill_foreground() {    if (t.foreground.pid != -1) {        // Kill process        kill(t.foreground.pid, SIGTERM);        // Set finished flag        t.foreground.finished = true;        printf("\n");    }}

Также добавим функцию для завершения background процесса.

int term(char** args) {    char* idx_str;      // Cursor in index arg    int   proc_idx = 0; // Converted to int index arg    if (args[1] == NULL) {        printf("[ERROR] No process index to stop!\n");    } else {        // Set cursor in index arg        idx_str = args[1];        // Convert string index arg to int        while (*idx_str >= '0' && *idx_str <= '9') {            proc_idx = (proc_idx * 10) + ((*idx_str) - '0');            // Move cursor to right            idx_str += 1;        }        // Kill process if process index not bad        // and target process not finished        if (*idx_str != '\0' || proc_idx >= t.cursor) {            printf("[ERROR] Incorrect background process index!\n");        } else if (!t.background[proc_idx].finished) {            kill(t.background[proc_idx].pid, SIGTERM);        }    }    return CONTINUE;}

Данная функция принимает в себя массив токенов вида {"term", "<bg task index>", NULL}. После чего преобразует токен индекса background задачи в число. Убивает background задачу посредством системного вызова kill.

Непосредственно запуск процессов.

Для удобства введем функцию is_background, определяющую является ли задача фоновым процессом. Данная функция просто проверяет наличие & в конце.

int is_background(char** args) {    // Current position in array    int last_arg = 0;    // Finding last arg in array    while (args[last_arg + 1] != NULL) {        last_arg += 1;    }    // Checking if task is background`    if (strcmp(args[last_arg], "&") == 0) {        // Remove '&' token for future executing        args[last_arg] = NULL;        // Return true        return 1;    }    // Return false if: '&' wasn't founded    return 0;}

Введем функцию launch которая будет запускать background процесс если в конце присутствует токен &, иначе будет запускаться foreground процесс.

int launch(char** args) {    pid_t pid;        // Fork process id    int   background; // Is background task    // Checking if task is background    background = is_background(args);    // Create child process    pid = fork();    // If created failure log error    if (pid < 0) {        printf("[ERROR] Couldn't create child process!\n");    }    // Child process    else if (pid == 0) {        // Try launch task        if (execvp(args[0], args) == -1) {            printf("[ERROR] Couldn't execute unknown command!\n");        }        exit(1);    }    // Parent process    else {        if (background) {            // Try add background task to array            if (add_background(pid, args[0]) == -1) {                // Kill all processes and free                // memory before exit                quit();            }        } else {            // Set foreground task to store            set_foreground(pid);            // Wait while process not ended            if (waitpid(pid, NULL, 0) == -1) {                // Logging error if process tracked with error                // Except when interrupted by a signal                if (errno != EINTR) {                    printf("[ERROR] Couldn't track the completion of the process!\n");                }            }        }    }    return CONTINUE;}

То, что происходит в этой функции уже должно быть все понятно.

  1. Создается дубликат процесса с помощью системного вызова fork

  2. Заменяем дочерний процесс на требуемый с помощью системного вызова exec

  3. Определяем является ли процесс фоновым

  4. Если процесс фоновый - просто добавляем его в список bacground задач

  5. Если процесс не фоновый - дожидаемся окончания выполнения процесса

В функции присутствует неизвестная функция quit. Ее мы разберем в следующем блоке.

Вспомогательные функции для терминала.

Введем функцию execute, которая в зависимости от первого токена выбирает нужное действие.

int execute(char** args) {    if (args[0] == NULL) {        return CONTINUE;    } else if (strcmp(args[0], "cd") == 0) {        return cd(args);    } else if (strcmp(args[0], "help") == 0) {        return help();    } else if (strcmp(args[0], "quit") == 0) {        return quit();    } else if (strcmp(args[0], "bg") == 0) {        return bg();    } else if (strcmp(args[0], "term") == 0) {        return term(args);    } else {       return launch(args);    }}

Данная функция пропускает действие, если первый токен NULL. Переходит в директорию, если первый токен cd. Выводит справку о пользовании, если первый токен help. Завершает работу терминала, если первый токен quit. Выводит список background задач, если первый токен bg. Убивает процесс по индексу, если первый токен term.

Во всех других случаях запускается процесс.

Реализация вспомогательных функций.

#define CONTINUE 1#define EXIT     0

Значение CONTINUEозначает дальнейшее исполнение главного цикла терминала. Значение EXITпрерывает выполнение главного цикла программы.

int cd(char** args) {    if (args[1] == NULL) {        printf("[ERROR] Expected argument for \"cd\" command!\n");    } else if (chdir(args[1]) != 0) {        printf("[ERROR] Couldn't change directory to \"%s\"!\n", args[1]);    }    return CONTINUE;}
int help() {    printf(        "Simple shell by Denis Glazkov.                               \n\n"        "Just type program names and arguments, and hit enter.          \n"        "Run tasks in background using '&' in the end of command.     \n\n"        "Built in functions:                                           \n"        "  cd   <path>        - Changes current working directory      \n"        "  term <bg_task_idx> - Prints list of background tasks        \n"        "  help               - Prints info about shell                \n"        "  bg                 - Prints list of background tasks        \n"        "  quit               - Terminates shell and all active tasks\n\n"        "Use the man command for information on other programs.         \n"    );    return CONTINUE;}
int quit() {    // Temp background task variable    bg_task* bt;    // Disable logging on child killed    signal(SIGCHLD, SIG_IGN);    // Kill foreground process    if (!t.foreground.finished) {        kill_foreground();    }    // Kill all active background tasks    for (size_t i = 0; i < t.cursor; i++) {        // Place background task to temp variable        bt = &t.background[i];        // Kill process if active        if (!bt->finished) {            kill(bt->pid, SIGTERM);        }        // Free memory for command name        free(bt->cmd);    }    return EXIT;}

Функция quit отключает все callback функции по событию SIGCHLD - т. е. функции, выполняющиеся когда дочерний элемент был завершен. После этого завершает все активные процессы.

#define PRIMARY_COLOR   "\033[92m"#define SECONDARY_COLOR "\033[90m"#define RESET_COLOR     "\033[0m"

Основные цвета терминала.

int bg() {    // Temp background task variable    bg_task* bt;    for (size_t i = 0; i < t.cursor; i++) {        // Store background task in temp variable        bt = &t.background[i];        // Print info about task        printf(            "[%zu]%s cmd: %s%s;%s pid: %s%d; %s"            "state: %s%s;%s timestamp: %s%s", i,            SECONDARY_COLOR, RESET_COLOR, bt->cmd,            SECONDARY_COLOR, RESET_COLOR, bt->pid,            SECONDARY_COLOR, RESET_COLOR, bt->finished ? "finished" : "active",            SECONDARY_COLOR, RESET_COLOR, bt->timestamp        );    }    return CONTINUE;}

Часть 4. Главный цикл терминала.

#include <stdlib.h>#include <signal.h>#include "include/shell.h"int main() {    char*  line;   // User input    char** args;   // Tokens in user input    int    status; // Status of execution    // Add signal for killing foreground child on ctrl-c    signal(SIGINT, kill_foreground);    // Add signal for handling end of child processes    signal(SIGCHLD, mark_ended_task);    // Shell is running while    // status == CONTINUE    do {        // Printing left shell info        display();        // Reading user input        line = readline();        if (line == NULL) {            exit(1);        }        // Parse line to tokens        args = split(line);        if (args == NULL) {            free(line);            exit(2);        }        // Try execute command        status = execute(args);        // Free allocated memory        free(line);        free(args);    } while (status);    return 0;}

Здесь и происходит вся магия. Взгляните на следующие строки. С помощью функции signal задаются callback функции на заданные события.

// Add signal for killing foreground child on ctrl-csignal(SIGINT, kill_foreground);// Add signal for handling end of child processessignal(SIGCHLD, mark_ended_task);

Событие SIGINT- срабатывает при нажатии комбинации ctrl-C, которое в дефолтном поведении завершает работу программы. В нашем же случае мы переназначаем его на завершение foreground процесса.

Событие SIGCHLD - срабатывает при завершении дочернего процесса созданyого с помощью системного вызова fork. В нашем случае мы переопределяем его на пометку фоновой задачи как выполненной с помощью функции mark_ended_task.

void mark_ended_task() {    // Temp background task variable    bg_task* bt;    // Get process id of ended process    pid_t pid = waitpid(-1, NULL, 0);    // Handle foreground process    if (pid == t.foreground.pid) {        t.foreground.finished = true;    }    // Handle background process    else {        // Search and remove process form background tasks array        for (size_t i = 0; i < t.cursor; i++) {            // Place task to temp variable            bt = &t.background[i];            if (bt->pid == pid) {                // Print info about process end                printf("[%zu] finished.\n", i);                // Set new state for background process                bt->finished = 1;                break;            }        }    }}

Все что описано в главном цикле терминала можно описать словами:

  1. Вывод информации о пользователе и текущей директории с помощью функции display

  2. Чтение строки из стандартного потока ввода

  3. Разбиение строки на токены

  4. Выполнение ранее описанной функции execute, которая в зависимости от массива токенов выполняет нужное нам действие.

Нам осталось реализовать одну оставшуюся функцию display. Которая получает информацию о текущей директории с помощью функции getcwd и имя пользователя с помощью функции getpwuid.

void display() {    // Try get and print username with color    uid_t uid = geteuid();    struct passwd *pw = getpwuid(uid);    if (pw != NULL) {        printf("%s%s%s:", PRIMARY_COLOR, pw->pw_name, RESET_COLOR);    }    // Try get and print current directory with color    char cwd[MAX_DIRECTORY_PATH];    if (getcwd(cwd, MAX_DIRECTORY_PATH) != NULL) {        printf("%s%s%s", SECONDARY_COLOR, cwd, RESET_COLOR);    }    // Print end of shell info    printf("# ");}

Часть 5. Итоговый результат.

Итоговый результатИтоговый результат

Надеюсь, данный материал полезен. Если вам есть чем дополнить данный материал, буду рад услышать ваши мысли в комментариях.

С исходным кодом проекта вы можете ознакомиться по данной ссылке.

Подробнее..

Зачем нужна новая POSIX-подобная файловая система

16.07.2020 10:19:49 | Автор: admin
Поговорим о том, как устроена ФС Hyperdrive, и тех, кто уже начать её использовать.


Фото moren hsu Unsplash

Пара слов о Hyperdrive


Это POSIX-подобная файловая система для приложений с распределённой архитектурой. Её иерархия представлена единым деревом, а все объекты обладают двумя именами: абсолютное (от корня) и относительное (от текущего рабочего каталога). Hyperdrive разрабатывают авторы открытого P2P-браузера Beaker он позволяет размещать сайты прямо в браузере достаточно создать локальную папку и поделиться соответствующей ссылкой.

Как устроена система


Она реализована на Node.js её исходный код лежит на GitHub. По словам авторов, работа с Hyperdrive напоминает взаимодействие со стандартным модулем Node fs. Вот пример:

var hyperdrive = require('hyperdrive')var archive = hyperdrive('./my-first-hyperdrive') // content will be stored in this folderarchive.writeFile('/hello.txt', 'world', function (err) {  if (err) throw err  archive.readdir('/', function (err, list) {    if (err) throw err    console.log(list) // prints ['hello.txt']    archive.readFile('/hello.txt', 'utf-8', function (err, data) {      if (err) throw err      console.log(data) // prints 'world'    })  })})

В основе Hyperdrive лежат две особые структуры под названием Hypercores. Это журналы, в которые можно только добавлять данные (append-only logs). Первый хранит индексные метаданные, а второй бинарники файлов. Имена файлов и папок проиндексированы с помощью префиксного дерева хешей, чтобы упростить поиск. В каком-то смысле оно служит быстрой системой типа ключ-значение. Целостность данных проверяется с помощью дерева Меркла с криптографической хеш-функцией BLAKE2b-256.

За обработку обращений пользователей к файловой системе отвечает специальный демон. Его CLI позволяют создавать, расшаривать и просматривать директории Hyperdrive. Демон поддерживает FUSE, поэтому диски Hyperdrive могут отображаться как обычные папки на системах Linux и Mac.

Где её используют


Hyperdrive подойдет для разработки распределённых приложений, которые напоминают по функциональности облачные хранилища. Например, её использует платформа с открытым исходным кодом ScienceFair. Она помогает исследователям искать научную литературу, журналы, статьи и выжимки из них, а также обмениваться собственными наработками.

Разумеется, свою файловую систему используют сами разработчики браузера Beaker. Она хранит данные, необходимые для отображения веб-сайтов.


Фото Clint Adair Unsplash

Hyperdrive лежит в основе протокола Dat. Он нужен для обмена данными в распределённых сетях. При передаче файлы разбиваются на небольшие фрагменты разных размеров и собираются в единый Dat-файл принимающей стороной. Система позволяет добавлять в него новые фрагменты, но не разрешает модифицировать или удалять существующие. Такой подход позволяет сохранить историю изменения документов.

Сегодня вокруг Dat уже сформировалось достаточно обширное сообщество, а его продвижением занимается специальный фонд Dat Foundation его поддерживает Mozilla и Code for Science & Society. В перспективе эти организации поспособствуют росту популярности как протокола Dat, так и файловой системы Hyperdrive.



О чем мы пишем в корпоративном блоге 1cloud.ru:

Резервное копирование файлов: как подстраховаться от потери данных
Минимизация рисков: как не потерять ваши данные
Где полезны объектные хранилища
RAID-массив в виртуальной машине


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru