add(dcli): refactoring from dcli_common

This commit is contained in:
Patrick Stewart 2024-08-13 12:50:36 -07:00
parent 46c43388e2
commit 59e30aa9be
5 changed files with 223 additions and 1 deletions

View file

@ -0,0 +1,21 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// This library file exports common utility functions and controllers used in the Protevus Console package.
///
/// It includes:
/// - Functionality for determining paths, exported from 'determine_paths.dart'
/// - A unit test controller, exported from 'unit_test_controller.dart'
///
/// These exports allow other parts of the application to easily access and use
/// these common utilities without needing to import them individually.
library;
export 'package:protevus_console/src/common/determine_paths.dart';
export 'package:protevus_console/src/common/unit_test_controller.dart';

View file

@ -0,0 +1,144 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:path/path.dart';
/// Determines the paths for source and backup directories based on the given parameters.
///
/// [path] is the relative or absolute path to the
/// file that we are going to backup. If [path] is
/// relative then it is relative to [workingDirectory].
/// [backupDir] is the temporary directory that we
/// are going to backup [path] to.
///
/// We use the following directory structure for the backup
/// relative/<path to [path]>
/// absolute/<path to [path]>
///
/// On Windows to accomodate drive letters we need a slightly
/// different directory structure
/// relative/<path to [path]>
/// absolute/<XDrive>/<path to [path]>
///
/// Where 'X' is the drive letter that [path] is located on.
///
Paths determinePaths({
required String path,
required String workingDirectory,
required String backupDir,
}) {
late final String sourcePath;
late final String backupPath;
/// we use two different directories for relative and absolute
/// paths otherwise we can't differentiate when it comes time
/// to restore.
if (isRelative(path)) {
backupPath = normalize(absolute(join(backupDir, 'relative', path)));
sourcePath = join(workingDirectory, path);
} else {
sourcePath = normalize(absolute(path));
final translatedPath =
translateAbsolutePath(path, workingDirectory: workingDirectory);
backupPath = join(backupDir, 'absolute', _stripRootPrefix(translatedPath));
}
return Paths(sourcePath, backupPath);
}
/// Represents a pair of paths for source and backup files.
///
/// This class is used to store and manage the paths for a source file
/// and its corresponding backup file.
///
/// [sourcePath] is the path to the original source file.
/// [backupPath] is the path where the backup of the source file will be stored.
class Paths {
Paths(this.sourcePath, this.backupPath);
String sourcePath;
String backupPath;
}
/// Removes the root prefix (/ or \) from an absolute path
/// If there is no root prefix the original [absolutePath]
/// is returned untouched.
///
/// If the [absolutePath] only contains the root prefix
/// then a blank string is returned
///
/// /hellow -> hellow
/// hellow -> hellow
/// / ->
///
String? _stripRootPrefix(String absolutePath) {
if (absolutePath.startsWith(r'\') || absolutePath.startsWith('/')) {
if (absolutePath.length > 1) {
return absolutePath.substring(1);
} else {
// the path only contained the root prefix and nothing else.
return '';
}
}
return absolutePath;
}
/// Translates an absolute path to a standardized format, primarily for Windows systems.
///
/// C:/abc -> /CDrive/abc
/// C:\abc -> /CDrive\abc
/// \\\abc -> \abc
/// \\abc -> abc
///
/// The [context] is only used for unit testing so
/// we can fake the platform separator.
String translateAbsolutePath(
String absolutePath, {
String? workingDirectory,
p.Context? context,
}) {
final windowsStyle = context != null && context.style == Style.windows;
if (!windowsStyle && !Platform.isWindows) {
return absolutePath;
}
context ??= p.context;
// ignore: parameter_assignments
workingDirectory ??= Directory.current.path;
final parts = context.split(absolutePath);
if (parts[0].contains(':')) {
final index = parts[0].indexOf(':');
final drive = parts[0][index - 1].toUpperCase();
return context.joinAll(['\\${drive}Drive', ...parts.sublist(1)]);
}
if (parts[0].startsWith(r'\\')) {
final uncparts = parts[0].split(r'\\');
return context.joinAll([r'\UNC', ...uncparts.sublist(1)]);
}
if (absolutePath.startsWith(r'\') || absolutePath.startsWith('/')) {
String drive;
if (workingDirectory.contains(':')) {
drive = workingDirectory[0].toUpperCase();
} else {
drive = Directory.current.path[0].toUpperCase();
}
return context.joinAll(['\\${drive}Drive', ...parts.sublist(1)]);
}
/// probably not an absolute path
/// so just pass back what we were handed.
return absolutePath;
}

View file

@ -0,0 +1,56 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'package:scope/scope.dart';
/// A utility class for managing unit test behavior in DCli.
///
/// This class provides static members to control and detect when code is running
/// within a unit test environment. It uses the `scope` package to manage a boolean
/// flag indicating whether the current execution context is a unit test.
class UnitTestController {
/// A ScopeKey used to indicate whether the current execution is within a unit test.
///
/// This key is injected when running a unit test, allowing DCli code to be
/// 'unit test' aware and modify its behavior to be unit test friendly.
/// The default value is false, indicating that by default, the code is not
/// running in a unit test environment.
///
/// Usage:
/// - When set to true, it signals that the code is running within a unit test.
/// - DCli functions can check this key to adjust their behavior accordingly.
static final unitTestingKey =
ScopeKey<bool>.withDefault(false, 'Running in a unit test');
/// Executes the provided action within a unit test context.
///
/// This method creates a new [Scope] where the [unitTestingKey] is set to true,
/// indicating that the code is running within a unit test environment. It then
/// executes the provided [action] within this scope.
///
/// Certain DCli functions modify their behavior when run within a unit test.
/// They rely on this scope to determine if they are in a unit test environment.
///
/// Usage:
/// ```dart
/// await UnitTestController.withUnitTest(() {
/// // Your unit test code here
/// });
/// ```
///
/// Parameters:
/// - action: A void function that contains the code to be executed within the unit test context.
///
/// Returns:
/// A [Future] that completes when the action has finished executing.
static Future<void> withUnitTest(void Function() action) async {
final scope = Scope()..value(unitTestingKey, true);
await scope.run(() async => action());
}
}

View file

@ -10,7 +10,8 @@ environment:
# Add regular dependencies here.
dependencies:
# path: ^1.8.0
path: ^1.9.0
scope: ^4.1.0
dev_dependencies:
lints: ^3.0.0