feat: add tag_system.dart and update command.dart & to-do
This commit is contained in:
parent
fd476df246
commit
a54f54ef20
3 changed files with 258 additions and 189 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
236
lib/oto/core/tag_system.dart
Normal file
236
lib/oto/core/tag_system.dart
Normal 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)
|
||||
/// ```
|
||||
///
|
||||
/// 태그가 문자열 전체인 경우 원본 타입 그대로 반환 (String→Any).
|
||||
/// 문자열 일부인 경우 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 예제 주석 추가
|
||||
**우선순위: 중간 / 난이도: 낮음**
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue