Разработка команд 1C:EDT CLI

Разработка команд 1C:EDT CLI #

1C:EDT CLI (далее — CLI) можно расширить собственными командами. Для этого необходимо подключиться к точке расширения com.e1c.g5.v8.dt.cli.api.cliCommand классом, реализующим интерфейс com.e1c.g5.v8.dt.cli.api.ICliCommand. Если вы хотите иметь доступ к сервисам из команды, то подключаться нужно через фабрику создания расширений, как описано в разделе Публичные сервисы.

В интерфейсе ICliCommand нет методов: это интерфейс-маркер, который указывает, что данный класс предоставляет команды CLI. Все методы класса, помеченные аннотацией @com.e1c.g5.v8.dt.cli.api.CliCommand, будут зарегистрированы в качестве команд CLI с именами, аналогичными именам методов, но в “kebab-case” (то есть один класс может предоставить несколько команд CLI).

Если имя метода начинается с _, set, get или is, эти приставки будут удалены при регистрации команды. То есть метод c именем _import будет зарегистрирован как команда import. Это позволяет использовать ключевые слова Java и типичные геттеры и сеттеры Java как команды CLI без лишних приставок.

Также будут удалены приставки с0_, с1_, ..., с9_. Их можно использовать, если нужно сделать в одном классе несколько команд с одинаковым именем, принимающих одинаковые по количеству и типам параметры, но различающихся по названиям аргументов (см. ниже).

Пример реализации команды platform-versions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.e1c.g5.v8.dt.internal.cli.api;
 
import java.util.List;
 
import com._1c.g5.v8.dt.cli.api.IPlatformVersionsApi;
import com.e1c.g5.v8.dt.cli.api.CliCommand;
import com.e1c.g5.v8.dt.cli.api.ICliCommand;
import com.google.inject.Inject;
 
public class PlatformVersionsCmd
    implements ICliCommand
{
 
    @Inject
    IPlatformVersionsApi api;
 
    @CliCommand("PlatformVersionsCmd_Description")
    public void platformVersions()
    {
        api.getSupportedVersions().stream().forEach(System.out::println);
    }
}

Значение аннотации @CliCommand — это справка о команде. Она может быть как обычным текстом, так и названием переменной класса Messages из того же пакета, где лежит класс, реализующий команду. В последнем случае мы можем использовать NLS (фреймворк для локализации в Eclipse) для локализации справки по командам.

Команда может выдать результат напрямую в System.out (например, при помощи System.out.println()). Потоки System.out/in/err переназначены для команд, поэтому, даже если команда участвует в pipe, вывод в System.out будет корректно передан дальше по pipe.

BaseCliCommand #

Вместо реализации интерфейса ICliCommand напрямую вы можете унаследоваться от класса com.e1c.g5.v8.dt.cli.api.components.BaseCliCommand, который предоставляет дополнительные возможности по работе с проектами и рабочей областью (workspace).

Если вы наследуете свой класс от BaseCliCommand, то для подключения к точке расширения вам нужно реализовать свою фабрику создания расширений, унаследовав ее от класса com.e1c.g5.v8.dt.cli.api.components.BaseCliCommandExtensionFactory.

Аргументы команд #

Команда CLI может иметь аргументы, которые передаются как аргументы метода, реализующего команду. Рассмотрим на примере реализации команды import:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class ImportConfigurationFilesCmd
    implements ICliCommand
{
 
    @Inject
    IImportConfigurationFilesApi api;
 
    @CliCommand("ImportConfigurationFilesCmd_Description")
    public void _import(
        @Argument(value = "--version", descriptor = "Version", defaultValue = "") String version,
        @Argument(value = "--base-project-name", descriptor = "BaseProject", defaultValue = "") String baseProject,
        @Argument(value = "--configuration-files", descriptor = "ConfigurationFilesLocation") String configurationFilesLocation,
        @Argument(value = "--project-name", descriptor = "Project") String projectName)
    {
        api.importProject(Paths.get(configurationFilesLocation), projectName, Strings.emptyToNull(version), Strings.emptyToNull(baseProject));
    }
}

Здесь мы видим, что команда import имеет обязательные аргументы --configuration-files и --project, а также необязательные аргументы (для которых указано значение по умолчанию) --version и --base-project-name.

Для объявления аргументов служит аннотация @com.e1c.g5.v8.dt.cli.api.Argument. Ее аргументы:

  1. value (необязательный) — список строк. Имена аргумента (ключи) в командной строке. Если опущен, аргумент не имеет ключа. Такой аргумент может быть только один, и он должен идти последним.
  2. descriptor (необязательный) — строка. Справка по аргументу или имя переменной из класса Messages (аналогично тому, что может быть задано в аннотации @CliCommand для самого метода. См. выше).
  3. defaultValue (необязательный) — строка. Значение по умолчанию, которое передается в аргумент функции, если пользователь указал этот аргумент в командной строке.

Типы аргументов #

Поддерживаемые типы аргументов:

  1. Примитивные типы (int, boolean, float и т. д.).
  2. String.
  3. Массивы других поддерживаемых типов (int[], String[] и т. д.).

Специальные аргументы #

Команда может иметь специальный аргумент типа com.e1c.g5.v8.dt.cli.api.ICliSession. Этот аргумент не задается пользователем с командной строки, а подставляется интерпретатором команд. Поэтому этот аргумент не нужно оформлять через аннотацию @Argument. Он предоставляет специализированные возможности, которые могут понадобиться для некоторых команд. Например, при помощи сессии можно попросить у пользователя ввести данные, как это делает стандартная команда exit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class BaseCmds
    implements ICliCommand
{
 
    @CliCommand("BaseCmds_Exit")
    public void exit(ICliSession session,
        @Argument(value = { "-y", "--yes" }, descriptor = "BaseCmds_NoConfirm", defaultValue = "false") boolean yes)
        throws IOException
    {
        if (!yes)
        {
            String reply = session.readLine(Messages.BaseCmds_ExitConfirm);
            yes = reply != null && (reply.toLowerCase().startsWith(Messages.BaseCmds_ExitYes) || reply.isBlank());
        }
        if (yes && !Thread.currentThread().isInterrupted())
        {
            // shutdown logic here
        }
    }
}

Перегрузка команд #

Можно определить несколько команд с одинаковым именем, но разными аргументами (воспользовавшись стандартной перегрузкой методов в Java). Если же вам нужны несколько команд с одинаковым именем и параметрами одинаковых типов, можете воспользоваться приставками c0_, c1_, ..., c9_:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class CleanUpProjectSourceCmd
    implements ICliCommand
{
 
    @Inject
    ICleanUpProjectSourcesApi cleanUpApi;
 
    @CliCommand("CleanUpProjectSourceCmd_Description")
    public void c1_cleanUpSource(@Argument(value = "--project", descriptor = "ProjectLocation") String projectLocation)
    {
        cleanUpApi.cleanUpProjectSources(Paths.get(projectLocation));
    }
 
    @CliCommand("CleanUpProjectSourceCmd_Description")
    public void c2_cleanUpSource(@Argument(value = "--project-name", descriptor = "Project") String projectName)
    {
        cleanUpApi.cleanUpProjectSources(projectName);
    }  
 
}

Запуск команд в режиме разработки (из Eclipse) #

Чтобы запустить CLI в режиме разработки (из Eclipse), нужно сделать следующее:

  1. Откройте меню редактирования конфигураций запуска Eclipse (Run -> Run Configurations… либо Debug Configurations…).
  2. Сделайте дубликат вашей обычной конфигурации для запуска плагинов (правый клик по конфигурации -> Duplicate). Дальше работайте с дубликатом.
  3. На вкладке Arguments:
    • В Program Arguments допишите в конец через пробел -console.
    • В VM arguments допишите в конец через пробел.
    -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dgosh.args=file:///C:/Users/username/path/to/1CEDT/installation/1cedtcli-startup.txt
    
    (если здесь возникли трудности, см. ниже).
  4. Запустите эту конфигурацию.
  5. После загрузки появится приглашение “1C:EDT> " в консоли Eclipse (вкладка Console). Перейдите на вкладку Console и можете вводить команды.
    • Если вы не видите приглашения, возможно, оно где-то выше. Нажмите Enter пару раз.

При таком способе запуска путь до файла 1cedtcli-startup.txt не должен содержать пробелы и русскоязычные символы. Если путь до папки установки 1C:EDT содержит их, вы можете скопировать этот файл в любое место на диске с простым путем и указать этот путь.

Если по каким-то причинам у вас нет файла 1cedtcli-startup.txt, создайте обычный текстовый файл следующего содержания и используйте его:

equinox:start com.e1c.g5.v8.dt.cli.api
gogo:until { gogo:type -q 1csupport:edtsh } { }
1csupport:edtsh

Если вместо приглашения “1C:EDT> " вы видите приглашение “osgi> “, вы можете вручную ввести следующие команды:

equinox:start com.e1c.g5.v8.dt.cli.api
<... подождать некоторое время, пока загрузится ...>
1csupport:edtsh

После этого, если все прошло успешно, вы увидите приглашение “1С> “.

Тестирование команд #

Для написания интеграционных тестов для команд 1C:EDT CLI удобно использовать сервис com.e1c.g5.v8.dt.cli.api.ICliCommandExecutor. Этот сервис позволяет запускать команды как строки, то есть именно в том виде, в каком они будут запускаться пользователями 1C:EDT CLI. Сервис даёт возможность получить результат выполнения команды, а также весь текст, выведенный на консоль во время её исполнения (отдельно можно получить текст сообщений об ошибках, если они были). В случае ошибки исполнения будет выброшено исключение com.e1c.g5.v8.dt.cli.api.CliCommandException.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@RunWith(JUnitGuiceRunner.class)
@GuiceModules(modules = { ExternalDependenciesModule.class })
public class YourCommandTest
{

    @Inject
    private ICliCommandExecutor commandExecutor;

    @Test
    public void testYourCommand() throws Exception
    {
        ICommandResult result =
            commandExecutor.execute("your-command -arg1 5 -arg2 [a b c d]");

        assertEquals(5, result.getResult());
        assertEquals("...", result.getOutput());
        assertTrue(result.getErrorOutput().isEmpty());
    }

}