feat: add tag_system.dart and update command.dart & to-do

This commit is contained in:
toki 2026-04-04 17:18:17 +09:00
parent fd476df246
commit a54f54ef20
3 changed files with 258 additions and 189 deletions

View file

@ -3,9 +3,9 @@
import 'dart:async';
import 'dart:io';
import 'package:dart_framework/platform/process.dart';
import 'package:dart_framework/utils/path.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:oto_cli/oto/application.dart';
import 'package:oto_cli/oto/core/tag_system.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'package:oto_cli/oto/data/jenkins_data.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe_handle.dart';
@ -108,188 +108,28 @@ abstract class Command {
Command();
late String id;
static RegExp regEx = RegExp(r'(<!)([_a-zA-Z0-9.-]+)(>)');
static RegExp regSetEx = RegExp(r'(<@)([_a-zA-Z0-9.-]+)(>)');
static RegExp get regEx => TagSystem.regEx;
static RegExp get regSetEx => TagSystem.regSetEx;
static Map<String, dynamic> replaceAllTagsMap(Map<String, dynamic> map,
{bool replace = false}) {
var newMap = <String, dynamic>{};
var deleteKeys = <String>[];
for (var pair in map.entries) {
var key = pair.key;
var keyReplacedTag = Command.replaceTagValue(key);
if (keyReplacedTag != key) {
deleteKeys.add(key);
}
if (map[key] is String) {
newMap[keyReplacedTag] = Command.replaceTagValue(map[key]);
} else if (map[key] is List) {
newMap[keyReplacedTag] = replaceAllTagsList(map[key], replace: replace);
} else if (map[key] is Map) {
newMap[keyReplacedTag] = replaceAllTagsMap(map[key], replace: replace);
} else {
newMap[keyReplacedTag] = map[key];
}
if (replace) {
map[keyReplacedTag] = newMap[keyReplacedTag];
}
}
if (replace) {
for (var key in deleteKeys) {
map.remove(key);
}
return map;
}
return newMap;
}
{bool replace = false}) =>
TagSystem.replaceAllTagsMap(map, replace: replace);
static List replaceAllTagsList(List list, {bool replace = false}) {
var newList = <dynamic>[];
for (var i = 0; i < list.length; ++i) {
if (list[i] is String) {
newList.add(Command.replaceTagValue(list[i]));
} else if (list[i] is List) {
newList.add(replaceAllTagsList(list[i]));
} else if (list[i] is Map) {
newList.add(replaceAllTagsMap(list[i]));
} else {
newList.add(list[i]);
}
if (replace) {
list[i] = newList[i];
}
}
if (replace) {
return newList;
}
return newList;
}
static List replaceAllTagsList(List list, {bool replace = false}) =>
TagSystem.replaceAllTagsList(list, replace: replace);
static Future setPropertyValue(String tag, dynamic value,
{bool? showPrint}) async {
var match = regSetEx.firstMatch(tag);
if (match == null) {
throw Exception('Please put the correct value. (ex: <@property.value>)');
}
tag = match.group(2)!;
if (tag.startsWith('property')) {
var key = tag.replaceFirst('property.', '');
Application.instance.property[key] = value;
// await CLI.println('[Return] $key : $value', color: Color.yellowStrong);
if (showPrint ?? true) {
if (value is List) {
Application.log('[Return] $key :\n');
var index = 0;
var length = value.length.toString().length;
for (var item in value) {
var indexStr = index.toString().padLeft(length);
Application.log('$indexStr - $item');
index++;
}
} else {
Application.log('[Return] $key : $value');
}
}
}
return simpleFuture;
}
{bool? showPrint}) =>
TagSystem.setPropertyValue(tag, value, showPrint: showPrint);
static dynamic _getTagValue(String value) {
var setted = false;
dynamic data;
if (value.startsWith('property.')) {
var map = Application.instance.property;
var param = value.replaceFirst('property.', '');
if (map.containsKey(param)) {
data = map[param];
setted = true;
} else {
var params = param.split('.');
if (params.length > 1) {
var subMap = map;
for (var index = 0; index < params.length; index++) {
if (subMap.containsKey(params[index])) {
var value = subMap[params[index]];
if (value is Map<String, dynamic>) {
subMap = value;
} else {
data = value;
setted = true;
}
}
}
}
}
static String getAbs(String path) => TagSystem.getAbs(path);
if (!setted) {
Application.log('"$value" is not exist in property');
data = null;
setted = true;
}
}
static String getPathNormal(String path) => TagSystem.getPathNormal(path);
if (value.startsWith('state.')) {
var map = Application.instance.commandStates;
var param = value.replaceFirst('state.', '');
if (map.containsKey(param)) {
var value = map[param].toString().replaceAll('CommandState.', '');
data = value == '' ? null : value;
} else {
data = null;
}
setted = true;
}
static dynamic replaceTagValue(String raw) => TagSystem.replaceTagValue(raw);
if (!setted) {
throw Exception(
'Command.getTagValue: "$value" is not defined in property.');
}
return data;
}
static String getAbs(String path) {
path = getPathNormal(path);
if (path == '.' || path.startsWith('./') || path.startsWith('.\\')) {
path = path.replaceFirst('.', getPathNormal(Application.current));
}
if (path == '..' || path.startsWith('../') || path.startsWith('..\\')) {
path =
path.replaceFirst('..', '${getPathNormal(Application.current)}/..');
}
return File(path).absolute.path;
}
static String getPathNormal(String path) {
return replaceTagValue(Path.getNormal(path));
}
static dynamic replaceTagValue(String raw) {
var list = regEx.allMatches(raw);
if (list.length == 1) {
var item = list.first;
var sub = list.first.input.substring(item.start, item.end);
if (sub == raw) {
return replaceSingleTagValue(raw);
}
}
for (var match in list) {
var param = match.group(2);
var target = '<!${param!}>';
raw = raw.replaceAll(target, _getTagValue(param).toString());
}
return raw;
}
static dynamic replaceSingleTagValue(String raw) {
dynamic value;
var list = regEx.allMatches(raw);
for (var match in list) {
var param = match.group(2);
value = _getTagValue(param!);
}
return value;
}
static dynamic replaceSingleTagValue(String raw) =>
TagSystem.replaceSingleTagValue(raw);
late ExeHandleData? exeHandle = null;

View file

@ -0,0 +1,236 @@
// ignore_for_file: avoid_init_to_null
import 'dart:io';
import 'package:dart_framework/utils/path.dart';
import 'package:oto_cli/oto/application.dart';
/// OTO
///
/// YAML .
///
/// ## ( )
///
/// ```yaml
/// exe: BuildiOS
/// param:
/// workspace: <!property.workspace> # property
/// version: <!property.build.version> # property
/// state: <!state.buildStep> # commandStates (ready/progress/complete)
/// ```
///
/// (StringAny).
/// toString() .
///
/// ## ( )
///
/// ```yaml
/// exe: SetValue
/// param:
/// set-version: <@property.appVersion> # property에
/// ```
///
/// `setPropertyValue()` `setProperty()` .
class TagSystem {
/// : `<!namespace.key>`
static RegExp regEx = RegExp(r'(<!)([_a-zA-Z0-9.-]+)(>)');
/// : `<@namespace.key>`
static RegExp regSetEx = RegExp(r'(<@)([_a-zA-Z0-9.-]+)(>)');
/// Map의 / .
///
/// [replace] true이면 [map] .
static Map<String, dynamic> replaceAllTagsMap(Map<String, dynamic> map,
{bool replace = false}) {
var newMap = <String, dynamic>{};
var deleteKeys = <String>[];
for (var pair in map.entries) {
var key = pair.key;
var keyReplacedTag = TagSystem.replaceTagValue(key);
if (keyReplacedTag != key) {
deleteKeys.add(key);
}
if (map[key] is String) {
newMap[keyReplacedTag] = TagSystem.replaceTagValue(map[key]);
} else if (map[key] is List) {
newMap[keyReplacedTag] = replaceAllTagsList(map[key], replace: replace);
} else if (map[key] is Map) {
newMap[keyReplacedTag] =
replaceAllTagsMap(map[key], replace: replace);
} else {
newMap[keyReplacedTag] = map[key];
}
if (replace) {
map[keyReplacedTag] = newMap[keyReplacedTag];
}
}
if (replace) {
for (var key in deleteKeys) {
map.remove(key);
}
return map;
}
return newMap;
}
/// List의 .
static List replaceAllTagsList(List list, {bool replace = false}) {
var newList = <dynamic>[];
for (var i = 0; i < list.length; ++i) {
if (list[i] is String) {
newList.add(TagSystem.replaceTagValue(list[i]));
} else if (list[i] is List) {
newList.add(replaceAllTagsList(list[i]));
} else if (list[i] is Map) {
newList.add(replaceAllTagsMap(list[i]));
} else {
newList.add(list[i]);
}
if (replace) {
list[i] = newList[i];
}
}
if (replace) {
return newList;
}
return newList;
}
/// (`<@property.key>`) property에 .
static Future setPropertyValue(String tag, dynamic value,
{bool? showPrint}) async {
var match = regSetEx.firstMatch(tag);
if (match == null) {
throw Exception('Please put the correct value. (ex: <@property.value>)');
}
tag = match.group(2)!;
if (tag.startsWith('property')) {
var key = tag.replaceFirst('property.', '');
Application.instance.property[key] = value;
if (showPrint ?? true) {
if (value is List) {
Application.log('[Return] $key :\n');
var index = 0;
var length = value.length.toString().length;
for (var item in value) {
var indexStr = index.toString().padLeft(length);
Application.log('$indexStr - $item');
index++;
}
} else {
Application.log('[Return] $key : $value');
}
}
}
return Future.value();
}
/// .
///
/// - `property.key` `Application.instance.property[key]`
/// - `property.nested.key` Map
/// - `state.commandId` `Application.instance.commandStates[commandId]`
static dynamic _getTagValue(String value) {
var setted = false;
dynamic data;
if (value.startsWith('property.')) {
var map = Application.instance.property;
var param = value.replaceFirst('property.', '');
if (map.containsKey(param)) {
data = map[param];
setted = true;
} else {
var params = param.split('.');
if (params.length > 1) {
var subMap = map;
for (var index = 0; index < params.length; index++) {
if (subMap.containsKey(params[index])) {
var value = subMap[params[index]];
if (value is Map<String, dynamic>) {
subMap = value;
} else {
data = value;
setted = true;
}
}
}
}
}
if (!setted) {
Application.log('"$value" is not exist in property');
data = null;
setted = true;
}
}
if (value.startsWith('state.')) {
var map = Application.instance.commandStates;
var param = value.replaceFirst('state.', '');
if (map.containsKey(param)) {
var value = map[param].toString().replaceAll('CommandState.', '');
data = value == '' ? null : value;
} else {
data = null;
}
setted = true;
}
if (!setted) {
throw Exception(
'TagSystem.getTagValue: "$value" is not defined in property.');
}
return data;
}
/// (`.`, `..`) . .
static String getAbs(String path) {
path = getPathNormal(path);
if (path == '.' || path.startsWith('./') || path.startsWith('.\\')) {
path = path.replaceFirst('.', getPathNormal(Application.current));
}
if (path == '..' || path.startsWith('../') || path.startsWith('..\\')) {
path =
path.replaceFirst('..', '${getPathNormal(Application.current)}/..');
}
return File(path).absolute.path;
}
/// .
static String getPathNormal(String path) {
return replaceTagValue(Path.getNormal(path));
}
/// (`<!...>`) .
///
/// (dynamic) .
/// .
static dynamic replaceTagValue(String raw) {
var list = regEx.allMatches(raw);
if (list.length == 1) {
var item = list.first;
var sub = list.first.input.substring(item.start, item.end);
if (sub == raw) {
return replaceSingleTagValue(raw);
}
}
for (var match in list) {
var param = match.group(2);
var target = '<!${param!}>';
raw = raw.replaceAll(target, _getTagValue(param).toString());
}
return raw;
}
/// .
static dynamic replaceSingleTagValue(String raw) {
dynamic value;
var list = regEx.allMatches(raw);
for (var match in list) {
var param = match.group(2);
value = _getTagValue(param!);
}
return value;
}
}

View file

@ -35,25 +35,18 @@ AI(Claude 등)가 코드베이스를 더 잘 이해하고 수정할 수 있도
2. `command.dart` enum에 1줄 추가 → `build_runner` 재실행
3. `command_registry.dart`에 import + 함수 호출 추가 (신규 파일인 경우)
### 4. 태그 시스템 중앙화
- **작업**: 태그 파싱/치환 로직을 `command.dart`에서 분리
- **결과**:
- `lib/oto/core/tag_system.dart` 신규 생성 — `TagSystem` 클래스에 전체 구현 이동
- 태그 문법 레퍼런스 주석 포함 (`<!property.key>`, `<!state.cmdId>`, `<@property.key>`)
- `command.dart`의 태그 메서드는 `TagSystem`으로의 얇은 위임(delegate)만 유지
- 기존 `Command.replaceTagValue()` 등 외부 호출부는 수정 없이 그대로 동작
---
## 📋 할 일
### 4. 태그 시스템 중앙화
**우선순위: 중간 / 난이도: 낮음**
현재 태그 처리 로직(`<!property.key>` 등)이 `commands/command.dart` 내부에
흩어져 있다. 별도 파일로 분리하면 AI가 "어떤 태그를 쓸 수 있나?" 질문에
코드 한 곳만 읽어도 답할 수 있다.
작업 내용:
- `lib/oto/core/tag_system.dart` 파일 생성
- 태그 파싱/치환 로직을 `command.dart`에서 이동
- 태그 종류 명세를 주석으로 정리 (`property`, `state`, `common` 등)
- `command.dart``tag_system.dart`를 import해서 사용
---
### 5. Pipeline Executor에 YAML 예제 주석 추가
**우선순위: 중간 / 난이도: 낮음**