364 lines
11 KiB
Dart
364 lines
11 KiB
Dart
/// 字符串工具类
|
||
///
|
||
/// 提供常用的字符串处理功能,包括命名风格转换、注释生成等。
|
||
///
|
||
/// # 典型用法示例
|
||
/// ```dart
|
||
/// // snake_case 转 camelCase
|
||
/// StringUtils.toDartPropertyName('user_id'); // userId
|
||
/// // 含特殊字符
|
||
/// StringUtils.toDartPropertyName('user-id'); // userId
|
||
/// // 数字开头
|
||
/// StringUtils.toDartPropertyName('1st_field'); // n1stField
|
||
/// // 空字符串
|
||
/// StringUtils.toDartPropertyName(''); // property
|
||
/// ```
|
||
///
|
||
/// This utility provides string conversion helpers for code generation, such as
|
||
/// converting snake_case to camelCase, generating Dart class names, and cleaning descriptions.
|
||
///
|
||
library;
|
||
|
||
import '../core/models.dart';
|
||
|
||
class StringUtils {
|
||
/// 转换为camelCase
|
||
static String toCamelCase(String input) {
|
||
if (input.isEmpty) return input;
|
||
|
||
// 如果输入已经是camelCase格式(首字母小写),直接返回
|
||
if (RegExp(r'^[a-z][a-zA-Z0-9]*$').hasMatch(input)) {
|
||
return input;
|
||
}
|
||
|
||
// 如果输入是PascalCase格式(首字母大写),转换为camelCase
|
||
if (RegExp(r'^[A-Z][a-zA-Z0-9]*$').hasMatch(input)) {
|
||
return input[0].toLowerCase() + input.substring(1);
|
||
}
|
||
|
||
// 处理下划线分隔的字符串
|
||
final parts = input.split('_').where((p) => p.isNotEmpty).toList();
|
||
if (parts.isEmpty) return input;
|
||
|
||
String result = parts.first.toLowerCase();
|
||
for (int i = 1; i < parts.length; i++) {
|
||
final part = parts[i];
|
||
if (part.isNotEmpty) {
|
||
result += part[0].toUpperCase() + part.substring(1).toLowerCase();
|
||
}
|
||
}
|
||
|
||
return result.isEmpty ? input : result;
|
||
}
|
||
|
||
/// 转换为PascalCase
|
||
static String toPascalCase(String input) {
|
||
if (input.isEmpty) return input;
|
||
|
||
// 如果输入包含下划线,先按下划线分割并转换每个部分
|
||
if (input.contains('_')) {
|
||
final parts = input.split('_');
|
||
String result = '';
|
||
for (final part in parts) {
|
||
if (part.isNotEmpty) {
|
||
// 保持每个部分的原始大小写,只确保首字母大写
|
||
if (part[0].toUpperCase() == part[0]) {
|
||
// 如果首字母已经是大写,保持整个部分不变
|
||
result += part;
|
||
} else {
|
||
// 如果首字母是小写,只转换首字母为大写
|
||
result += part[0].toUpperCase() + part.substring(1);
|
||
}
|
||
}
|
||
}
|
||
return result.isEmpty ? input : result;
|
||
}
|
||
|
||
// 如果输入已经是PascalCase格式(没有下划线且首字母大写),直接返回
|
||
if (input[0].toUpperCase() == input[0]) {
|
||
return input;
|
||
}
|
||
|
||
// 如果输入是camelCase格式(没有下划线),转换首字母为大写
|
||
if (RegExp(r'^[a-zA-Z][a-zA-Z0-9]*$').hasMatch(input)) {
|
||
return input[0].toUpperCase() + input.substring(1);
|
||
}
|
||
|
||
return input;
|
||
}
|
||
|
||
/// 转换为snake_case
|
||
static String toSnakeCase(String input) {
|
||
if (input.isEmpty) return input;
|
||
|
||
// 处理PascalCase和camelCase
|
||
final result =
|
||
input.replaceAllMapped(RegExp(r'([A-Z]+)([A-Z][a-z])'), (match) {
|
||
return '${match[1]!.substring(0, match[1]!.length - 1)}_${match[2]}';
|
||
}).replaceAllMapped(RegExp(r'([a-z\d])([A-Z])'), (match) {
|
||
return '${match[1]}_${match[2]}';
|
||
}).toLowerCase();
|
||
|
||
return result;
|
||
}
|
||
|
||
/// 转换为符合Dart命名规范的属性名
|
||
///
|
||
/// - 支持 snake_case、kebab-case、空格、特殊字符自动转为 camelCase
|
||
/// - 已经是驼峰命名的字符串保持不变
|
||
/// - PascalCase(首字母大写)转换为camelCase(首字母小写)
|
||
/// - 数字开头自动加前缀 n
|
||
/// - 空字符串返回 'property'
|
||
///
|
||
/// # 示例
|
||
/// ```dart
|
||
/// StringUtils.toDartPropertyName('user_id'); // userId
|
||
/// StringUtils.toDartPropertyName('user-id'); // userId
|
||
/// StringUtils.toDartPropertyName('1st_field'); // n1stField
|
||
/// StringUtils.toDartPropertyName('classCadreId'); // classCadreId
|
||
/// StringUtils.toDartPropertyName('PageIndex'); // pageIndex
|
||
/// StringUtils.toDartPropertyName(''); // property
|
||
/// ```
|
||
static String toDartPropertyName(String propName) {
|
||
// 如果已经是camelCase命名(没有下划线且首字母小写),直接返回
|
||
if (RegExp(r'^[a-z][a-zA-Z0-9]*$').hasMatch(propName)) {
|
||
return propName;
|
||
}
|
||
// PascalCase 直接转 camelCase
|
||
if (RegExp(r'^[A-Z][a-zA-Z0-9]*$').hasMatch(propName)) {
|
||
return propName[0].toLowerCase() + propName.substring(1);
|
||
}
|
||
// 处理特殊字符和数字开头的情况
|
||
String result = propName;
|
||
// 如果以数字开头,添加前缀
|
||
if (RegExp(r'^[0-9]').hasMatch(result)) {
|
||
result = 'n$result';
|
||
}
|
||
// 替换特殊字符为下划线
|
||
result = result.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
||
// 转换为camelCase
|
||
result = toCamelCase(result);
|
||
// 确保不为空
|
||
if (result.isEmpty) {
|
||
result = 'property';
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// 清理描述文本,移除特殊字符和格式化多行注释
|
||
static String cleanDescription(String description) {
|
||
if (description.isEmpty) return description;
|
||
|
||
// 移除多余的空白字符和换行符
|
||
String cleaned = description
|
||
.replaceAll(RegExp(r'\s+'), ' ')
|
||
.replaceAll(RegExp(r'[\r\n]+'), ' ')
|
||
.trim();
|
||
|
||
// 移除可能引起语法错误的特殊字符
|
||
cleaned = cleaned
|
||
.replaceAll(RegExp(r'[^\w\s\u4e00-\u9fa5,,.。::;;!!??_/\\-]'), '')
|
||
.replaceAll(RegExp(r'\s+'), ' ')
|
||
.trim();
|
||
|
||
// 如果描述过长,截取前200个字符
|
||
if (cleaned.length > 200) {
|
||
cleaned = '${cleaned.substring(0, 200)}...';
|
||
}
|
||
|
||
return cleaned;
|
||
}
|
||
|
||
/// 生成端点名称
|
||
static String generateEndpointName(String path, String? operationId) {
|
||
// 如果有operationId,优先使用
|
||
if (operationId != null && operationId.isNotEmpty) {
|
||
return toCamelCase(operationId);
|
||
}
|
||
|
||
// 移除路径中的版本前缀
|
||
String cleanPath = path.replaceFirst('/api/v1', '');
|
||
|
||
// 移除开头的斜杠
|
||
if (cleanPath.startsWith('/')) {
|
||
cleanPath = cleanPath.substring(1);
|
||
}
|
||
|
||
// 将路径转换为camelCase
|
||
final parts = cleanPath.split('/');
|
||
if (parts.length >= 2) {
|
||
final controller = parts[0];
|
||
final action = parts[1];
|
||
|
||
// 特殊情况处理
|
||
if (controller.toLowerCase() == 'login' &&
|
||
action.toLowerCase() == 'userlogin') {
|
||
return 'login';
|
||
}
|
||
|
||
// 转换为camelCase
|
||
return toCamelCase('${controller}_$action');
|
||
}
|
||
|
||
return toCamelCase(cleanPath.replaceAll('/', '_'));
|
||
}
|
||
|
||
/// 生成Dart类名
|
||
static String generateClassName(String name) {
|
||
// 确保类名以大写字母开头
|
||
final cleanName = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
||
return toPascalCase(cleanName);
|
||
}
|
||
|
||
/// 生成常量名称 (UPPER_SNAKE_CASE)
|
||
static String generateConstantName(String name) {
|
||
// 清理特殊字符
|
||
final cleanName = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
||
// 转换为 snake_case 然后转为大写
|
||
return toSnakeCase(cleanName).toUpperCase();
|
||
}
|
||
|
||
/// 生成文件名
|
||
static String generateFileName(String name) {
|
||
// 转换为snake_case并添加.dart扩展名
|
||
return '${toSnakeCase(name)}.dart';
|
||
}
|
||
|
||
/// 验证是否为有效的Dart标识符
|
||
static bool isValidDartIdentifier(String identifier) {
|
||
if (identifier.isEmpty) return false;
|
||
|
||
// Dart标识符规则:以字母或下划线开头,后面可以是字母、数字或下划线
|
||
final regex = RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$');
|
||
return regex.hasMatch(identifier);
|
||
}
|
||
|
||
/// 生成枚举值名称
|
||
static String generateEnumValueName(dynamic value, int index) {
|
||
if (value is String) {
|
||
// 尝试从字符串生成合法的枚举名
|
||
final cleanValue = value.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '');
|
||
if (cleanValue.isNotEmpty && isValidDartIdentifier(cleanValue)) {
|
||
return toCamelCase(cleanValue);
|
||
}
|
||
}
|
||
|
||
// 默认使用 value + 索引
|
||
return 'value${index + 1}';
|
||
}
|
||
|
||
/// 提取控制器名称
|
||
static String extractControllerName(ApiPath path) {
|
||
// 从tags中提取控制器名称
|
||
if (path.tags.isNotEmpty) {
|
||
return path.tags.first;
|
||
}
|
||
|
||
// 从路径中提取控制器名称
|
||
final pathParts = path.path.split('/');
|
||
if (pathParts.length > 1) {
|
||
return toPascalCase(pathParts[1]);
|
||
}
|
||
|
||
return 'General';
|
||
}
|
||
|
||
/// 清理路径,保留版本前缀
|
||
static String cleanPath(String path) {
|
||
// 保留版本前缀,只清理其他不必要的字符
|
||
return path;
|
||
}
|
||
|
||
/// 格式化字节大小
|
||
static String formatBytes(int bytes) {
|
||
if (bytes < 1024) return '${bytes}B';
|
||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB';
|
||
if (bytes < 1024 * 1024 * 1024) {
|
||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
|
||
}
|
||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
|
||
}
|
||
|
||
/// 格式化持续时间
|
||
static String formatDuration(Duration duration) {
|
||
if (duration.inMilliseconds >= 1000) {
|
||
return '${(duration.inMilliseconds / 1000).toStringAsFixed(2)}秒';
|
||
} else {
|
||
return '${duration.inMilliseconds}毫秒';
|
||
}
|
||
}
|
||
|
||
/// 转义字符串中的特殊字符
|
||
String escapeString(String input) {
|
||
return input
|
||
.replaceAll('\\', '\\\\')
|
||
.replaceAll('"', '\\"')
|
||
.replaceAll('\n', '\\n')
|
||
.replaceAll('\r', '\\r')
|
||
.replaceAll('\t', '\\t');
|
||
}
|
||
|
||
/// 缩进文本
|
||
String indent(String text, int spaces) {
|
||
final indentation = ' ' * spaces;
|
||
return text.split('\n').map((line) => '$indentation$line').join('\n');
|
||
}
|
||
|
||
/// 生成注释块
|
||
static String generateComment(String text, {bool isBlock = false}) {
|
||
if (text.isEmpty) return '';
|
||
|
||
final cleanText = cleanDescription(text);
|
||
|
||
if (isBlock) {
|
||
return '/**\n * $cleanText\n */';
|
||
} else {
|
||
return '/// $cleanText';
|
||
}
|
||
}
|
||
|
||
/// pluralize 单词
|
||
String pluralize(String word) {
|
||
if (word.isEmpty) return word;
|
||
|
||
final lowerWord = word.toLowerCase();
|
||
|
||
// 特殊复数形式
|
||
const irregularPlurals = {
|
||
'child': 'children',
|
||
'person': 'people',
|
||
'man': 'men',
|
||
'woman': 'women',
|
||
'mouse': 'mice',
|
||
'goose': 'geese',
|
||
};
|
||
|
||
if (irregularPlurals.containsKey(lowerWord)) {
|
||
return irregularPlurals[lowerWord]!;
|
||
}
|
||
|
||
// 规则复数形式
|
||
if (lowerWord.endsWith('y')) {
|
||
return '${word.substring(0, word.length - 1)}ies';
|
||
} else if (lowerWord.endsWith('s') ||
|
||
lowerWord.endsWith('sh') ||
|
||
lowerWord.endsWith('ch') ||
|
||
lowerWord.endsWith('x') ||
|
||
lowerWord.endsWith('z')) {
|
||
return '${word}es';
|
||
} else {
|
||
return '${word}s';
|
||
}
|
||
}
|
||
|
||
/// 生成文件头注释
|
||
static String generateFileHeader(String description, String source) {
|
||
// final timestamp = DateTime.now().toLocal().toString().split(" ").first;
|
||
return '''// $description
|
||
// 基于 Swagger API 文档:
|
||
// 由 xy_swagger_generator by max 生成
|
||
// Copyright (C) 2025 YuanXuan. All rights reserved.
|
||
''';
|
||
}
|
||
}
|