OberonCore
https://forum.oberoncore.ru/

A2 в качестве командного интерпретатора
https://forum.oberoncore.ru/viewtopic.php?f=22&t=6402
Страница 1 из 1

Автор:  Ярослав Романченко [ Понедельник, 27 Май, 2019 08:30 ]
Заголовок сообщения:  A2 в качестве командного интерпретатора

Как-то в очередной раз у меня возникла задача поменять пару строчек в нескольких файлах и я не стал использовать готовое решение :) , а задумал целую концепцию :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 Мб весит.

Автор:  budden [ Понедельник, 27 Май, 2019 11:37 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

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

Автор:  Ярослав Романченко [ Понедельник, 27 Май, 2019 11:44 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Автор:  Comdiv [ Понедельник, 27 Май, 2019 13:45 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

На хабре Вам сразу расскажут про sed, awk, python и powershell.

Автор:  Ярослав Романченко [ Понедельник, 27 Май, 2019 13:48 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

Comdiv писал(а):
На хабре Вам сразу расскажут про sed, awk, python и powershell для Windows.
так они ж большие и тяжёлые :)

Автор:  Comdiv [ Понедельник, 27 Май, 2019 13:58 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Автор:  Ярослав Романченко [ Понедельник, 27 Май, 2019 14:40 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Автор:  budden [ Вторник, 28 Май, 2019 18:25 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Автор:  Ярослав Романченко [ Вторник, 28 Май, 2019 19:41 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Автор:  budden [ Вторник, 28 Май, 2019 23:10 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Автор:  Ярослав Романченко [ Четверг, 12 Сентябрь, 2019 13:22 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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

Вложения:
OberonConsoleUtils.zip [780.52 КБ]
Скачиваний: 20

Автор:  Ярослав Романченко [ Воскресенье, 22 Сентябрь, 2019 02:09 ]
Заголовок сообщения:  Re: A2 в качестве командного интерпретатора

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


Вложения:
OberonConsoleUtils.zip [782.6 КБ]
Скачиваний: 8

Страница 1 из 1 Часовой пояс: UTC + 3 часа
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/