OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Четверг, 28 Март, 2024 16:58

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 13 ] 
Автор Сообщение
СообщениеДобавлено: Понедельник, 27 Май, 2019 08:30 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
Как-то в очередной раз у меня возникла задача поменять пару строчек в нескольких файлах и я не стал использовать готовое решение :) , а задумал целую концепцию :D
Думаю, если накинуть сюда ещё парочку интересных задачек и решений, может получиться материал для неплохой статьи на Хабре для популяризации А2 :roll:
Что-бы решить задачу замены строк в нескольких файлах надо либо качать какую-то утилиту, либо воспользоваться средствами, предоставляемыми командным интерпретатором системы.
Решение для командного интерпретатора системы мне показалось вообще страшным (что в нём вообще происходит, как понять :?: ):
Код:
@echo off
REM -- Prepare the Command Processor --
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEDELAYEDEXPANSION

::BatchSubstitude - parses a File line by line and replaces a substring"
::syntax: BatchSubstitude.bat OldStr NewStr File
::          OldStr [in] - string to be replaced
::          NewStr [in] - string to replace with
::          File   [in] - file to be parsed
:$changed 20100115
:$source https://www.dostips.com
if "%~1"=="" findstr "^::" "%~f0"&GOTO:EOF
for /f "tokens=1,* delims=]" %%A in ('"type %3|find /n /v """') do (
    set "line=%%B"
    if defined line (
        call set "line=echo.%%line:%~1=%~2%%"
        for /f "delims=" %%X in ('"echo."%%line%%""') do %%~X
    ) ELSE echo.
)

А теперь Active Oberon way. Можно читателя на Хабре познакомить со всеми нюансами кода, например, обратить внимание на перегрузку операторов для удобного вывода сообщений в лог и на разбор параметров командной строки и надёжное закрытие файлов:
Код:
MODULE OberonConsoleUtils;

(** AUTHOR "Yaroslav Romanchenko (SAGE) http://sage.com.ua/ rapturize@gmail.com";
   PURPOSE "Different utilities usefull for console"; *)

IMPORT
   Commands, Options, Files, Strings, Streams;
   
CONST
   CRLF = ''0DX''0AX'';
   
TYPE
   String = Strings.String;
   Chars = ARRAY OF CHAR;
   
OPERATOR "+"(CONST a1, a2: Chars): String;
VAR str: String;
BEGIN
   NEW(str, Strings.Length(a1) + Strings.Length(a2) + 1);
   Strings.Concat(a1, a2, str^);
   RETURN str
END "+";

OPERATOR "+"(i: HUGEINT; CONST a2: Chars): String;
VAR
   a1: ARRAY 32 OF CHAR;
   str: String;
BEGIN
   Strings.IntToStr(i, a1);
   NEW(str, Strings.Length(a1) + Strings.Length(a2) + 1);
   Strings.Concat(a1, a2, str^);
   RETURN str
END "+";

VAR
   bSilent: BOOLEAN;

PROCEDURE Message(log: Streams.Writer; strMessage: String);
BEGIN
   IF ~bSilent THEN
      log.String(strMessage^)
   END
END Message;

PROCEDURE ReadFile(log: Streams.Writer; CONST aFileName: ARRAY OF CHAR; VAR iLength: LONGINT): String;
VAR
   file: Files.File;
   reader: Files.Reader;
   str: String;
BEGIN
   str := NIL;
   file := Files.Old(aFileName);
   IF file = NIL THEN
      Message(log, 'Bad file name: "' + aFileName + '"' + CRLF)
   ELSE
      NEW(str, file.Length() + 1);
      Files.OpenReader(reader, file, 0);
      reader.Bytes(str^, 0, LEN(str^), iLength)
   END;
   RETURN str
FINALLY
   IF file # NIL THEN
      file.Close()
   END
END ReadFile;

PROCEDURE WriteFile(log: Streams.Writer; CONST aFileName, aContent: ARRAY OF CHAR; iPos, iLength: LONGINT);
VAR
   file: Files.File;
   writer: Files.Writer;
BEGIN
   Message(log, 'Writing file "' + aFileName + '" ...' + CRLF);
   file := Files.New(aFileName);
   IF file = NIL THEN
      Message(log, 'Unable to create file: "' + aFileName + '"' + CRLF)
   ELSE
      Files.OpenWriter(writer, file, 0);
      writer.Bytes(aContent, iPos, iLength)
   END
FINALLY
   IF file # NIL THEN
      writer.Update();
      Files.Register(file);
      file.Close()
   END
END WriteFile;

PROCEDURE StringReplace(CONST aString, aOldPat, aNewPat: ARRAY OF CHAR; bIgnoreCase: BOOLEAN; VAR nReplaced: LONGINT): Strings.Buffer;
VAR
   length: LONGINT;
   from, pos, posLast: SIZE;
   buffer: Strings.Buffer;
   writer: Streams.Writer;
BEGIN
   length := LEN(aString);
   NEW(buffer, length + length DIV 16);
   writer := buffer.GetWriter();
   from := 0;
   posLast := -1;
   nReplaced := 0;
   REPEAT
      pos := Strings.GenericPos(aOldPat, from, aString, bIgnoreCase, FALSE);
      IF pos >= 0 THEN
         posLast := pos;
         INC(nReplaced);
         IF pos > 0 THEN
            writer.Bytes(aString, LONGINT(from), LONGINT(pos - from))
         END;
         writer.Bytes(aNewPat, 0, Strings.Length(aNewPat));
         from := pos + Strings.Length(aOldPat)
      ELSIF posLast >= 0 THEN (* Write tail *)
         writer.Bytes(aString, LONGINT(from), LONGINT(length - from))
      END
   UNTIL pos < 0;
   IF nReplaced = 0 THEN (* Nothing replaced, just copy *)
      writer.Bytes(aString, 0, LONGINT(length))
   END;
   RETURN buffer
END StringReplace;

(** Usage: ConsoleUtils.ReplaceStringInFile --old="old string" --new="new string" <Input file> [<Ouput file>] *)
PROCEDURE ReplaceStringInFile*(context: Commands.Context);
VAR
   bOptionsOk, bIgnoreCase: BOOLEAN;
   options: Options.Options;
   aOldPat, aNewPat: ARRAY 256 OF CHAR;
   aFileNameIn, aFileNameOut: Files.FileName;
   strContent: String;
   buffer: Strings.Buffer;
   nRead, nReplaced: LONGINT;
BEGIN
   NEW(options);
   options.Add("o", "old", Options.String);
   options.Add("n", "new", Options.String);
   options.Add("s", "silent", Options.Flag);
   options.Add("c", "case", Options.Flag); (* case sensitive *)
   bOptionsOk := options.Parse(context.arg, context.out) &
      options.GetString("old", aOldPat) & options.GetString("new", aNewPat);
   IF bOptionsOk THEN
      context.arg.SkipWhitespace();
      context.arg.String(aFileNameIn);
      bOptionsOk := aFileNameIn # ""
   END;
   
   bSilent := options.GetFlag("silent");
   bIgnoreCase := ~options.GetFlag("case");

   IF ~bOptionsOk THEN
      Message(context.out, CRLF + "Required parameters not provided. Usage:" + CRLF +
         'ConsoleUtils.ReplaceStringInFile --old="old string" --new="new string" <Input file> [<Ouput file>]' + CRLF)
   ELSE
      context.arg.SkipWhitespace();
      context.arg.String(aFileNameOut);
      strContent := ReadFile(context.out, aFileNameIn, nRead);
      IF strContent # NIL THEN
         Message(context.out, 'File "' + aFileNameIn + '" opened, ' + nRead + ' bytes read.' + CRLF);
         buffer := StringReplace(strContent^, aOldPat, aNewPat, bIgnoreCase, nReplaced);
         Message(context.out, nReplaced + ' occurences of pattern "' + aOldPat + '" found.' + CRLF);
         IF aFileNameOut # "" THEN
            WriteFile(context.out, aFileNameOut, buffer.GetString()^, 0, buffer.GetLength() - 1)
         ELSE
            WriteFile(context.out, aFileNameIn, buffer.GetString()^, 0, buffer.GetLength() - 1)
         END
      END
   END
END ReplaceStringInFile;

BEGIN
END OberonConsoleUtils.

System.Free OberonConsoleUtils ~
И вот, как это можно использовать. Например, если-бы вы разрабатывали что-то в среде CodeTyphon (как я в своё время разработал CGI движок своего сайта), вы бы могли столкнуться с ситуацией, когда автор CodeTyphon взял и поменял расширения для некоторых типов файлов в CodeTyphon... и, естественно, какие-то файлы на какие-то ссылаются. В общем, расширения поменять не проблема, но ссылки поправить уже придётся руками. Автор CodeTyphon не предусмотрел ни какой конверсии между версиями проектов.
И вот, теперь уже с применением А2 и моей небольшой утилиты можно так эту проблему решить:
Код:
rem Convert project from CT v. 5.XX to CT v. 6.XX
rename *.lpi *.ctpr
rename *.lpr *.ppr
rename *.lfm *.frm
del oberon.txt
for %%G in (*.pas) do echo/OberonConsoleUtils.ReplaceStringInFile -o=*.lfm -n=*.frm %%G ~>>oberon.txt
for %%G in (*.ctpr) do echo/OberonConsoleUtils.ReplaceStringInFile -o=.lpr -n=.ppr %%G ~>>oberon.txt
echo/System.PowerDown ~>>oberon.txt
oberon.exe run oberon.txt
Код:
rem Convert project from CT v. 6.XX to CT v. 5.XX
rename *.ctpr *.lpi
rename *.ppr *.lpr
rename *.frm *.lfm
del oberon.txt
for %%G in (*.pas) do echo/OberonConsoleUtils.ReplaceStringInFile -o=*.frm -n=*.lfm %%G ~>>oberon.txt
for %%G in (*.lpi) do echo/OberonConsoleUtils.ReplaceStringInFile -o=.ppr -n=.lpr %%G ~>>oberon.txt
echo/System.PowerDown ~>>oberon.txt
oberon.exe run oberon.txt
В итоге, для запуска такого батничка понадобится лишь oberon.exe и скомпилированный OberonConsoleUtils.
В случае 32-х разрядной А2 oberon.exe будет занимать 952 Кб, а OberonConsoleUtils.GofW - 8,44 Кб
Неплохой такой контраст в мире гигабайтных сред разработки :mrgreen:
Ведь можно утилиты и посерьёзнее написать, чем замена строк в файлах...
Что скажете, какие ещё идеи предложите?

PS. А в качестве среды разработки таких утилит можно предложить WinA2Mini, собранную из последних исходников, которая в 7z архиве всего 9,12 Мб весит.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Понедельник, 27 Май, 2019 11:37 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 1557
Идея использовать мини-А32 хороша. А вот спроектировать хороший командный интерпретатор - это задача очень сложная. Во всяком случае, у меня пока есть только определённые намётки. Проблема в том, что есть традиции. Они зачастую плохие (вообще путь юникс - это кучка протекающих абстракций). Но беда в том, что они удобны и наступание на грабли происходит недостаточно часто, чтобы отказаться от них. И, главное, они стандартизированы.

Кроме того, идеологически командные интерпретаторы астрономически далеки от привычных парадигм программирования, таких, как ООП или асинхронное программирование.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Понедельник, 27 Май, 2019 11:44 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
budden писал(а):
Кроме того, идеологически командные интерпретаторы астрономически далеки от привычных парадигм программирования, таких, как ООП или асинхронное программирование.
Как вариант, такие скрипты с минимальной традиционной обвязкой, как я привёл, зато с возможностью подключить "тяжёлую артиллерию" в виде A2 с задействованием мощи процессора на полную катушку с параллельными потоками и т.д. и скоростью компилированного кода.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Понедельник, 27 Май, 2019 13:45 

Зарегистрирован: Четверг, 08 Май, 2008 19:13
Сообщения: 1447
Откуда: Киев
На хабре Вам сразу расскажут про sed, awk, python и powershell.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Понедельник, 27 Май, 2019 13:48 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
Comdiv писал(а):
На хабре Вам сразу расскажут про sed, awk, python и powershell для Windows.
так они ж большие и тяжёлые :)


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Понедельник, 27 Май, 2019 13:58 

Зарегистрирован: Четверг, 08 Май, 2008 19:13
Сообщения: 1447
Откуда: Киев
sed ~ 70Kb, awk ~ 700 Kb, а python и powershell всё равно идут в комплекте в соответствующих ОС. Я, кстати, только что установил powershell на Ubuntu 16.04. Лёгкость установки компенсирует размер пакета, а лишний десяток мегабайт сейчас никого не испугает, там не гигабайты.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Понедельник, 27 Май, 2019 14:40 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
Comdiv писал(а):
sed ~ 70Kb, awk ~ 700 Kb, а python и powershell всё равно идут в комплекте в соответствующих ОС. Я, кстати, только что установил powershell на Ubuntu 16.04. Лёгкость установки компенсирует размер пакета, а лишний десяток мегабайт сейчас никого не испугает, там не гигабайты.
Ну, вот, хорошо, что на родной оберонкоре сначала запостил. Оберонщики сильно бить не будут :lol:


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 28 Май, 2019 18:25 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 1557
Насколько я понял показанный код - это всё же колхоз. При этом - да, идея использовать нормальный язык в качестве командного интерпретатора - это очень хорошая идея. Есть, к примеру, wish, который умеет показывать окошки, модальные или немодальные, но в нём нет & и > из bash. А чтобы был и с окошками, и с перенаправлением, и компилируемые - вот это была бы конфетка.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 28 Май, 2019 19:41 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
budden писал(а):
колхоз
Колхоз ни колхоз, а скорее, некий прототип.
Я пробовал сделать такой же батник, без создания временного файла что-бы передавать параметры в oberon.exe, например, через pipe, но пока что-то не срабатывает. Хотя, oberon.exe умеет общаться через pipe.
Как-то так:
Цитата:
(for %%G in (*.pas) do echo/ OberonConsoleUtils.ReplaceStringInFile -o=*.lfm -n=*.frm %%G
for %%G in (*.ctpr) do echo/ OberonConsoleUtils.ReplaceStringInFile -o=.lpr -n=.ppr %%G
echo/ System.PowerDown )|oberon.exe
Ведь в данном случае хорошо сформировать все команды к oberon.exe в один большой пакет, а не дёргать его каждый раз. Не правда-ли?
budden писал(а):
модальные или немодальные
просто диалоговые окошки для ввода параметров? В чём сложности? Микро A2 можно под это приспособить


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 28 Май, 2019 19:54 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
Можно сформировать один исполнимый файл и с минимальной обвязкой он будет исполнять всякие команды, показывать диалоги... и т.д. и даже при этом запустившись всего один единственный раз за всю сессию выполнения скрипта.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 28 Май, 2019 23:10 

Зарегистрирован: Понедельник, 11 Сентябрь, 2017 13:23
Сообщения: 1557
См. как устроен wish. Либо ActiveTcl, либо в Linux просто команда wish. Не нужно никаких передач. A2 сам должен всё это делать - запускать программы, создавать трубы и прочая. Хотя, честно сказать, это задача тяжёлая и стрёмная. Например, Powershell вроде работает под Linux, но говорят, что слишком большая доля его полезности связана с наличием интерфейсов к виндоус-специфичным возможностям, соответственно в Linux получается мимо кассы.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Четверг, 12 Сентябрь, 2019 13:22 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
Вот, ощутив в очередной раз потребность в простой утилите для решения своей конкретной задачи, ещё немного расширил библиотечку командных утилит функцией преобразования кодировок.
Потребность была в преобразовании кучи паскаль исходников из кодировки 1250 в UTF-8 с BOM.
Хоть 1250 и не кириллическая, быстро подшаманил её в свой модуль CyrillicUtilities, благо он в плане добавления кодировок очень легко расширяем.
Попутно нашёл в каком месте декодеры превращали все окончания строк из Windows в Unix и для 1250 это исправил. Это конечно можно считать дефектом, но лучше, вероятно, расширить интерфейс кодеков возможностью конвертации Windows <-> Unix.
И ещё попутно втемяшил добавление BOM. Тоже можно сделать опцией. Но, поскольку, теперешний интерфейс кодеков не предполагает никаких опций пришлось ввести на скорую руку.
Может сообщество изволит опять закидать камнями. Можно бесконечно всё, конечно, обсуждать... Но, для продвижения Оберонов надо именно что-то делать.
Вот, такой вот вырисовывается "чемоданчик" утилит. Уже с двумя функциями :D


Вложения:
OberonConsoleUtils.zip [780.52 КБ]
Скачиваний: 292
Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Воскресенье, 22 Сентябрь, 2019 02:09 
Аватара пользователя

Зарегистрирован: Пятница, 11 Май, 2007 21:57
Сообщения: 1488
Откуда: Украина, Киев
1. Убрал зависимость модуля Codecs от Unzip (благодаря этому для работы утилиты теперь не нужны модули Unzip и Inflate)
2. Изменения в CyrillicUtilities интегрировал в репозиторий
3. Исправил интересный дефект из-за которого всё работало как-то через раз...
Оказывается, если запускать батники, например, из TotalCommander, буква дисковода в путях будет в верхнем регистре, если запускать из терминального окна, буква дисковода будет в нижнем регистре. Это, оказывается, хорошо известная проблема в Windows.
Поскольку для A2 регистр символов важен, это вызывало проблемы.
Вот, можете позапускать такой простой батник из разных мест и посмотреть, какая будет буква диска:
Код:
echo %~dp0
pause


Вложения:
OberonConsoleUtils.zip [782.6 КБ]
Скачиваний: 296
Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 13 ] 

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2005-2024, участники конференции «OberonCore», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Без разрешения участников и ссылки на конференцию «OberonCore» любое воспроизведение и/или копирование высказываний полностью и/или по частям запрещено.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB