Пролог

В программировании микроконтроллеров часто приходится писать программные компоненты которые, в общем-то, очень похожие друг на друга по своей структуре и API. Как у нас говорят

Сделай по образу и подобию

Вот вам яркий пример. Надо написать драйвер SPI на основе драйвера UART. Чтобы функции HAL примерно одинаково выглядели за исключением, конечно же, самого названия программного компонента. Это соответствует принципу наименьшего удивления.

В чем проблема?

Как обычно это происходит. Берут драйвер UART копируют его, переименовывают слово UART на SPI и далее вручную прорабатывают конкретные места в коде.

Проблема в то, что переименовывать файлы и ключевые слова это весьма рутинная и утомительная процедура. Очевидно, что надо её как-то автоматизировать.

Терминология

токен - строка без пробела

консистентность - согласованность данных друг с другом

Постановка задачи

Написать консольную утилиту r.exe, которая рекурсивно заменяет одно ключевое слово (токен) на другое ключевое слово (токен) в той папке, в которой вызвали эту утилиту r

1--при замене учитывать регистр в токене

2--заменять ключевое слово в названии файла

3--Утилита должна называться одной буквой r, чтобы её было легко запомнить (rename all) и быстро набирать

4--У утилиты r должно быть только два ключа old new

-->    r old_word new_word

Реализация.

Как известно есть bash команды, которые делают эту работу .

Фаза 1 Переименовать ключевое слово в файлах

1--Рекурсивно заменить слово oldtext на newtext во всех файлах внутри директории.

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'

Тут для grep -r означает, что искать внутри папок рекурсивно, -l означает показывать только пути к файлам. Для sed -i означает, что замена слова будет произведена прямо в этом же файле (in place), s означает, что надо заменить первый токен на второй токен, флаг g заменяет все вхождения заменяемого токена.

У этой команды есть три недостатка:

1--токен oldtext надо указывать два раза

2--команда длинная и ее сложно запомнить

3--команда длинная и ее долго набирать

Фаза 2: Переименовать имена файлов

--Взять все файлы в этой папке и в каждом имени файла переименовать подстроку at24c02mtr на at24cxx

find . -type f -exec rename -v at24c02mtr at24cxx {} \;

Эта очень удобная команда, когда надо написать программный компонент по образу и подобию другого программного компонента. Сначала переименовываем названия файлов (find+rename)

Исходный код

Исходный код тут тривиальный так как основную работу делают внешние утилиты sed, grep, find и rename

#include "sed.h"

#include <stdio.h>
#include <string.h>

#include "log.h"
#include "win_utils.h"

char* getcwd(char* buf, size_t size);


bool win_cmd_run(const char* const command) {
    bool res = false;
    if(command) {
        LOG_DEBUG(PC, "ExeCmd:[%s],Size:%u", command, strlen(command));
        int ret = system(command);
        if(0 == ret) {
            res = true;
        } else {
            LOG_ERROR(PC, "Ret,%d=%s", ret, CmdRetToStr(ret));
        }
    }
    return res;
}

bool sed_replace(const char* const old, const char* const new) {
    bool res = false;
    if(old) {
        if(new) {
            LOG_INFO(SED, "Replace:[%s]->[%s]", old, new);
            char CurDir[500] = {0};
            char* str = getcwd(CurDir, sizeof(CurDir));
            if(str) {
                LOG_INFO(SED, "Current working dir: %s\n", CurDir);
            } else {
                LOG_ERROR(SED, "getcwd() error");
            }

            char replaceToken[1000] = {0};
            char format[100] = "grep -rl %s . | xargs sed -i 's/%s/%s/g'";
            snprintf(replaceToken, sizeof(replaceToken), format, old, old, new);
            LOG_INFO(SED, "CMD,replaceToken:[%s]", replaceToken);
            res = win_cmd_run(replaceToken);
            log_res(SED, res, "CmdT");

            char repl_file[1000] = {0};
            char formatF[100] = "find . -type f -exec rename -v %s %s {} \;";
            snprintf(repl_file, sizeof(repl_file), formatF, old, new);
            LOG_INFO(SED, "CMD,ReplaceFileName:[%s]", repl_file);
            res = win_cmd_run(repl_file);
            log_res(SED, res, "CmdF");
        }
    }
    return res;
}

Проверка работы утилиты r

Вот так отработала утилита r.exe

C:\workspace\code_base\source\mcal\mcal_yuntu\uart1>r uart spi
I,[SYS] argc 3
I,[SYS] Arg0 [r]
I,[SYS] Arg1 [uart]
I,[SYS] Arg2 [spi]
I,[Sed] Replace:[uart]->[spi]
I,[Sed] Current working dir: C:\workspace\code_base\source\mcal\mcal_yuntu\uart1
I,[Sed] CMD,replaceToken:[grep -rl uart . | xargs sed -i 's/uart/spi/g']
I,[Sed] CMD,ReplaceFileName:[find . -type f -exec rename -v uart spi {} ;]
`./uart.gvi' -> `./spi.gvi'
`./uart.mk' -> `./spi.mk'
`./uart_custom_commands.c' -> `./spi_custom_commands.c'
`./uart_custom_commands.h' -> `./spi_custom_commands.h'
`./uart_custom_const.h' -> `./spi_custom_const.h'
`./uart_custom_diag.h' -> `./spi_custom_diag.h'
`./uart_custom_drv.h' -> `./spi_custom_drv.h'
`./uart_custom_isr.c' -> `./spi_custom_isr.c'
`./uart_custom_isr.h' -> `./spi_custom_isr.h'
`./uart_custom_types.h' -> `./spi_custom_types.h'
`./uart_drv_old21.c' -> `./spi_drv_old21.c'
`./uart_mcal.c' -> `./spi_mcal.c'
`./uart_preconfig.mk' -> `./spi_preconfig.mk'
`./uart_verify.txt' -> `./spi_verify.txt'

C:\workspace\code_base\source\mcal\mcal_yuntu\uart1>

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

Итог

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

Как видите, программирование микроконтроллеров - это не только варить прошивки. Это ещё и разработка самих средств разработки. Да... Это могут быть утилиты loader-ы, обновители версий, стилистические анализаторы и прочее.

Если у Вас есть пожелания к улучшению утилиты r, то пишите в комментариях.

Ссылки

Название

URL

Полезные Заготовки Вызова Утилит Командной Строки

https://habr.com/ru/articles/754858/

Бинарь утилиты r.exe

https://github.com/aabzel/Artifacts/blob/main/r.exe

Стилистический-Анализатор: Проверка Наличия Комментария в Конце Фигурной Скобки

https://habr.com/ru/articles/865536/

Стилистический анализатор: синхронизация объявлений и определений static функций

https://habr.com/ru/articles/846020/

Стилистический Анализатор: Синхронизация порядка объявлений и определений функций

https://habr.com/ru/articles/844436/

Автоматическое Обновление Версии Прошивки

https://habr.com/ru/articles/791768/

C Language: system function (Perform Operating System Command)

https://www.techonthenet.com/c_language/standard_library_functions/stdlib_h/system.php

Комментарии (10)


  1. gev
    21.06.2025 19:33

    Мы сначала делаем интерфейс. Для UART выглядит так, например

    UART interface
    UART interface
    UART interface


  1. alexs963
    21.06.2025 19:33

    #!/bin/sh
    OLD="$1"
    NEW="$2"
    grep -rl $OLD . | xargs sed -i "s/$OLD/$NEW/g"
    find . -type f -exec rename -v "$OLD" "$NEW" {} \;
    


    1. aabzel Автор
      21.06.2025 19:33

      Здорово!


    1. beefdeadbeef
      21.06.2025 19:33

      так появились на свет энциклонги :]


      1. mvv-rus
        21.06.2025 19:33

        ... а также - бинтеллектага в одной из русефекаций древней игрушки "Arx Fatalis".
        А уж что получилось при массовой замене "огр" на "людоед" в другой русефекации из слова "погребальный" - это я написать тут не решаюсь.


    1. randomsimplenumber
      21.06.2025 19:33

      Главное не переименовать переменную I, будет интересно:)


  1. beefdeadbeef
    21.06.2025 19:33

    Я слышал, есть такие clangd или ccls -- попробуйте, вдруг зайдёт.

    Или даже, чем чёрт не шутит, coccinelle.


  1. Jijiki
    21.06.2025 19:33

    вообще у подхода несколько решений, можно грепом, можно оттолкнуться от токенизатора, и сперва отталкиваться от языка который парсим тоесть надо знать особенности языка который будет анализироваться,
    например С подобный язык хорошо кладётся на основу скобочек, сперва реализовывается просчет всех скобочек(скобочки как балансиры {}[]() - я начинал путешествие с фигурных скобок, поиск всех слов от 1 слова как функционал, все стилистические штуки просто захардкодил, но там тоже красиво можно сделать ), и тут в этой позиции, просто открываем файл в дирректории проекта анализируем хидеры, далее пока идёт анализ текущего файла надо учесть то что анализируем заходя если надо в файлы которые заинклюдены в текущий файл, это надо для того чтобы учесть вхождение), конечно можно обойти эту суету с деревом или синтаксимческим анализатором, какими-то скриптами

    можно вообще уйти от рекурсии работая только с текущим файлом, учитывая слова, строки, отступы, и слово которое ищем по всему файлу, тогда анализатор всё равно будет отрабатывать просто окном будет текущий файл проекта

    например в яве на свинге так прокатывает тоже, но там вилка из двух подходов, декоративная работа и работа где надо точно делать потомучто буферы текста не простые там соотв есть подход через обнуление, и через просто проходы с применением стиля оба метода вроде работают, но я знаю способ ток покачто через обнуление и хардкод


  1. yappari
    21.06.2025 19:33

    У этой команды есть три недостатка:

    1--токен oldtext надо указывать два раза

    2--команда длинная и ее сложно запомнить

    3--команда длинная и ее долго набирать

    Это ведь такая шЮтка юмора, да?


  1. randomsimplenumber
    21.06.2025 19:33

    Даже не знаю что более удивительное - перепиливания драйвера UART в SPI, или переименовывание не через IDE.