diff --git a/packages/console/lib/common.dart b/packages/console/lib/common.dart deleted file mode 100644 index 757bd68..0000000 --- a/packages/console/lib/common.dart +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * 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'; diff --git a/packages/console/lib/core.dart b/packages/console/lib/core.dart deleted file mode 100644 index bc40d64..0000000 --- a/packages/console/lib/core.dart +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -library; - -export 'src/functions/backup.dart'; -export 'src/functions/cat.dart'; -export 'src/functions/copy.dart'; // show copy, CopyException; -export 'src/functions/copy_tree.dart' show CopyTreeException, copyTree; -export 'src/functions/create_dir.dart' - show CreateDirException, createDir, createTempDir, withTempDirAsync; -export 'src/functions/create_dir.dart'; -export 'src/functions/dcli_function.dart'; -export 'src/functions/delete.dart' show DeleteException, delete; -export 'src/functions/delete_dir.dart' show DeleteDirException, deleteDir; -export 'src/functions/env.dart' - show - Env, - HOME, - PATH, - env, - envs, - isOnPATH, - withEnvironment, - withEnvironmentAsync; -export 'src/functions/find.dart'; -export 'src/functions/find_async.dart'; -export 'src/functions/head.dart'; -export 'src/functions/is.dart'; -export 'src/functions/move.dart' show MoveException, move; -export 'src/functions/move_dir.dart' show MoveDirException, moveDir; -export 'src/functions/move_tree.dart'; -export 'src/functions/pwd.dart' show pwd; -export 'src/functions/tail.dart'; -export 'src/functions/touch.dart'; -export 'src/functions/which.dart' show Which, WhichSearch, which; -export 'src/settings.dart'; -export 'src/utils/dcli_exception.dart'; -export 'src/utils/dcli_platform.dart'; -export 'src/utils/dev_null.dart'; -export 'src/utils/file.dart'; -export 'src/utils/limited_stream_controller.dart'; -export 'src/utils/line_action.dart'; -export 'src/utils/line_file.dart'; -export 'src/utils/platform.dart'; -export 'src/utils/run_exception.dart'; -export 'src/utils/stack_list.dart'; -export 'src/utils/truepath.dart' show privatePath, rootPath, truepath; diff --git a/packages/console/lib/input.dart b/packages/console/lib/input.dart deleted file mode 100644 index e18e997..0000000 --- a/packages/console/lib/input.dart +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -library; - -export 'src/input/ask.dart'; -export 'src/input/confirm.dart'; -export 'src/input/echo.dart'; -export 'src/input/menu.dart'; diff --git a/packages/console/lib/src/.gitkeep b/packages/console/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/console/lib/src/common/determine_paths.dart b/packages/console/lib/src/common/determine_paths.dart deleted file mode 100644 index f296798..0000000 --- a/packages/console/lib/src/common/determine_paths.dart +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * 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/ -/// absolute/ -/// -/// On Windows to accomodate drive letters we need a slightly -/// different directory structure -/// relative/ -/// absolute// -/// -/// 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; -} diff --git a/packages/console/lib/src/common/unit_test_controller.dart b/packages/console/lib/src/common/unit_test_controller.dart deleted file mode 100644 index 2e6604b..0000000 --- a/packages/console/lib/src/common/unit_test_controller.dart +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * 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.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 withUnitTest(void Function() action) async { - final scope = Scope()..value(unitTestingKey, true); - await scope.run(() async => action()); - } -} diff --git a/packages/console/lib/src/functions/backup.dart b/packages/console/lib/src/functions/backup.dart deleted file mode 100644 index 2a5fb48..0000000 --- a/packages/console/lib/src/functions/backup.dart +++ /dev/null @@ -1,321 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:path/path.dart'; -import 'package:protevus_console/common.dart'; -import 'package:protevus_console/core.dart'; - -/// Provide a very simple mechanism to backup a single file. -/// -/// The backup is placed in '.bak' subdirectory under the passed -/// [pathToFile]'s directory. -/// -/// Be cautious that you don't nest backups of the same file -/// in your code as we always use the same backup target. -/// Instead use [withFileProtectionAsync]. -/// -/// We also renamed the backup to '.bak' to ensure -/// the backupfile doesn't interfere with dev tools -/// (e.g. we don't want an extra pubspec.yaml hanging about) -/// -/// If a file at [pathToFile] doesn't exist then a [BackupFileException] -/// is thrown unless you pass the [ignoreMissing] flag. -/// -/// See: [restoreFile] -/// [withFileProtectionAsync] -/// -void backupFile(String pathToFile, {bool ignoreMissing = false}) { - if (!exists(pathToFile)) { - throw BackupFileException( - 'The backup file ${truepath(pathToFile)} is missing', - ); - } - final pathToBackupFile = _backupFilePath(pathToFile); - if (exists(pathToBackupFile)) { - delete(pathToBackupFile); - } - if (!exists(dirname(pathToBackupFile))) { - createDir(dirname(pathToBackupFile)); - } - - verbose(() => 'Backing up ${truepath(pathToFile)}'); - copy(pathToFile, pathToBackupFile); -} - -/// Designed to work with [backupFile] to restore -/// a file from backup. -/// The existing file is deleted and restored -/// from the .bak/.bak file created when -/// you called [backupFile]. -/// -/// Consider using [withFileProtectionAsync] for a more robust solution. -/// -/// When the last .bak file is restored, the .bak directory -/// will be deleted. If you don't restore all files (your app crashes) -/// then a .bak directory and files may be left hanging around and you may -/// need to manually restore these files. -/// If the backup file doesn't exists this function throws -/// a [RestoreFileException] unless you pass the [ignoreMissing] -/// flag. -void restoreFile(String pathToFile, {bool ignoreMissing = false}) { - final pathToBackupFile = _backupFilePath(pathToFile); - - if (exists(pathToBackupFile)) { - if (exists(pathToFile)) { - delete(pathToFile); - } - - move(pathToBackupFile, pathToFile); - - if (isEmpty(dirname(pathToBackupFile))) { - deleteDir(dirname(pathToBackupFile)); - } - verbose(() => 'Restoring ${truepath(pathToFile)}'); - } else { - if (ignoreMissing) { - verbose( - () => 'Missing restoreFile ${truepath(pathToBackupFile)} ignored.', - ); - } else { - throw RestoreFileException( - 'The backup file ${truepath(pathToBackupFile)} is missing', - ); - } - } -} - -/// EXPERIMENTAL - use with caution and the api may change. -/// -/// Allows you to nominate a list of files to be backed up -/// before an operation commences -/// and then restored once the operation completes. -/// -/// Currently the files a protected by making a copy of each file/directory -/// into a unique system temp directory and then moved back once the -/// [action] has completed. -/// - -/// -/// [withFileProtectionAsync] is safe to use in a nested fashion as each call -/// to [withFileProtectionAsync] creates its own separate backup area. -/// -/// If the VM aborts during execution of the [action] you will find -/// the backed up files in the system temp directory under a directory named -/// .withFileProtection'. You may need to use the time stamp to determine which -/// directory is the right one if you have had mulitple failures. -/// Under normal circumstances the temp directory is delete once the action -/// completes. -/// -/// The [protected] list can contain files, directories. -/// -/// If the entry is a directory then all children (files and directories) -/// are protected. -/// -/// Entries in the [protected] list may be relative or absolute. -/// -/// If [protected] contains a file or directory that doesn't exist -/// and the [action] subsequently creates those entities, then those files -/// and/or directories will be deleted after [action] completes. -/// -/// This function can be useful for doing dry-run operations -/// where you need to ensure the filesystem is restore to its -/// prior state after the dry-run completes. -/// -// ignore: flutter_style_todos -/// TODO(bsutton): make this work for other than current drive under Windows -/// -Future withFileProtectionAsync( - List protected, - Future Function() action, { - String? workingDirectory, -}) async { - // removed glob support for the moment. - // This is because if one of the protected entries is missing - // then we are assuming its a glob. - // We should probably change to accepting a Pattern - // and the have the user pass an actual Glob. - // Problem with this is that find uses a subset of Glob. - // so for the moment, no glob support - // a glob pattern as supported by the [find] command. - // We only support searching for files by the glob pattern (not directories). - // If the entry is a glob pattern then it is applied recusively. - - final workingDirectory0 = workingDirectory ?? pwd; - final result = await withTempDirAsync( - (backupDir) async { - verbose(() => 'withFileProtection: backing up to $backupDir'); - - /// backup the protected files - /// to a backupDir - for (final path in protected) { - final paths = determinePaths( - path: path, - workingDirectory: workingDirectory0, - backupDir: backupDir, - ); - - if (!exists(paths.sourcePath)) { - /// the file/directory doesn't exist. - /// During the restore process this path will be deleted - /// so that once again they don't exist. - continue; - } - - if (isFile(paths.sourcePath)) { - if (!exists(dirname(paths.backupPath))) { - createDir(dirname(paths.backupPath), recursive: true); - } - - /// the entity is a simple file. - copy(paths.sourcePath, paths.backupPath); - } else if (isDirectory(paths.sourcePath)) { - /// the entity is a directory so copy the whole tree - /// recursively. - if (!exists(paths.backupPath)) { - createDir(paths.backupPath, recursive: true); - } - copyTree(paths.sourcePath, paths.backupPath, includeHidden: true); - } else { - throw BackupFileException( - 'Unsupported entity type for ${paths.sourcePath}. ' - 'Only files and directories are supported', - ); - } - // else { - // /// Must be a glob. - // for (final file in find(paths.source, includeHidden: true) - // .toList()) { - // // we need to determine the paths for each [file] - // // as the can have a different relative path as we - // // do a recursive search. - // final paths = _determinePaths( - // path: file, sourceDir: sourceDir, backupDir: backupDir); - - // if (!exists(dirname(paths.target))) { - // createDir(dirname(paths.target), recursive: true); - // } - // copy(paths.source, paths.target); - // } - // } - } - final result = await action(); - - /// restore the protected entities - for (final path in protected) { - final paths = determinePaths( - path: path, - workingDirectory: workingDirectory0, - backupDir: backupDir, - ); - { - if (!exists(paths.backupPath)) { - /// If the protected entity didn't exist before we started - /// the make certain it doesn't exist now. - _deleteEntity(paths.sourcePath); - } - - if (isFile(paths.backupPath)) { - await _restoreFile(paths); - } - - if (isDirectory(paths.backupPath)) { - _restoreDirectory(paths); - } - } - } - - return result; - }, - keep: true, - ); - - return result; -} - -Future _restoreFile(Paths paths) async { - await withTempFileAsync( - (dotBak) async { - try { - if (exists(paths.sourcePath)) { - move(paths.sourcePath, dotBak); - } - - // ignore: flutter_style_todos - /// TODO(bsutton): consider only restoring the file if its last modified - /// time has changed. - move(paths.backupPath, paths.sourcePath); - if (exists(dotBak)) { - delete(dotBak); - } - // ignore: avoid_catches_without_on_clauses - } catch (e) { - /// The restore failed so if the dotBak file - /// exists lets at least restore that. - if (exists(dotBak)) { - /// this should never happen as if we have the dotBak - /// file then the originalFile should not exists. - /// but just in case. - if (exists(paths.sourcePath)) { - delete(paths.sourcePath); - } - move(dotBak, paths.sourcePath); - } - } - }, - create: false, - ); -} - -String _backupFilePath(String pathToFile) { - final sourcePath = dirname(pathToFile); - final destPath = join(sourcePath, '.bak'); - final filename = basename(pathToFile); - - return '${join(destPath, filename)}.bak'; -} - -/// Thrown by the [restoreFile] function when -/// the backup file is missing. -class RestoreFileException extends DCliException { - /// Creates a [RestoreFileException] with the given - /// message. - RestoreFileException(super.message); -} - -/// Thrown by the [backupFile] function when -/// the file to be backed up is missing. -class BackupFileException extends DCliException { - /// Creates a [BackupFileException] with the given - /// message. - BackupFileException(super.message); -} - -void _restoreDirectory(Paths paths) { - /// For directories we just recreate them if necessary. - /// This allows us to restore empty directories. - if (exists(paths.sourcePath)) { - deleteDir(paths.sourcePath); - } - createDir(paths.sourcePath, recursive: true); - - /// The find command will return all of the nested files so - /// we don't need to restore them when we see the directory. - moveTree(paths.backupPath, paths.sourcePath, includeHidden: true); -} - -void _deleteEntity(String path) { - if (isFile(path)) { - delete(path); - } else if (isDirectory(path)) { - deleteDir(path); - } else { - verbose(() => 'Path is of unsuported type'); - } -} diff --git a/packages/console/lib/src/functions/cat.dart b/packages/console/lib/src/functions/cat.dart deleted file mode 100644 index b992954..0000000 --- a/packages/console/lib/src/functions/cat.dart +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:protevus_console/core.dart'; - -/// Prints the contents of the file located at [path] to stdout. -/// -/// ```dart -/// cat("/var/log/syslog"); -/// ``` -/// -/// If the file does not exists then a CatException is thrown. -/// -void cat(String path, {LineAction stdout = print}) => - Cat().cat(path, stdout: stdout); - -/// Class for the [cat] function. -class Cat extends DCliFunction { - /// implementation for the [cat] function. - void cat(String path, {LineAction stdout = print}) { - verbose(() => 'cat: ${truepath(path)}'); - - if (!exists(path)) { - throw CatException('The file at ${truepath(path)} does not exists'); - } - - LineFile(path).readAll((line) { - stdout(line); - return true; - }); - } -} - -/// Thrown if the [cat] function encouters an error. -class CatException extends DCliFunctionException { - /// Thrown if the [cat] function encouters an error. - CatException(super.reason, [super.stacktrace]); -} diff --git a/packages/console/lib/src/functions/copy.dart b/packages/console/lib/src/functions/copy.dart deleted file mode 100644 index 42f6d9a..0000000 --- a/packages/console/lib/src/functions/copy.dart +++ /dev/null @@ -1,96 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * 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'; - -import 'package:protevus_console/core.dart'; - -/// -/// Copies the file [from] to the path [to]. -/// -/// ```dart -/// copy("/tmp/fred.text", "/tmp/fred2.text", overwrite=true); -/// ``` -/// -/// [to] may be a directory in which case the [from] filename is -/// used to construct the [to] files full path. -/// -/// If [to] is a file then the file must not exist unless [overwrite] -/// is set to true. -/// -/// If [to] is a directory then the directory must exist. -/// -/// If [from] is a symlink we copy the file it links to rather than -/// the symlink. This mimics the behaviour of gnu 'cp' command. -/// -/// If you need to copy the actualy symlink see [symlink]. -/// -/// The default for [overwrite] is false. -/// -/// If an error occurs a [CopyException] is thrown. - -void copy(String from, String to, {bool overwrite = false}) { - var finalto = to; - if (isDirectory(finalto)) { - finalto = join(finalto, basename(from)); - } - verbose(() => - 'copy ${truepath(from)} -> ${truepath(finalto)} overwrite: $overwrite'); - - if (overwrite == false && exists(finalto, followLinks: false)) { - throw CopyException( - 'The target file ${truepath(finalto)} already exists.', - ); - } - - try { - /// if we are copying a symlink then we copy the file rather than - /// the symlink as this mimicks gnu 'cp'. - if (isLink(from)) { - final resolvedFrom = resolveSymLink(from); - File(resolvedFrom).copySync(finalto); - } else { - File(from).copySync(finalto); - } - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - /// lets try and improve the message. - /// We do these checks only on failure - /// so in the most common case (everything is correct) - /// we don't waste cycles on unnecessary work. - if (isDirectory(from)) { - throw CopyException( - "The 'from' argument ${truepath(from)} is a directory. " - 'Use copyTree instead.'); - } - if (!exists(from)) { - throw CopyException("The 'from' file ${truepath(from)} does not exists."); - } - if (!exists(dirname(to))) { - throw CopyException( - "The 'to' directory ${truepath(dirname(to))} does not exists.", - ); - } - - throw CopyException( - 'An error occured copying ${truepath(from)} to ${truepath(finalto)}. ' - 'Error: $e', - ); - } -} - -/// Throw when the [copy] function encounters an error. -class CopyException extends DCliFunctionException { - /// Throw when the [copy] function encounters an error. - CopyException(super.reason); -} diff --git a/packages/console/lib/src/functions/copy_tree.dart b/packages/console/lib/src/functions/copy_tree.dart deleted file mode 100644 index 673e4a9..0000000 --- a/packages/console/lib/src/functions/copy_tree.dart +++ /dev/null @@ -1,161 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:path/path.dart'; -import 'package:protevus_console/core.dart'; - -/// -/// Copies the contents of the [from] directory to the -/// [to] path with an optional filter. -/// -/// The [to] path must exist. -/// -/// If any copied file already exists in the [to] path then -/// an exeption is throw and a parital copyTree may occur. -/// -/// You can force the copyTree to overwrite files in the [to] -/// directory by setting [overwrite] to true (defaults to false). -/// -/// The [recursive] argument controls whether subdirectories are -/// copied. If [recursive] is true (the default) it will copy -/// subdirectories. -/// -/// -/// ```dart -/// copyTree("/tmp/", "/tmp/new_dir", overwrite:true); -/// ``` -/// By default hidden files are ignored. To allow hidden files to -/// be processed set [includeHidden] to true. -/// -/// You can select which files are to be copied by passing a [filter]. -/// If a [filter] isn't passed then all files are copied as per -/// the [includeHidden] state. -/// -/// ```dart -/// copyTree("/tmp/", "/tmp/new_dir", overwrite:true, includeHidden:true -/// , filter: (file) => extension(file) == 'dart'); -/// ``` -/// -/// The [filter] method can also be used to report progress as it -/// is called just before we copy a file. -/// -/// ```dart -/// copyTree("/tmp/", "/tmp/new_dir", overwrite:true -/// , filter: (file) { -/// var include = extension(file) == 'dart'; -/// if (include) { -/// print('copying: $file'); -/// } -/// return include; -/// }); -/// ``` -/// -/// -/// The default for [overwrite] is false. -/// -/// If an error occurs a [CopyTreeException] is thrown. -void copyTree( - String from, - String to, { - bool overwrite = false, - bool includeHidden = false, - bool recursive = true, - bool Function(String file) filter = _allowAll, -}) => - _CopyTree().copyTree( - from, - to, - overwrite: overwrite, - includeHidden: includeHidden, - filter: filter, - recursive: recursive, - ); - -bool _allowAll(String file) => true; - -class _CopyTree extends DCliFunction { - void copyTree( - String from, - String to, { - bool overwrite = false, - bool Function(String file) filter = _allowAll, - bool includeHidden = false, - bool recursive = true, - }) { - verbose(() => 'copyTree: from: $from, to: $to, overwrite: $overwrite ' - 'includeHidden: $includeHidden recursive: $recursive '); - if (!isDirectory(from)) { - throw CopyTreeException( - 'The [from] path ${truepath(from)} must be a directory.', - ); - } - if (!exists(to)) { - throw CopyTreeException( - 'The [to] path ${truepath(to)} must already exist.', - ); - } - - if (!isDirectory(to)) { - throw CopyTreeException( - 'The [to] path ${truepath(to)} must be a directory.', - ); - } - - try { - find('*', - workingDirectory: from, - includeHidden: includeHidden, - recursive: recursive, progress: (item) { - _process(item.pathTo, filter, from, to, - overwrite: overwrite, recursive: recursive); - return true; - }); - verbose( - () => 'copyTree copied: ${truepath(from)} -> ${truepath(to)}, ' - 'includeHidden: $includeHidden, recursive: $recursive, ' - 'overwrite: $overwrite', - ); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw CopyTreeException( - 'An error occured copying directory' - ' ${truepath(from)} to ${truepath(to)}. ' - 'Error: $e', - ); - } - } - - void _process( - String file, bool Function(String file) filter, String from, String to, - {required bool overwrite, required bool recursive}) { - if (filter(file)) { - final target = join(to, relative(file, from: from)); - - if (recursive && !exists(dirname(target))) { - createDir(dirname(target), recursive: true); - } - - if (!overwrite && exists(target)) { - throw CopyTreeException( - 'The target file ${truepath(target)} already exists.', - ); - } - - copy(file, target, overwrite: overwrite); - } - } -} - -/// Throw when the [copy] function encounters an error. -class CopyTreeException extends DCliFunctionException { - /// Throw when the [copy] function encounters an error. - CopyTreeException(super.reason); -} diff --git a/packages/console/lib/src/functions/create_dir.dart b/packages/console/lib/src/functions/create_dir.dart deleted file mode 100644 index 11bd26e..0000000 --- a/packages/console/lib/src/functions/create_dir.dart +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * 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'; -import 'package:uuid/uuid.dart'; -import 'package:protevus_console/core.dart'; - -/// Creates a directory as described by [path]. -/// Path may be a single path segment (e.g. bin) -/// or a full or partial tree (e.g. /usr/bin) -/// -/// ```dart -/// createDir("/tmp/fred/tools", recursive: true); -/// ``` -/// -/// If [recursive] is true then any parent -/// paths that don't exist will be created. -/// -/// If [recursive] is false then any parent paths -/// don't exist then a [CreateDirException] will be thrown. -/// -/// If the [path] already exists an exception is thrown. -/// -/// As a convenience [createDir] returns the same path -/// that it was passed. -/// -/// ```dart -/// var path = createDir('/tmp/new_home')); -/// ``` -/// - -String createDir(String path, {bool recursive = false}) => - _CreateDir().createDir(path, recursive: recursive); - -/// Creates a temp directory and then calls [action]. -/// Once action completes the temporary directory will be deleted. -/// -/// The actions return value [R] is returned from the [withTempDirAsync] -/// function. -/// -/// If you pass [keep] = true then the temp directory won't be deleted. -/// This can be useful when testing and you need to examine the temp directory. -/// -/// You can optionally pass in your own tempDir via [pathToTempDir]. -/// This can be useful when sometimes you need to control the tempDir -/// and sometimes you want it created. -/// If you pass in [pathToTempDir] it will NOT be deleted regardless -/// of the value of [keep]. -Future withTempDirAsync(Future Function(String tempDir) action, - {bool keep = false, String? pathToTempDir}) async { - final dir = pathToTempDir ?? createTempDir(); - - R result; - try { - result = await action(dir); - } finally { - if (!keep && pathToTempDir == null) { - deleteDir(dir); - } - } - return result; -} - -/// Creates a temporary directory under the system temp folder. -/// -/// The temporary directory name is formed from a uuid. -/// It is your responsiblity to delete the directory once you have -/// finsihed with it. -String createTempDir() => _CreateDir().createDir( - join(Directory.systemTemp.path, '.dclitmp', const Uuid().v4()), - recursive: true, - ); - -class _CreateDir extends DCliFunction { - String createDir(String path, {required bool recursive}) { - verbose(() => 'createDir: ${truepath(path)} recursive: $recursive'); - - try { - if (exists(path)) { - throw CreateDirException('The path ${truepath(path)} already exists'); - } - - Directory(path).createSync(recursive: recursive); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw CreateDirException( - 'Unable to create the directory ${truepath(path)}. Error: $e', - ); - } - return path; - } -} - -/// Thrown when the function [createDir] encounters an error -class CreateDirException extends DCliFunctionException { - /// Thrown when the function [createDir] encounters an error - CreateDirException(super.reason); -} diff --git a/packages/console/lib/src/functions/dcli_function.dart b/packages/console/lib/src/functions/dcli_function.dart deleted file mode 100644 index 4375d0a..0000000 --- a/packages/console/lib/src/functions/dcli_function.dart +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:protevus_console/core.dart'; - -/// Base class for the classes that implement -/// the public DCli functions. -class DCliFunction {} - -/// Base class for all dcli function exceptions. -class DCliFunctionException extends DCliException { - /// Base class for all dcli function exceptions. - DCliFunctionException(super.message, [super.stackTrace]); -} diff --git a/packages/console/lib/src/functions/delete.dart b/packages/console/lib/src/functions/delete.dart deleted file mode 100644 index 04d095c..0000000 --- a/packages/console/lib/src/functions/delete.dart +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'package:protevus_console/core.dart'; - -/// -/// Deletes the file at [path]. -/// -/// If the file does not exists a DeleteException is thrown. -/// -/// ```dart -/// delete("/tmp/test.fred", ask: true); -/// ``` -/// -/// If the [path] is a directory a DeleteException is thrown. -void delete(String path) => _Delete().delete(path); - -class _Delete extends DCliFunction { - void delete(String path) { - verbose(() => 'delete: ${truepath(path)}'); - - if (!exists(path)) { - throw DeleteException('The path ${truepath(path)} does not exists.'); - } - - if (isDirectory(path)) { - throw DeleteException('The path ${truepath(path)} is a directory.'); - } - - try { - File(path).deleteSync(); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw DeleteException( - 'An error occured deleting ${truepath(path)}. Error: $e', - ); - } - } -} - -/// Thrown when the [delete] function encounters an error -class DeleteException extends DCliFunctionException { - /// Thrown when the [delete] function encounters an error - DeleteException(super.reason); -} diff --git a/packages/console/lib/src/functions/delete_dir.dart b/packages/console/lib/src/functions/delete_dir.dart deleted file mode 100644 index 6ba44d4..0000000 --- a/packages/console/lib/src/functions/delete_dir.dart +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; -import 'package:protevus_console/core.dart'; - -/// -/// Deletes the directory located at [path]. -/// -/// If [recursive] is true (default true) then the directory and all child files -/// and directories will be deleted. -/// -/// ```dart -/// deleteDir("/tmp/testing", recursive=true); -/// ``` -/// -/// If [path] is not a directory then a [DeleteDirException] is thrown. -/// -/// If the directory does not exists a [DeleteDirException] is thrown. -/// -/// If the directory cannot be delete (e.g. permissions) a -/// [DeleteDirException] is thrown. -/// -/// If recursive is false the directory must be empty otherwise a -/// [DeleteDirException] is thrown. -/// -/// See [isDirectory] -/// [exists] -/// -void deleteDir(String path, {bool recursive = true}) => - _DeleteDir().deleteDir(path, recursive: recursive); - -class _DeleteDir extends DCliFunction { - void deleteDir(String path, {required bool recursive}) { - verbose(() => 'deleteDir: ${truepath(path)} recursive: $recursive'); - - if (!exists(path)) { - throw DeleteDirException('The path ${truepath(path)} does not exist.'); - } - - if (!isDirectory(path)) { - throw DeleteDirException( - 'The path ${truepath(path)} is not a directory.', - ); - } - - try { - Directory(path).deleteSync(recursive: recursive); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw DeleteDirException( - 'Unable to delete the directory ${truepath(path)}. Error: $e', - ); - } - } -} - -/// Throw when [deleteDir] function encounters an error -class DeleteDirException extends DCliFunctionException { - /// Throw when [deleteDir] function encounters an error - DeleteDirException(super.reason); -} diff --git a/packages/console/lib/src/functions/delete_tree.dart b/packages/console/lib/src/functions/delete_tree.dart deleted file mode 100644 index b9082cd..0000000 --- a/packages/console/lib/src/functions/delete_tree.dart +++ /dev/null @@ -1,135 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; -import 'package:protevus_console/core.dart'; - -/// Recursively deletes the contents of the directory located at [path] -/// with an optional filter. The directory at [path] is not deleted. -/// -/// -/// [path] must be a directory and must exist. -/// -/// ```dart -/// deleteTree(join(rootPath, 'tmp'))); -/// ``` -/// -/// Pass a filter to control what is deleted. Only files/directories -/// that match the filter will be deleted. -/// -/// ```dart -/// deleteTree(join(rootPath, 'tmp') -/// , filter: (type, path) => extension(file) == 'dart'); -/// ``` -/// -/// The [filter] method can also be used to report progress as it -/// is called just before we move a file or directory. -/// -/// ```dart -/// deleteTree(join(rootPath, 'tmp') -/// , filter: (entity) { -/// var delete = extension(entity) == 'dart'; -/// if (delete) { -/// print('deleting: $file'); -/// } -/// return delete; -/// }); -/// ``` -/// -/// -/// If an error occurs a [DeleteTreeException] is thrown. -/// -/// EXPERIMENTAL -void deleteTree( - String path, { - bool Function(FileSystemEntityType type, String file) filter = _deleteAll, -}) => - _DeleteTree().deleteTree( - path, - filter: filter, - ); - -bool _deleteAll(FileSystemEntityType type, String file) => true; - -class _DeleteTree extends DCliFunction { - void deleteTree( - String path, { - bool Function(FileSystemEntityType type, String file) filter = _deleteAll, - }) { - if (!exists(path)) { - throw DeleteTreeException( - 'The [path] ${truepath(path)} does not exist.', - ); - } - if (!isDirectory(path)) { - throw DeleteTreeException( - 'The [path] ${truepath(path)} is not a directory.', - ); - } - - verbose(() => 'deleteTree called ${truepath(path)}'); - try { - find('*', - workingDirectory: path, - includeHidden: true, - types: [Find.file, Find.directory, Find.link], progress: (item) { - _process(item.pathTo, filter, item.type); - return true; - }); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw DeleteTreeException( - 'An error occured deleting directory ${truepath(path)}. ' - 'Error: $e', - ); - } - } - - void _process( - String pathToFile, - bool Function(FileSystemEntityType type, String file) filter, - FileSystemEntityType type, - ) { - if (filter(type, pathToFile)) { - // we create directories as we go. - // only directories that contain a file that is to be - // moved will be created. - // ignore: exhaustive_cases - switch (type) { - case FileSystemEntityType.directory: - { - deleteDir(pathToFile); - break; - } - case FileSystemEntityType.file: - { - delete(pathToFile); - break; - } - case FileSystemEntityType.link: - { - deleteSymlink(pathToFile); - break; - } - } - - verbose( - () => 'deleteTree delting: ${truepath(pathToFile)}', - ); - } - } -} - -/// Thrown when the [deleteTree] function encouters an error. -class DeleteTreeException extends DCliFunctionException { - /// Thrown when the [deleteTree] function encouters an error. - DeleteTreeException(super.reason); -} diff --git a/packages/console/lib/src/functions/env.dart b/packages/console/lib/src/functions/env.dart deleted file mode 100644 index 8adef80..0000000 --- a/packages/console/lib/src/functions/env.dart +++ /dev/null @@ -1,428 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:convert'; -import 'dart:io'; - -import 'package:collection/collection.dart'; -import 'package:path/path.dart'; -import 'package:scope/scope.dart'; - -// ignore: unused_import -import '../settings.dart'; -import '../utils/dcli_exception.dart'; -import '../utils/truepath.dart'; -import 'dcli_function.dart'; - -/// Provides access to shell environment variables. -Env get env => Env(); - -/// Tests if the given [path] is contained -/// in the OS's PATH environment variable. -/// An canonicalized match of [path] is made against -/// each path on the OS's path. -bool isOnPATH(String path) => Env().isOnPATH(path); - -/// Returns the list of directory paths that are contained -/// in the OS's PATH environment variable. -/// They are returned in the same order that they appear within -/// the PATH environment variable (as order is important.) -//ignore: non_constant_identifier_names -List get PATH => Env()._path; - -/// returns the path to the OS specific HOME directory -//ignore: non_constant_identifier_names -String get HOME => Env().HOME; - -/// Returns a map of all the environment variables -/// inherited from the parent as well as any changes -/// made by calls to [env[]=]. -/// -/// See [env[]] -/// [env[]=] -Map get envs => Env()._envVars; - -/// -/// Sets gets an environment variable for the current process. -/// -/// Passing a null value will remove the key from the -/// set of environment variables. -/// -/// Any child process spawned will inherit these changes. -/// e.g. -/// ``` -/// /// Set the environment variable 'XXX' to 'A Value' -/// env['XXX'] = 'A Value'; -/// -/// // the echo command will display the value off XXX. -/// '''echo $XXX'''.run; -/// -/// /// Get the current value of an environment variable -/// var xxx = env['XXX']; -/// -/// ``` -/// NOTE: this does NOT affect the parent -/// processes environment. -/// - -/// Implementation class for the functions [env[]] and [env[]=]. -class Env extends DCliFunction { - /// Implementation class for the functions [env[]] and [env[]=]. - /// Returns a singleton unless we are running in a [Scope] - /// and a [scopeKey] for [Env] has been placed into the scope. - factory Env() { - if (Scope.hasScopeKey(scopeKey)) { - return Scope.use(scopeKey); - } else { - return _self ??= Env._internal(); - } - } - - /// Use this ctor for injecting an altered Environment - /// into a Scope. - /// The main use for this ctor is for unit testing. - factory Env.forScope(Map map) { - final env = Env._internal(); - - map.forEach((key, value) { - env[key] = value; - }); - return env; - } - - Env._internal() : _caseSensitive = !Settings().isWindows { - final platformVars = Platform.environment; - - _envVars = - CanonicalizedMap((key) => _caseSensitive ? key : key.toUpperCase()); - - // build a local map with all of the OS environment vars. - for (final entry in platformVars.entries) { - _envVars.putIfAbsent(entry.key, () => entry.value); - } - } - - static ScopeKey scopeKey = ScopeKey(); - static Env? _self = Env._internal(); - - late Map _envVars; - - final bool _caseSensitive; - - /// Returns true if environment variable keys are case sensitive. - /// All OSs are case sensitive except for Windows - bool get caseSensitive => _caseSensitive; - - String? _env(String name) { - verbose(() => 'env: $name:${_envVars[name]}'); - - return _envVars[_caseSensitive ? name : name.toUpperCase()]; - } - - /// Returns the complete set of Environment variable entries. - Iterable> get entries => _envVars.entries; - - /// Adds all of the entries in the [other] map as environment variables. - /// Case translation will occur if the platform is case sensitive. - void addAll(Map other) { - for (final entry in other.entries) { - _setEnv(entry.key, entry.value); - } - } - - /// Returns true if an environment variable with the name - /// [key] exists. - bool exists(String key) => _envVars.keys.contains(key); - - /// returns the PATH environment var as an ordered - /// array of the paths contained in the PATH. - /// The list is ordered Left to right of the paths in - /// the PATH environment var. - List get _path { - final pathEnv = this['PATH'] ?? ''; - - return pathEnv - .split(delimiterForPATH) - // on linux an empty path equates to the current directory. - // .where((value) => value.trim().isNotEmpty) - .toList(); - } - - /// Returns the value of an environment variable. - /// - /// name of the environment variable. - /// - /// On posix systems name of the environment variable is case sensitive. - /// - /// - ///```dart - ///String path = env["PATH"]; - ///``` - /// - String? operator [](String name) => _env(name); - - /// Sets the value of an environment variable - /// ```dart - /// env["PASS"] = 'mypassword'; - /// ``` - /// - void operator []=(String name, String? value) => _setEnv(name, value); - - /// Appends [newPath] to the list of paths in the - /// PATH environment variable. - /// - /// If [newPath] is already in PATH no action is taken. - /// - /// Changing the PATH has no affect on the parent - /// process (shell) that launched this script. - /// - /// Changing the path affects the current script - /// and any children that it spawns. - /// - /// See: [prependToPATH] - /// [removeFromPATH] - void appendToPATH(String newPath) { - if (!isOnPATH(newPath)) { - final path = PATH..add(newPath); - _setEnv('PATH', path.join(delimiterForPATH)); - } - } - - /// Prepends [newPath] to the list of paths in the - /// PATH environment variable provided the - /// path isn't already on the PATH. - /// - /// If [newPath] is already in PATH no action is taken. - /// - /// Changing the PATH has no affect on the parent - /// process (shell) that launched this script. - /// - /// Changing the path affects the current script - /// and any children that it spawns. - /// - /// See: [appendToPATH] - /// [removeFromPATH] - void prependToPATH(String newPath) { - if (!isOnPATH(newPath)) { - final path = PATH..insert(0, newPath); - _setEnv('PATH', path.join(delimiterForPATH)); - } - } - - /// Removes the given [oldPath] from the PATH environment variable. - /// - /// Changing the PATH has no affect on the parent - /// process (shell) that launched this script. - /// - /// Changing the path affects the current script - /// and any children that it spawns. - /// - /// See: [appendToPATH] - /// [prependToPATH] - void removeFromPATH(String oldPath) { - final path = PATH..remove(oldPath); - _setEnv('PATH', path.join(delimiterForPATH)); - } - - /// Adds [newPath] to the PATH environment variable - /// if it is not already present. - /// - /// The [newPath] will be added to the end of the PATH list. - /// - /// Changing the PATH has no affect on the parent - /// process (shell) that launched this script. - /// - /// Changing the PATH affects the current script - /// and any children that it spawns. - @Deprecated('Use appendToPATH') - void addToPATHIfAbsent(String newPath) => appendToPATH(newPath); - - /// - /// Gets the path to the user's home directory - /// using the enviornment var appropriate for the user's OS. - //ignore: non_constant_identifier_names - String get HOME { - String? home; - - if (Settings().isWindows) { - home = _env('APPDATA'); - } else { - home = _env('HOME'); - } - - if (home == null) { - if (Settings().isWindows) { - throw DCliException( - "Unable to find the 'APPDATA' enviroment variable. " - 'Ensure it is set and try again.', - ); - } else { - throw DCliException( - "Unable to find the 'HOME' enviroment variable. " - 'Ensure it is set and try again.', - ); - } - } - return home; - } - - /// returns true if the given [checkPath] is in the list - /// of paths defined in the environment variable [PATH]. - bool isOnPATH(String checkPath) { - final canon = canonicalize(truepath(checkPath)); - var found = false; - for (final path in _path) { - if (canonicalize(truepath(path)) == canon) { - found = true; - break; - } - } - return found; - } - - /// Passing a null [value] will remove the key from the - /// set of environment variables. - void _setEnv(String name, String? value) { - verbose(() => 'env[$name] = $value'); - if (value == null) { - _envVars.remove(name); - if (Settings().isWindows) { - if (name == 'HOME' || name == 'APPDATA') { - _envVars - ..remove('HOME') - ..remove('APPDATA'); - } - } - } else { - _envVars[name] = value; - - if (Settings().isWindows) { - if (name == 'HOME' || name == 'APPDATA') { - _envVars['HOME'] = value; - _envVars['APPDATA'] = value; - } - } - } - } - - /// returns the delimiter used by the PATH enviorment variable. - /// - /// On linix it is ':' ond windows it is ';' - /// - /// NOTE do NOT confuses this with the file system path root!!! - /// - String get delimiterForPATH { - var separator = ':'; - - if (Settings().isWindows) { - separator = ';'; - } - return separator; - } - - /// Encodes all environment variables to a json string. - /// This method is intended to be used in conjuction with - /// [fromJson]. - /// - /// You will find this method useful when spawning an isolate - /// that depends on environment variables created by calls - /// to DCli [env] property. - /// - /// When creating an isolate it takes its environment variables - /// from [Platform.environment]. This means that any environment - /// variables created via DCli will not be visible to the isolate. - /// - /// The way to over come this problem is to call [Env().toJson()] - /// pass the resulting string to the isolate and then have the - /// isolate call [Env().fromJson()] which resets the isolates - /// environment variables. - /// - /// ```dart - /// Future startIsolate() async { - /// var iso = await IsolateRunner.spawn(); - /// - /// try { - /// iso.run(scheduler, Env().toJson()); - /// } finally { - /// await iso.close(); - /// } - ///} - /// - /// // This method runs in the new isolate. - /// void scheduler(String jsonEnvironment) { - /// Env().fromJson(jsonEnvironment); - /// Certbot().scheduleRenews(); - /// } - /// ``` - String toJson() { - final envMap = {}..addEntries(env.entries.toSet()); - return JsonEncoder(_toEncodable).convert(envMap); - } - - /// Takes a json string created by [toJson] - /// clears the current set of environment variables - /// and replaces them with the environment variables - /// encoded in the json string. - /// - /// If you choose not to use [toJson] to create the json - /// then [json ] must be in form of an json encoded Map. - void fromJson(String json) { - _envVars.clear(); - env.addAll( - Map.from( - const JsonDecoder().convert(json) as Map, - ), - ); - } - - String _toEncodable(Object? object) => object.toString(); -} - -/// Injects environment variables into the scope -/// of the [callback] method. -/// -/// The passed [environment] map is merged with the current [env] and -/// injected into the [callback]'s scope. -/// -/// Note: code that access [Platform.environment] directly -/// will not see the environment variables injected via -/// this method. You must use the dcli [env] variable. -/// -/// Any changes to [env] within the scope of the callback -/// are only visible inside that scope and revert once [callback] -/// returns. -/// This is particularly useful for unit tests and running -/// a process that requires specific environment variables. -Future withEnvironmentAsync(Future Function() callback, - {required Map environment}) async { - final existing = Env()._envVars; - return (Scope() - ..value(Env.scopeKey, Env.forScope(existing)..addAll(environment))) - .run(() async => callback()); -} - -/// Injects environment variables into the scope -/// of the [callback] method. -/// You must [withEnvironmentAsync] if the callback is async. -/// -/// See [withEnvironmentAsync] for general details -R withEnvironment(R Function() callback, - {required Map environment}) { - final existing = Env()._envVars; - return (Scope() - ..value(Env.scopeKey, Env.forScope(existing)..addAll(environment))) - .runSync(() => callback()); -} - -/// Base class for all Environment variable related exceptions. -class EnvironmentException extends DCliException { - /// Create an environment variable exception. - EnvironmentException(super.message); -} diff --git a/packages/console/lib/src/functions/find.dart b/packages/console/lib/src/functions/find.dart deleted file mode 100644 index a5015a9..0000000 --- a/packages/console/lib/src/functions/find.dart +++ /dev/null @@ -1,564 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * 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'; - -import 'package:protevus_console/core.dart'; - -// TODO(bsutton): investigate if we need to restore the -// [LimitedStreamController] -// typedef FindController = LimitedStreamController; -typedef ProgressCallback = bool Function(FindItem item); - -/// -/// Returns the list of files in the current and child -/// directories that match the passed glob pattern. -/// -/// Each file is returned as an absolute path. -/// -/// You can obtain a relative path by calling: -/// ```dart -/// var relativePath = relative(filePath, from: searchRoot); -/// ``` -/// -/// Note: this is a limited implementation of glob. -/// See the below notes for details. -/// -/// ```dart -/// find('*.jpg', recursive:true).forEach((file) => print(file)); -/// -/// List results = find('[a-z]*.jpg', caseSensitive:true).toList(); -/// -/// find('*.jpg' -/// , types:[Find.directory, Find.file]) -/// .forEach((file) => print(file)); -/// ``` -/// -/// Valid patterns are: -/// ``` -/// -/// [*] - matches any number of any characters including none. -/// -/// [?] - matches any single character -/// -/// [[abc]] - matches any one character given in the bracket -/// -/// [[a-z]] - matches one character from the range given in the bracket -/// -/// [[!abc]] - matches one character that is not given in the bracket -/// -/// [[!a-z]] - matches one character that is not from the range given -/// in the bracket -/// ``` -/// -/// If [caseSensitive] is true then a case sensitive match is performed. -/// [caseSensitive] defaults to false. -/// -/// If [recursive] is true then a recursive search of all subdirectories -/// (all the way down) is performed. -/// [recursive] is true by default. -/// -/// [includeHidden] controls whether hidden files (.xx) are returned and -/// whether hidden directorys (.xx) are recursed into when the [recursive] -/// option is true. By default hidden files and directories are ignored. -/// If the wildcard begins with a '.' then includeHidden will be enabled -/// automatically. -/// -/// [types] allows you to specify the file types you want the find to return. -/// By default [types] limits the results to files. -/// -/// [workingDirectory] allows you to specify an alternate d -/// irectory to seach within -/// rather than the current work directory. -/// -/// [types] the list of types to search file. Defaults to [Find.file]. -/// See [Find.file], [Find.directory], [Find.link]. -/// -/// Passing a [progress] will allow you to process the results as the are -/// produced rather than having to wait for the call to find to complete. -/// The passed progress is also returned. -/// If the [progress] doesn't output [stdout] then you will get no results -/// back. -/// -// TODO(bsutton): consider having find return a Stream and eliminate passing -/// a controller in. -void find( - String pattern, { - required ProgressCallback progress, - bool caseSensitive = false, - bool recursive = true, - bool includeHidden = false, - String workingDirectory = '.', - List types = const [Find.file], -}) => - Find()._find( - pattern, - caseSensitive: caseSensitive, - recursive: recursive, - includeHidden: includeHidden, - workingDirectory: workingDirectory, - progress: progress, - types: types, - ); - -/// Implementation for the [_find] function. -class Find extends DCliFunction { - final bool _closed = false; - - /// Find matching files an call [progress] for each one. - void _find( - String pattern, { - required ProgressCallback progress, - bool caseSensitive = false, - bool recursive = true, - String workingDirectory = '.', - List types = const [Find.file], - bool includeHidden = false, - }) { - final config = FindConfig.build( - pattern: pattern, - workingDirectory: workingDirectory, - includeHidden: includeHidden, - caseSensitive: caseSensitive); - - _innerFind( - config: config, - recursive: recursive, - progress: progress, - types: types, - ); - } - - void _innerFind({ - required FindConfig config, - required ProgressCallback progress, - bool recursive = true, - List types = const [Find.file], - }) { - verbose( - () => 'find: pwd: $pwd ' - 'workingDirectory: ${truepath(config.workingDirectory)} ' - 'pattern: ${config.pattern} caseSensitive: ${config.caseSensitive} ' - 'recursive: $recursive types: $types ', - ); - final nextLevel = List.filled(100, null, growable: true); - final singleDirectory = - List.filled(100, null, growable: true); - final childDirectories = - List.filled(100, null, growable: true); - if (!_processDirectory( - config.workingDirectory, - config.workingDirectory, - recursive, - types, - config.matcher, - config.includeHidden, - progress, - childDirectories, - )) { - return; - } - while (childDirectories[0] != null) { - _zeroElements(nextLevel); - for (final directory in childDirectories) { - if (directory == null) { - break; - } - // print('calling _processDirectory ${count++}'); - if (!_processDirectory( - config.workingDirectory, - directory.path, - recursive, - types, - config.matcher, - config.includeHidden, - progress, - singleDirectory, - )) { - break; - } - _appendTo(nextLevel, singleDirectory); - _zeroElements(singleDirectory); - } - _copyInto(childDirectories, nextLevel); - } - } - - bool _processDirectory( - String workingDirectory, - String currentDirectory, - bool recursive, - List types, - PatternMatcher matcher, - bool includeHidden, - ProgressCallback progress, - List nextLevel, - ) { - // print('process Directory ${dircount++}'); - final list = Directory(currentDirectory).listSync(followLinks: false); - - var nextLevelIndex = 0; - - for (final entity in list) { - try { - late final FileSystemEntityType type; - type = FileSystemEntity.typeSync(entity.path, followLinks: false); - - if (types.contains(type) && - matcher.match(entity.path) && - _allowed( - workingDirectory, - entity, - includeHidden: includeHidden, - )) { - if (_closed) { - return false; - } - - /// If the controller has been paused or hasn't yet been - /// listened to then we don't want to add files to - /// it otherwise we may run out of memory. - if (!progress(FindItem(entity.path, type))) { - return false; - } - } - - /// If we are recursing then we need to add any directories - /// to the list of childDirectories that need to be recursed. - if (recursive && type == Find.directory) { - if (nextLevel.length > nextLevelIndex) { - nextLevel[nextLevelIndex] = entity; - } else { - nextLevel.add(entity); - } - nextLevelIndex++; - } - // ignore: avoid_catches_without_on_clauses - } catch (e) { - if (_isGeneralIOError(e)) { - /// can mean a corrupt disk, problems with virtualisation - /// I've seen this when gdrive. - } else if (e is FileSystemException && - e.osError?.errorCode == _accessDenied) { - /// check for and ignore permission denied. - verbose(() => 'Permission denied: ${e.path}'); - } else if (e is FileSystemException && e.osError?.errorCode == 40) { - /// ignore recursive symbolic link problems. - verbose(() => 'Too many levels of symbolic links: ${e.path}'); - } else if (e is FileSystemException && e.osError?.errorCode == 22) { - /// Invalid argument - not really certain what this means but we get - /// it when processing a .steam folder that includes a windows - /// emulator. - verbose(() => 'Invalid argument: ${e.path}'); - } else if (e is FileSystemException && - e.osError?.errorCode == _directoryNotFound) { - /// The directory may have been deleted between us finding it and - /// processing it. - verbose( - () => 'File or Directory deleted whilst we were processing it:' - ' ${e.path}', - ); - } else { - // ignore: only_throw_errors - rethrow; - } - } - } - return true; - } - - int get _accessDenied => Settings().isWindows ? 5 : 13; - int get _directoryNotFound => Settings().isWindows ? 3 : 2; - - /// Checks if a hidden file is allowed. - /// Non-hidden files are always allowed. - bool _allowed( - String workingDirectory, - FileSystemEntity entity, { - required bool includeHidden, - }) => - includeHidden || !_isHidden(workingDirectory, entity); - - // check if the entity is a hidden file (.xxx) or - // if lives in a hidden directory. - bool _isHidden(String workingDirectory, FileSystemEntity entity) { - final relativePath = relative(entity.path, from: workingDirectory); - - final parts = relativePath.split(separator); - - var isHidden = false; - for (final part in parts) { - if (part.startsWith('.')) { - isHidden = true; - break; - } - } - return isHidden; - } - - /// set all elements in the array to null so we can re-use the list - /// to reduce GC. - void _zeroElements(List nextLevel) { - for (var i = 0; i < nextLevel.length && nextLevel[i] != null; i++) { - nextLevel[i] = null; - } - } - - void _copyInto( - List childDirectories, - List nextLevel, - ) { - _zeroElements(childDirectories); - for (var i = 0; i < nextLevel.length; i++) { - if (childDirectories.length > i) { - childDirectories[i] = nextLevel[i]; - } else { - childDirectories.add(nextLevel[i]); - } - } - } - - void _appendTo( - List nextLevel, - List singleDirectory, - ) { - var index = _firstAvailable(nextLevel); - - for (var i = 0; i < singleDirectory.length; i++) { - if (singleDirectory[i] == null) { - break; - } - if (index >= nextLevel.length) { - nextLevel.add(singleDirectory[i]); - index++; - } else { - nextLevel[index++] = singleDirectory[i]; - } - } - } - - int _firstAvailable(List nextLevel) { - var firstAvailable = 0; - while (firstAvailable < nextLevel.length && - nextLevel[firstAvailable] != null) { - firstAvailable++; - } - return firstAvailable; - } - - /// pass as a value to the find types argument - /// to select files to be found - static const file = FileSystemEntityType.file; - - /// pass as a value to the final types argument - /// to select directories to be found - static const directory = FileSystemEntityType.directory; - - /// pass as a value to the final types argument - /// to select links to be found - static const link = FileSystemEntityType.link; - - bool _isGeneralIOError(Object e) { - var error = false; - error = e is FileSystemException && - !Platform.isWindows && - e.osError?.errorCode == 5; - - if (error) { - verbose(() => 'General IO Error(5) accessing: ${e.path}'); - } - - return error; - } -} - -class PatternMatcher { - PatternMatcher( - this.pattern, { - required this.workingDirectory, - required this.caseSensitive, - }) { - regEx = buildRegEx(); - - final patternParts = split(dirname(pattern)); - var count = patternParts.length; - if (patternParts.length == 1 && patternParts[0] == '.') { - count = 0; - } - directoryParts = count; - } - - String pattern; - String workingDirectory; - late RegExp regEx; - bool caseSensitive; - - /// the no. of directories in the pattern - late final int directoryParts; - - bool match(String path) { - final matchPart = _extractMatchPart(path); - // print('path: $path, matchPart: $matchPart pattern: $pattern'); - return regEx.stringMatch(matchPart) == matchPart; - } - - RegExp buildRegEx() { - var regEx = ''; - - for (var i = 0; i < pattern.length; i++) { - final char = pattern[i]; - - switch (char) { - case '[': - regEx += '['; - break; - case ']': - regEx += ']'; - break; - case '*': - regEx += '.*'; - break; - case '?': - regEx += '.'; - break; - case '-': - regEx += '-'; - break; - case '!': - regEx += '^'; - break; - case '.': - regEx += r'\.'; - break; - case r'\': - regEx += r'\\'; - break; - default: - regEx += char; - break; - } - } - return RegExp(regEx, caseSensitive: caseSensitive); - } - - /// A pattern may contain a relative path in which case - /// we need to match [path] with the same no. of directories - /// as is contained in the pattern. - /// - /// This method extracts the components of a absolute [path] - /// that must be used when doing the pattern match. - String _extractMatchPart(String path) { - if (directoryParts == 0) { - return basename(path); - } - - final pathParts = split(dirname(relative(path, from: workingDirectory))); - - var partsCount = pathParts.length; - if (pathParts.length == 1 && pathParts[0] == '.') { - partsCount = 0; - } - - /// If the path doesn't have enough parts then just - /// return the path relative to the workingDirectory. - if (partsCount < directoryParts) { - return relative(path, from: workingDirectory); - } - - /// return just the required parts. - return joinAll( - [...pathParts.sublist(partsCount - directoryParts), basename(path)], - ); - } -} - -//typedef FindProgress = Future Function(String path); -//typedef FindProgress = Sink(); - -/// Holds details of a file system entity returned by the -/// [find] function. -class FindItem { - /// [pathTo] is the path to the file system entity - /// [type] is the type of file system entity. - FindItem(this.pathTo, this.type); - - /// the path to the file system entity - String pathTo; - - /// type of file system entity - FileSystemEntityType type; - - @override - String toString() => pathTo; -} - -/// Thrown when the [find] function encouters an error. -class FindException extends DCliFunctionException { - /// Thrown when the [move] function encouters an error. - FindException(super.reason); -} - -class FindConfig { - factory FindConfig.build( - {required String pattern, - required String workingDirectory, - required bool includeHidden, - required bool caseSensitive}) { - /// strip any path components out of the pattern - /// and add them to the working directory. - /// If there is no dirname component we get '.' - final directoryPart = dirname(pattern); - if (directoryPart != '.') { - workingDirectory = join(workingDirectory, directoryPart); - } - pattern = basename(pattern); - - if (!exists(workingDirectory)) { - throw FindException( - 'The path ${truepath(workingDirectory)} does not exists', - ); - } - - final matcher = PatternMatcher( - pattern, - caseSensitive: caseSensitive, - workingDirectory: workingDirectory, - ); - if (workingDirectory == '.') { - workingDirectory = pwd; - } else { - workingDirectory = truepath(workingDirectory); - } - - if (basename(pattern).startsWith('.')) { - includeHidden = true; - } - - return FindConfig._( - workingDirectory: workingDirectory, - pattern: pattern, - includeHidden: includeHidden, - caseSensitive: caseSensitive, - matcher: matcher); - } - FindConfig._( - {required this.workingDirectory, - required this.pattern, - required this.includeHidden, - required this.caseSensitive, - required this.matcher}); - String workingDirectory; - String pattern; - bool includeHidden; - bool caseSensitive; - PatternMatcher matcher; -} diff --git a/packages/console/lib/src/functions/find_async.dart b/packages/console/lib/src/functions/find_async.dart deleted file mode 100644 index 49077c6..0000000 --- a/packages/console/lib/src/functions/find_async.dart +++ /dev/null @@ -1,369 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:async'; -import 'dart:io'; - -import 'package:path/path.dart'; - -import 'package:protevus_console/core.dart'; - -/// -/// Returns the list of files in the current and child -/// directories that match the passed glob pattern as a Stream -/// of absolute paths. -/// -/// You can obtain a relative path by calling: -/// ```dart -/// var relativePath = relative(filePath, from: searchRoot); -/// ``` -/// -/// Note: this is a limited implementation of glob. -/// See the below notes for details. -/// -/// ```dart -/// await for (final file in find('*.jpg', recursive:true)) -/// print(file); -/// -/// List results = findAsync('[a-z]*.jpg', caseSensitive:true).toList(); -/// -/// await for (final file in find('*.jpg', types:[Find.directory, Find.file]) -/// print(file); -/// ``` -/// -/// Valid patterns are: -/// ``` -/// -/// [*] - matches any number of any characters including none. -/// -/// [?] - matches any single character -/// -/// [[abc]] - matches any one character given in the bracket -/// -/// [[a-z]] - matches one character from the range given in the bracket -/// -/// [[!abc]] - matches one character that is not given in the bracket -/// -/// [[!a-z]] - matches one character that is not from the range given -/// in the bracket -/// ``` -/// -/// If [caseSensitive] is true then a case sensitive match is performed. -/// [caseSensitive] defaults to false. -/// -/// If [recursive] is true then a recursive search of all subdirectories -/// (all the way down) is performed. -/// [recursive] is true by default. -/// -/// [includeHidden] controls whether hidden files (.xx) are returned and -/// whether hidden directorys (.xx) are recursed into when the [recursive] -/// option is true. By default hidden files and directories are ignored. -/// If the wildcard begins with a '.' then includeHidden will be enabled -/// automatically. -/// -/// [types] allows you to specify the file types you want the find to return. -/// By default [types] limits the results to files. -/// -/// [workingDirectory] allows you to specify an alternate d -/// irectory to seach within -/// rather than the current work directory. -/// -/// [types] the list of types to search file. Defaults to [Find.file]. -/// See [Find.file], [Find.directory], [Find.link]. -/// -Stream findAsync( - String pattern, { - bool caseSensitive = false, - bool recursive = true, - bool includeHidden = false, - String workingDirectory = '.', - List types = const [Find.file], -}) async* { - // We us a [LimitedStreamController] as a slow reader - // can cause an out of memory exception if we keep pumping - // more files into the stream. - // ignore: close_sinks - final controller = LimitedStreamController(100); - await FindAsync()._findAsync( - pattern, - caseSensitive: caseSensitive, - recursive: recursive, - includeHidden: includeHidden, - workingDirectory: workingDirectory, - controller: controller, - types: types, - ); - - yield* controller.stream; -} - -/// Implementation for the [_findAsync] function. -class FindAsync extends DCliFunction { - final bool _closed = false; - - /// Find matching files and return them as a stream - Future _findAsync( - String pattern, { - required LimitedStreamController controller, - bool caseSensitive = false, - bool recursive = true, - String workingDirectory = '.', - List types = const [Find.file], - bool includeHidden = false, - }) async { - final config = FindConfig.build( - pattern: pattern, - workingDirectory: workingDirectory, - includeHidden: includeHidden, - caseSensitive: caseSensitive); - - await _innerFindAsync( - config: config, - recursive: recursive, - controller: controller, - types: types, - ); - } - - Future _innerFindAsync({ - required FindConfig config, - required LimitedStreamController controller, - bool recursive = true, - List types = const [Find.file], - }) async { - verbose( - () => 'find: pwd: $pwd ' - 'workingDirectory: ${truepath(config.workingDirectory)} ' - 'pattern: ${config.pattern} caseSensitive: ${config.caseSensitive} ' - 'recursive: $recursive types: $types ', - ); - final nextLevel = List.filled(100, null, growable: true); - final singleDirectory = - List.filled(100, null, growable: true); - final childDirectories = - List.filled(100, null, growable: true); - - if (!await _processDirectory( - config, - config.workingDirectory, - recursive, - types, - controller, - childDirectories, - )) { - return; - } - while (childDirectories[0] != null) { - _zeroElements(nextLevel); - for (final directory in childDirectories) { - if (directory == null) { - break; - } - // print('calling _processDirectory ${count++}'); - if (!await _processDirectory( - config, - directory.path, - recursive, - types, - controller, - singleDirectory, - )) { - break; - } - _appendTo(nextLevel, singleDirectory); - _zeroElements(singleDirectory); - } - _copyInto(childDirectories, nextLevel); - } - unawaited(controller.close()); - } - - Future _processDirectory( - FindConfig config, - String currentDirectory, - bool recursive, - List types, - LimitedStreamController controller, - List nextLevel, - ) async { - // print('process Directory ${dircount++}'); - - var nextLevelIndex = 0; - - await for (final entity - in Directory(currentDirectory).list(followLinks: false)) { - try { - late final FileSystemEntityType type; - type = FileSystemEntity.typeSync(entity.path, followLinks: false); - - if (types.contains(type) && - config.matcher.match(entity.path) && - _allowed( - config.workingDirectory, - entity, - includeHidden: config.includeHidden, - )) { - if (_closed) { - return false; - } - - // TODO(bsutton): do we need to wait if the controller is - /// paused? - await controller.asyncAdd(FindItem(entity.path, type)); - } - - /// If we are recursing then we need to add any directories - /// to the list of childDirectories that need to be recursed. - if (recursive && type == Find.directory) { - if (nextLevel.length > nextLevelIndex) { - nextLevel[nextLevelIndex] = entity; - } else { - nextLevel.add(entity); - } - nextLevelIndex++; - } - // ignore: avoid_catches_without_on_clauses - } catch (e) { - if (_isGeneralIOError(e)) { - /// can mean a corrupt disk, problems with virtualisation - /// I've seen this when gdrive. - } else if (e is FileSystemException && - e.osError?.errorCode == _accessDenied) { - /// check for and ignore permission denied. - verbose(() => 'Permission denied: ${e.path}'); - } else if (e is FileSystemException && e.osError?.errorCode == 40) { - /// ignore recursive symbolic link problems. - verbose(() => 'Too many levels of symbolic links: ${e.path}'); - } else if (e is FileSystemException && e.osError?.errorCode == 22) { - /// Invalid argument - not really certain what this means but we get - /// it when processing a .steam folder that includes a windows - /// emulator. - verbose(() => 'Invalid argument: ${e.path}'); - } else if (e is FileSystemException && - e.osError?.errorCode == _directoryNotFound) { - /// The directory may have been deleted between us finding it and - /// processing it. - verbose( - () => 'File or Directory deleted whilst we were processing it:' - ' ${e.path}', - ); - } else { - // ignore: only_throw_errors - rethrow; - } - } - } - return true; - } - - int get _accessDenied => Settings().isWindows ? 5 : 13; - int get _directoryNotFound => Settings().isWindows ? 3 : 2; - - /// Checks if a hidden file is allowed. - /// Non-hidden files are always allowed. - bool _allowed( - String workingDirectory, - FileSystemEntity entity, { - required bool includeHidden, - }) => - includeHidden || !_isHidden(workingDirectory, entity); - - // check if the entity is a hidden file (.xxx) or - // if lives in a hidden directory. - bool _isHidden(String workingDirectory, FileSystemEntity entity) { - final relativePath = relative(entity.path, from: workingDirectory); - - final parts = relativePath.split(separator); - - var isHidden = false; - for (final part in parts) { - if (part.startsWith('.')) { - isHidden = true; - break; - } - } - return isHidden; - } - - /// set all elements in the array to null so we can re-use the list - /// to reduce GC. - void _zeroElements(List nextLevel) { - for (var i = 0; i < nextLevel.length && nextLevel[i] != null; i++) { - nextLevel[i] = null; - } - } - - void _copyInto( - List childDirectories, - List nextLevel, - ) { - _zeroElements(childDirectories); - for (var i = 0; i < nextLevel.length; i++) { - if (childDirectories.length > i) { - childDirectories[i] = nextLevel[i]; - } else { - childDirectories.add(nextLevel[i]); - } - } - } - - void _appendTo( - List nextLevel, - List singleDirectory, - ) { - var index = _firstAvailable(nextLevel); - - for (var i = 0; i < singleDirectory.length; i++) { - if (singleDirectory[i] == null) { - break; - } - if (index >= nextLevel.length) { - nextLevel.add(singleDirectory[i]); - index++; - } else { - nextLevel[index++] = singleDirectory[i]; - } - } - } - - int _firstAvailable(List nextLevel) { - var firstAvailable = 0; - while (firstAvailable < nextLevel.length && - nextLevel[firstAvailable] != null) { - firstAvailable++; - } - return firstAvailable; - } - - /// pass as a value to the find types argument - /// to select files to be found - static const file = FileSystemEntityType.file; - - /// pass as a value to the final types argument - /// to select directories to be found - static const directory = FileSystemEntityType.directory; - - /// pass as a value to the final types argument - /// to select links to be found - static const link = FileSystemEntityType.link; - - bool _isGeneralIOError(Object e) { - var error = false; - error = e is FileSystemException && - !Platform.isWindows && - e.osError?.errorCode == 5; - - if (error) { - verbose(() => 'General IO Error(5) accessing: ${e.path}'); - } - - return error; - } -} diff --git a/packages/console/lib/src/functions/head.dart b/packages/console/lib/src/functions/head.dart deleted file mode 100644 index 69bc072..0000000 --- a/packages/console/lib/src/functions/head.dart +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:protevus_console/core.dart'; - -/// -/// Returns count [lines] from the file at [path]. -/// -/// ```dart -/// head('/var/log/syslog', 10).forEach((line) => print(line)); -/// ``` -/// -/// Throws a [HeadException] exception if [path] is not a file. -/// -List head(String path, int lines) => _Head().head(path, lines); - -class _Head extends DCliFunction { - List head( - String path, - int lines, - ) { - verbose(() => 'head ${truepath(path)} lines: $lines'); - - if (!exists(path)) { - throw HeadException('The path ${truepath(path)} does not exist.'); - } - - if (!isFile(path)) { - throw HeadException('The path ${truepath(path)} is not a file.'); - } - - try { - return withOpenLineFile(path, (file) { - final result = []; - file.readAll((line) { - result.add(line); - return result.length < lines; - }); - return result; - }); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw HeadException( - 'An error occured reading ${truepath(path)}. Error: $e', - ); - } finally {} - } -} - -/// Thrown if the [head] function encounters an error. -class HeadException extends DCliFunctionException { - /// Thrown if the [head] function encounters an error. - HeadException(super.reason); -} diff --git a/packages/console/lib/src/functions/is.dart b/packages/console/lib/src/functions/is.dart deleted file mode 100644 index fe21206..0000000 --- a/packages/console/lib/src/functions/is.dart +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'package:stack_trace/stack_trace.dart'; - -import 'package:protevus_console/core.dart'; - -// import 'package:posix/posix.dart' as posix; - -/// -/// Returns true if the given [path] points to a file. -/// -/// ```dart -/// isFile("~/fred.jpg"); -/// ``` -bool isFile(String path) => _Is().isFile(path); - -/// Returns true if the given [path] is a directory. -/// ```dart -/// isDirectory("/tmp"); -/// -/// ``` -bool isDirectory(String path) => _Is().isDirectory(path); - -/// Returns true if the given [path] is a symlink -/// -/// // ```dart -/// isLink("~/fred.jpg"); -/// ``` -bool isLink(String path) => _Is().isLink(path); - -/// Returns true if the given path exists. -/// It may be a file, directory or link. -/// -/// If [followLinks] is true (the default) then [exists] -/// will return true if the resolved path exists. -/// -/// If [followLinks] is false then [exists] will return -/// true if path exist, whether its a link or not. -/// -/// ```dart -/// if (exists("/fred.txt")) -/// ``` -/// -/// Throws [ArgumentError] if [path] is null or an empty string. -/// -/// See [isLink] -/// [isDirectory] -/// [isFile] -bool exists(String path, {bool followLinks = true}) => - _Is().exists(path, followLinks: followLinks); - -/// Returns the datetime the path was last modified -/// -/// [path[ can be either a file or a directory. -/// -/// Throws a [DCliException] with a nested -/// [FileSystemException] if the file does not -/// exist or the operation fails. -DateTime lastModified(String path) { - try { - return File(path).lastModifiedSync(); - } on FileSystemException catch (e) { - throw DCliException.from(e, Trace.current()); - } -} - -/// Sets the last modified datetime on the given the path. -/// -/// [path] can be either a file or a directory. -/// -/// Throws a [DCliException] with a nested -/// [FileSystemException] if the file does not -/// exist or the operation fails. - -void setLastModifed(String path, DateTime lastModified) { - try { - File(path).setLastModifiedSync(lastModified); - } on FileSystemException catch (e) { - throw DCliException.from(e, Trace.current()); - } -} - -/// Returns true if the passed [pathToDirectory] is an -/// empty directory. -/// For large directories this operation can be expensive. -bool isEmpty(String pathToDirectory) => _Is().isEmpty(pathToDirectory); - -class _Is extends DCliFunction { - bool isFile(String path) { - final fromType = FileSystemEntity.typeSync(path); - return fromType == FileSystemEntityType.file; - } - - /// true if the given path is a directory. - bool isDirectory(String path) { - final fromType = FileSystemEntity.typeSync(path); - return fromType == FileSystemEntityType.directory; - } - - bool isLink(String path) { - final fromType = FileSystemEntity.typeSync(path, followLinks: false); - return fromType == FileSystemEntityType.link; - } - - /// checks if the given [path] exists. - /// - /// Throws [ArgumentError] if [path] is an empty string. - bool exists(String path, {bool followLinks = true}) { - if (path.isEmpty) { - throw ArgumentError('path must not be empty.'); - } - - final exists = FileSystemEntity.typeSync(path, followLinks: followLinks) != - FileSystemEntityType.notFound; - - verbose( - () => - 'exists(${truepath(path)}) found: $exists followLinks: $followLinks', - ); - - return exists; - } - - /// checks if the given [path] exists. - /// - /// Throws [ArgumentError] if [path] is an empty string. - bool existsSync(String path, {required bool followLinks}) { - if (path.isEmpty) { - throw ArgumentError('path must not be empty.'); - } - - final exists = FileSystemEntity.typeSync(path, followLinks: followLinks) != - FileSystemEntityType.notFound; - - verbose( - () => - 'exists(${truepath(path)}) found: $exists followLinks: $followLinks', - ); - - return exists; - } - - DateTime lastModified(String path) => File(path).lastModifiedSync(); - - void setLastModifed(String path, DateTime lastModified) { - File(path).setLastModifiedSync(lastModified); - } - - /// Returns true if the passed [pathToDirectory] is an - /// empty directory. - /// For large directories this operation can be expensive. - bool isEmpty(String pathToDirectory) { - final empty = - Directory(pathToDirectory).listSync(followLinks: false).isEmpty; - verbose(() => 'isEmpty(${truepath(pathToDirectory)}) : $empty'); - - return empty; - } -} diff --git a/packages/console/lib/src/functions/move.dart b/packages/console/lib/src/functions/move.dart deleted file mode 100644 index ed52f8f..0000000 --- a/packages/console/lib/src/functions/move.dart +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * 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'; - -import 'package:protevus_console/core.dart'; - -/// -/// Moves the file [from] to the location [to]. -/// -/// ```dart -/// createDir('/tmp/folder'); -/// move('/tmp/fred.txt', '/tmp/folder/tom.txt'); -/// ``` -/// [from] must be a file. -/// -/// [to] may be a file or a path. -/// -/// If [to] is a file then a rename occurs. -/// -/// if [to] is a path then [from] is moved to the given path. -/// -/// If the move fails for any reason a [MoveException] is thrown. -/// - -void move(String from, String to, {bool overwrite = false}) { - verbose( - () => 'move ${truepath(from)} -> ${truepath(to)} overwrite: $overwrite'); - - var dest = to; - - if (isDirectory(to)) { - dest = p.join(to, p.basename(from)); - } - - if (!overwrite && exists(dest)) { - throw MoveException( - 'The [to] path ${truepath(dest)} already exists.' - ' Use overwrite:true ', - ); - } - try { - File(from).renameSync(dest); - } on FileSystemException catch (_) { - /// Invalid cross-device link - /// We can't move files across a partition so - /// do a copy/delete. - copy(from, to, overwrite: overwrite); - delete(from); - } - - /// ignore: avoid_catches_without_on_clauses - catch (e) { - _improveError(e, from, to); - } -} - -/// We try to improve the OS error message, because its crap. -void _improveError(Object e, String from, String to) { - if (!exists(from)) { - throw MoveException( - 'The Move of ${truepath(from)} failed as it does not exist.', - ); - } else if (!exists(dirname(truepath(to)))) { - throw MoveException( - 'The Move of ${truepath(from)} failed as the target directory ' - '${truepath(dirname(to))} does not exist.', - ); - } else { - throw MoveException( - 'The Move of ${truepath(from)} to ${truepath(to)} failed. Error $e', - ); - } -} - -/// Thrown when the [move] function encouters an error. -class MoveException extends DCliFunctionException { - /// Thrown when the [move] function encouters an error. - MoveException(super.reason); -} diff --git a/packages/console/lib/src/functions/move_dir.dart b/packages/console/lib/src/functions/move_dir.dart deleted file mode 100644 index 86c823a..0000000 --- a/packages/console/lib/src/functions/move_dir.dart +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'package:protevus_console/core.dart'; - -/// Moves or renames the [from] directory to the -/// to the [to] path. -/// -/// The [to] path must NOT exist. -/// -/// The [from] path must be a directory. -/// -/// [moveDir] first tries to rename the directory, if that -/// fails due to the [to] path being on a different device -/// we fall back to a copy/delete operation. -/// -/// ```dart -/// moveDir("/tmp/", "/tmp/new_dir"); -/// ``` -/// -/// Throws a [MoveDirException] if: -/// the [from] path doesn't exist -/// the [from] path isn't a directory -/// the [to] path already exists. -/// -void moveDir(String from, String to) => _MoveDir().moveDir( - from, - to, - ); - -class _MoveDir extends DCliFunction { - void moveDir(String from, String to) { - if (!exists(from)) { - throw MoveDirException( - 'The [from] path ${truepath(from)} does not exists.', - ); - } - if (!isDirectory(from)) { - throw MoveDirException( - 'The [from] path ${truepath(from)} must be a directory.', - ); - } - if (exists(to)) { - throw MoveDirException('The [to] path ${truepath(to)} must NOT exist.'); - } - - verbose(() => 'moveDir called ${truepath(from)} -> ${truepath(to)}'); - - try { - Directory(from).renameSync(to); - } on FileSystemException catch (_) { - /// Most likley an Invalid cross-device move. - /// We can't move files across a partition so - /// do a copy/delete. - verbose( - () => - 'rename failed so falling back to copy/delete: ${truepath(from)} -> ${truepath(to)}', - ); - - copyTree(from, to, includeHidden: true); - delete(from); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw MoveDirException( - 'The Move of ${truepath(from)} to ${truepath(to)} failed. Error $e', - ); - } - } -} - -/// Thrown when the [moveDir] function encouters an error. -class MoveDirException extends DCliFunctionException { - /// Thrown when the [moveDir] function encouters an error. - MoveDirException(super.reason); -} diff --git a/packages/console/lib/src/functions/move_tree.dart b/packages/console/lib/src/functions/move_tree.dart deleted file mode 100644 index cf6ebb2..0000000 --- a/packages/console/lib/src/functions/move_tree.dart +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:path/path.dart'; - -import 'package:protevus_console/core.dart'; - -/// Recursively moves the contents of the [from] directory to the -/// to the [to] path with an optional filter. -/// -/// When filtering any files that don't match the filter will be -/// left in the [from] directory tree. -/// -/// Any [from] directories that are emptied as a result of the move will -/// be removed. This includes the [from] directory itself. -/// -/// [from] must be a directory -/// -/// [to] must be a directory and its parent directory must exist. -/// -/// If any moved files already exists in the [to] path then -/// an exeption is throw and a parital move may occured. -/// -/// You can force moveTree to overwrite files in the [to] -/// directory by setting [overwrite] to true (defaults to false). -/// -/// -/// ```dart -/// moveTree("/tmp/", "/tmp/new_dir", overwrite: true); -/// ``` -/// -/// By default hidden files are ignored. To allow hidden files to -/// be passed set [includeHidden] to true. -/// -/// You can select which files/directories are to be moved by passing a [filter]. -/// If a [filter] isn't passed then all files/directories are copied as per -/// the [includeHidden] state. -/// -/// ```dart -/// moveTree("/tmp/", "/tmp/new_dir", overwrite: true -/// , filter: (file) => extension(file) == 'dart'); -/// ``` -/// -/// The [filter] method can also be used to report progress as it -/// is called just before we move a file or directory. -/// -/// ```dart -/// moveTree("/tmp/", "/tmp/new_dir", overwrite: true -/// , filter: (entity) { -/// var include = extension(entity) == 'dart'; -/// if (include) { -/// print('moving: $file'); -/// } -/// return include; -/// }); -/// ``` -/// -/// -/// The default for [overwrite] is false. -/// -/// If an error occurs a [MoveTreeException] is thrown. -/// -/// EXPERIMENTAL -void moveTree( - String from, - String to, { - bool overwrite = false, - bool includeHidden = false, - bool Function(String file) filter = _allowAll, -}) => - _MoveTree().moveTree( - from, - to, - overwrite: overwrite, - includeHidden: includeHidden, - filter: filter, - ); - -bool _allowAll(String file) => true; - -class _MoveTree extends DCliFunction { - void moveTree( - String from, - String to, { - bool overwrite = false, - bool Function(String file) filter = _allowAll, - bool includeHidden = false, - }) { - if (!isDirectory(from)) { - throw MoveTreeException( - 'The [from] path ${truepath(from)} must be a directory.', - ); - } - if (!exists(to)) { - throw MoveTreeException( - 'The [to] path ${truepath(to)} must already exist.', - ); - } - - if (!isDirectory(to)) { - throw MoveTreeException( - 'The [to] path ${truepath(to)} must be a directory.', - ); - } - - verbose(() => 'moveTree called ${truepath(from)} -> ${truepath(to)}'); - - try { - find('*', workingDirectory: from, includeHidden: includeHidden, - progress: (item) { - _process(item.pathTo, filter, to, from, overwrite: overwrite); - return true; - }); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw MoveTreeException( - 'An error occured moving directory ${truepath(from)} ' - 'to ${truepath(to)}. Error: $e', - ); - } - } - - void _process(String pathToFile, bool Function(String file) filter, String to, - String from, - {required bool overwrite}) { - if (filter(pathToFile)) { - final target = join(to, relative(pathToFile, from: from)); - - // we create directories as we go. - // only directories that contain a file that is to be - // moved will be created. - if (isDirectory(dirname(pathToFile))) { - if (!exists(dirname(target))) { - createDir(dirname(target), recursive: true); - } - } - - if (!overwrite && exists(target)) { - throw MoveTreeException( - 'The target file ${truepath(to)} already exists', - ); - } - - move(pathToFile, target, overwrite: overwrite); - verbose( - () => 'moveTree moving: ${truepath(from)} -> ${truepath(target)}', - ); - } - } -} - -/// Thrown when the [moveTree] function encouters an error. -class MoveTreeException extends DCliFunctionException { - /// Thrown when the [moveTree] function encouters an error. - MoveTreeException(super.reason); -} diff --git a/packages/console/lib/src/functions/pwd.dart b/packages/console/lib/src/functions/pwd.dart deleted file mode 100644 index 834e126..0000000 --- a/packages/console/lib/src/functions/pwd.dart +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'dcli_function.dart'; - -/// -/// Returns the current working directory. -/// -/// ```dart -/// print(pwd); -/// ``` -/// -/// See join -/// -String get pwd => _PWD().pwd; - -class _PWD extends DCliFunction { - String get pwd => Directory.current.path; -} diff --git a/packages/console/lib/src/functions/replace.dart b/packages/console/lib/src/functions/replace.dart deleted file mode 100644 index 5b322d7..0000000 --- a/packages/console/lib/src/functions/replace.dart +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:protevus_console/core.dart'; - -/// -/// Does an insitu replacement on the file located at [path]. -/// -/// [replace] searches the file at [path] for any occurances -/// of [existing] and replaces them with [replacement]. -/// -/// By default we only replace the first occurance of [existing] on each line. -/// To replace every (non-overlapping) occurance of [existing] on a -/// line then set [all] to true; -/// -/// The [replace] method returns the no. of lines modified. -/// -/// The [existing] argument can be a simple String which or a regex. -/// -/// ```dart -/// replace(pathToFile, 'change me', 'changed'); -/// replace(pathToFile, RegExp(r'change \w+'), 'changed'); -/// ``` -/// -/// During the process a temporary file called [path].tmp is created -/// in the directory of [path]. -/// The modified file is written to [path].tmp. -/// Once the replacement completes successfully the file at [path] -/// is renamed to [path].bak, [path].tmp is renamed to [path] and then -/// [path].bak is deleted. -/// -/// The above process essentially makes replace atomic so it should -/// be impossible to loose your file. If replace does crash you may -/// have to delete [path].tmp or [path].bak but this is highly unlikely. -/// -int replace( - String path, - Pattern existing, - String replacement, { - bool all = false, -}) => - _Replace().replace(path, existing, replacement, all: all); - -class _Replace extends DCliFunction { - int replace( - String path, - Pattern existing, - String replacement, { - bool all = false, - }) { - var changes = 0; - final tmp = '$path.tmp'; - if (exists(tmp)) { - delete(tmp); - } - touch(tmp, create: true); - withOpenLineFile(tmp, (tmpFile) { - withOpenLineFile(path, (file) { - file.readAll((line) { - String newline; - if (all) { - newline = line.replaceAll(existing, replacement); - } else { - newline = line.replaceFirst(existing, replacement); - } - if (newline != line) { - changes++; - } - - tmpFile.append(newline); - return true; - }); - }); - }); - - if (changes != 0) { - move(path, '$path.bak'); - move(tmp, path); - delete('$path.bak'); - } else { - delete(tmp); - } - return changes; - } -} diff --git a/packages/console/lib/src/functions/tail.dart b/packages/console/lib/src/functions/tail.dart deleted file mode 100644 index 5897c8e..0000000 --- a/packages/console/lib/src/functions/tail.dart +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:circular_buffer/circular_buffer.dart'; - -import '../settings.dart'; -import '../utils/line_file.dart'; -import '../utils/truepath.dart'; -import 'dcli_function.dart'; -import 'is.dart'; - -/// -/// Returns count [lines] from the end of the file at [path]. -/// -/// ```dart -/// tail('/var/log/syslog', 10).forEach((line) => print(line)); -/// ``` -/// -/// Throws a [TailException] exception if [path] is not a file. -/// -List tail(String path, int lines) => _Tail().tail(path, lines); - -class _Tail extends DCliFunction { - List tail( - String path, - int lines, - ) { - verbose(() => 'tail ${truepath(path)} lines: $lines'); - - if (lines < 1) { - throw TailException('lines must be >= 1'); - } - - if (!exists(path)) { - throw TailException('The path ${truepath(path)} does not exist.'); - } - - if (!isFile(path)) { - throw TailException('The path ${truepath(path)} is not a file.'); - } - - /// circbuffer requires a min size of 2 so we - /// add one to make certain it is always greater than one - /// and then adjust later. - final buffer = CircularBuffer(lines + 1); - try { - withOpenLineFile(path, (file) { - file.readAll((line) { - buffer.add(line); - return true; - }); - }); - } - // ignore: avoid_catches_without_on_clauses - catch (e) { - throw TailException( - 'An error occured reading ${truepath(path)}. Error: $e', - ); - } - - final lastLines = buffer.toList(); - - /// adjust the buffer by stripping extra line. - if (buffer.isFilled) { - lastLines.removeAt(0); - } - - return lastLines; - } -} - -/// thrown when the [tail] function encounters an exception -class TailException extends DCliFunctionException { - /// thrown when the [tail] function encounters an exception - TailException(super.reason); -} diff --git a/packages/console/lib/src/functions/touch.dart b/packages/console/lib/src/functions/touch.dart deleted file mode 100644 index ae336ab..0000000 --- a/packages/console/lib/src/functions/touch.dart +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * 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:protevus_console/core.dart'; - -/// Updates the last modified time stamp of a file. -/// -/// ```dart -/// touch('fred.txt'); -/// touch('fred.txt, create: true'); -/// ``` -/// -/// -/// If [create] is true and the file doesn't exist -/// it will be created. -/// -/// If [create] is false and the file doesn't exist -/// a [TouchException] will be thrown. -/// -/// [create] is false by default. -/// -/// As a convenience the touch function returns the [path] variable -/// that was passed in. - -String touch(String path, {bool create = false}) { - final absolutePath = truepath(path); - - verbose(() => 'touch: $absolutePath create: $create'); - - if (!exists(p.dirname(absolutePath))) { - throw TouchException( - 'The directory tree above $absolutePath does not exist. ' - 'Create the tree and try again.', - ); - } - if (create == false && !exists(absolutePath)) { - throw TouchException( - 'The file $absolutePath does not exist. ' - 'Did you mean to use touch(path, create: true) ?', - ); - } - - try { - final file = File(absolutePath); - - if (file.existsSync()) { - final now = DateTime.now(); - file - ..setLastAccessedSync(now) - ..setLastModifiedSync(now); - } else { - if (create) { - file.createSync(); - } - } - } on FileSystemException catch (e) { - throw TouchException('Unable to touch file $absolutePath: ${e.message}'); - } - return path; -} - -/// thrown when the [touch] function encounters an exception -class TouchException extends DCliFunctionException { - /// thrown when the [touch] function encounters an exception - TouchException(super.reason); -} diff --git a/packages/console/lib/src/functions/which.dart b/packages/console/lib/src/functions/which.dart deleted file mode 100644 index de01a51..0000000 --- a/packages/console/lib/src/functions/which.dart +++ /dev/null @@ -1,196 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:path/path.dart'; - -import 'package:protevus_console/core.dart'; - -/// -/// Searches the PATH for the location of the application -/// give by [appname]. -/// -/// The search is conducted by searching each of the -/// paths in the environment variable 'PATH' from -/// left to right (start to end) as this is the -/// same order the OS searches the path. -/// -/// If the [verbose] flag is true then a line is output to -/// the [progress] for each path searched. -/// -/// It is possible that more than one copy of the -/// appliation is found. -/// -/// [which] returns a list of paths that contain -/// [appname] in the order they were found. -/// -/// The first path in the list is the one the OS -/// will be using. -/// -/// if the [first] flag is true then which will -/// stop searching as soon as it finds a match. -/// [first] is true by default. -/// -/// ```dart -/// which('ls', first: false, verbose: true); -/// ``` -/// -/// To print the path to the command: -/// -/// ```dart -/// print(which('ls').path); -/// ``` -/// -/// To check if an app is on the path use: -/// -/// ```dart -/// if (which('apt').found) -/// { -/// print('found apt'); -/// } -/// ``` -/// -/// if [extensionSearch] is true and the passed [appname] doesn't have a file -/// extension then when running on Windows the which command will search -/// for [appname] plus [appname] with each of the extensions listed -/// in the Windows environment variable PATHEX. -/// This feature is intended to make it easier to implement cross platform -/// command search. For example the dart will be 'dart' -/// on Linux and 'dart.bat' on Windows. Using `which('dart')` will find `dart` -/// on linux and `dart.bat` on Windows. -Which which( - String appname, { - bool first = true, - bool verbose = false, - bool extensionSearch = true, - void Function(WhichSearch)? progress, -}) => - _Which().which( - appname, - first: first, - verbose: verbose, - extensionSearch: extensionSearch, - progress: progress, - ); - -/// Returned from the [which] funtion to provide the details we discovered -/// about appname. -class Which { - String? _path; - final _paths = []; - bool _found = false; - - /// The progress used to accumualte the results - /// If verbose was passed this will contain all - /// of the verbose output. If you passed a [progress] - /// into the which call then this will be the same progress - /// otherwse a Progress.devNull will be allocated and returned. - Stream? progress; - - /// The first path found containing appname - /// - /// See [paths] for a list of all paths that contained appname - String? get path => _path; - - /// Contains the list of paths that contain appname. - /// - /// If no paths are found then this list will be empty. - /// - /// If first is true this will contain at most 1 path. - List get paths => _paths; - - /// Returns true if at least one path was found that contained appname - bool get found => _found; - - /// Returns true if appname was not found in any path. - bool get notfound => !_found; -} - -/// Search resutls from the [which] method. -class WhichSearch { - /// the app was found on the path. - WhichSearch.found(this.path, this.exePath) : found = true; - - /// the app was not found. - WhichSearch.notfound(this.path) : found = false; - - /// passed in path to search for. - String path; - - /// true if the app was found - bool found; - - /// If the app was found this is the fully qualified path to the app. - String? exePath; -} - -class _Which extends DCliFunction { - /// - /// Searches the path for the given appname. - Which which( - String appname, { - required bool extensionSearch, - bool first = true, - bool verbose = false, - void Function(WhichSearch)? progress, - }) { - final results = Which(); - for (final path in PATH) { - final fullpath = - _appExists(path, appname, extensionSearch: extensionSearch); - if (fullpath == null) { - progress?.call(WhichSearch.notfound(path)); - } else { - progress?.call(WhichSearch.found(path, fullpath)); - - if (!results._found) { - results._path = fullpath; - } - results.paths.add(fullpath); - results._found = true; - if (first) { - break; - } - } - } - - return results; - } - - /// Checks if [appname] exists in [pathTo]. - /// - /// On Windows if [extensionSearch] is true and [appname] doesn't - /// have an extension then we check each appname.extension variant - /// to see if it exists. We first check if just an file of [appname] with - /// no extension exits. - String? _appExists( - String pathTo, - String appname, { - required bool extensionSearch, - }) { - final pathToAppname = join(pathTo, appname); - if (exists(pathToAppname)) { - return pathToAppname; - } - if (Settings().isWindows && extensionSearch && extension(appname).isEmpty) { - final pathExt = env['PATHEXT']; - - if (pathExt != null) { - final extensions = pathExt.split(';'); - for (final extension in extensions) { - final fullname = '$pathToAppname$extension'; - if (exists(fullname)) { - return fullname; - } - } - } - } - return null; - } -} diff --git a/packages/console/lib/src/input/ask.dart b/packages/console/lib/src/input/ask.dart deleted file mode 100644 index 6e03f93..0000000 --- a/packages/console/lib/src/input/ask.dart +++ /dev/null @@ -1,848 +0,0 @@ -/* Copyright (C) S. Brett Sutton - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Brett Sutton , Jan 2022 - */ - -import 'dart:convert'; -import 'dart:io'; -import 'package:protevus_console/core.dart' as core; -import 'package:protevus_console/core.dart'; -import 'package:protevus_console/terminal.dart'; -import 'package:meta/meta.dart'; -import 'package:validators2/validators2.dart'; - -import 'echo.dart'; - -typedef CustomAskPrompt = String Function( - String prompt, - String? defaultValue, - // ignore: avoid_positional_boolean_parameters - bool hidden); - -/// -/// Reads a line of text from stdin with an optional prompt. -/// -/// -/// ```dart -/// String response = ask("Do you like me?"); -/// ``` -/// -/// If the script is not attached to terminal [Terminal().hasTerminal] -/// then ask returns immediatly with the [defaultValue]. If no [defaultValue] -/// is passed then an empty string is returned. No validate will be applied. -/// -/// If [prompt] is set then the prompt will be printed -/// to the console and the cursor placed immediately after the prompt. -/// -/// Pass an empty string to suppress the prompt. -/// -/// ```dart -/// var secret = ask('', required: false); -/// ``` -/// -/// By default the ask [required] argument is true requiring the user to enter -/// a non-empty string. -/// All whitespace is trimmed from the string before the user input -/// is validated so -/// a single space is not an accepted input. -/// -/// If you set the [required] argument to false then the user can just hit -/// enter to skip past the ask prompt. If you use other validators when -/// [required] = false -/// then those validators will not be called if the entered value is empty -/// (after it is trimmed). -/// -/// if [toLower] is true then the returned result is converted to lower case. -/// This can be useful if you need to compare the entered value. -/// -/// If [hidden] is true then the entered values will not be echoed to the -/// console, instead '*' will be displayed. This is uesful for capturing -/// passwords. -/// -/// NOTE: if there is no terminal detected then this will fallback to -/// a standard ask input in which case the hidden characters WILL BE DISPLAYED -/// as they are typed. -/// -/// If a [defaultValue] is passed then it is displayed and the user -/// fails to enter a value (just hits the enter key) then the -/// [defaultValue] is returned. -/// -/// Passing a [defaultValue] also modifies the prompt to display the value: -/// -/// ```dart -/// var result = ask('How many', defaultValue: '5'); -/// > 'How many [5]' -/// ``` -/// [ask] will throw an [AskValidatorException] if the defaultValue -/// doesn't match the given [validator]. -/// -/// The [validator] is called each time the user hits enter. -/// -/// The [validator] allows you to normalise and validate the user's -/// input. The [validator] must return the normalised value which -/// will be the value returned by [ask]. -/// -/// If the [validator] detects an invalid input then you MUST -/// throw [AskValidatorException(error)]. The error will -/// be displayed on the console and the user reprompted. -/// You can color code the error using any of the dcli -/// color functions. By default all input is considered valid. -/// -/// The [customErrorMessage] allows you to provide a custom error message -/// to be displayed instead of the default one when the input is invalid. -/// This can be useful if you want to provide specific guidance or instructions -/// to the user regarding the expected input format or constraints. -/// -///```dart -/// var subject = ask( 'Subject'); -/// subject = ask( 'Subject', required: true); -/// subject = ask( 'Subject', validator: Ask.minLength(10)); -/// var name = ask( 'What is your name?', validator: Ask.alpha); -/// var age = ask( 'How old are you?', validator: Ask.integer); -/// var username = ask( 'Username?', validator: Ask.email); -/// var password = ask( 'Password?', hidden: true, -/// validator: Ask.all([Ask.alphaNumeric, AskValidatorLength(10,16)])); -/// var color = ask( 'Favourite colour?' -/// , Ask.inList(['red', 'green', 'blue'])); -/// -///``` -Future ask( - String prompt, { - bool toLower = false, - bool hidden = false, - bool required = true, - String? defaultValue, - CustomAskPrompt customPrompt = Ask.defaultPrompt, - AskValidator validator = Ask.dontCare, - String? customErrorMessage, -}) async => - Ask()._ask( - prompt, - toLower: toLower, - hidden: hidden, - required: required, - defaultValue: defaultValue, - customPrompt: customPrompt, - validator: validator, - customErrorMessage: customErrorMessage, - ); - -// ignore: avoid_clas -/// Class for [ask] and related code. -class Ask extends core.DCliFunction { - static const int _backspace = 127; - static const int _space = 32; - static const int _ = 8; - - /// - /// Reads user input from stdin and returns it as a string. - /// [prompt] - Future _ask( - String prompt, { - required bool hidden, - required bool required, - required AskValidator validator, - required CustomAskPrompt customPrompt, - bool toLower = false, - String? defaultValue, - String? customErrorMessage, - }) async { - ArgumentError.checkNotNull(prompt); - core.verbose( - () => 'ask: $prompt toLower: $toLower hidden: $hidden ' - 'required: $required ' - 'defaultValue: ${hidden ? '******' : defaultValue}', - ); - - if (!Terminal().hasTerminal) { - return defaultValue ?? ''; - } - final finalPrompt = customPrompt(prompt, defaultValue, hidden); - - var line = ''; - var valid = false; - do { - await echo('$finalPrompt '); - - if (hidden == true && stdin.hasTerminal) { - line = await _readHidden(); - } else { - line = stdin.readLineSync(encoding: Encoding.getByName('utf-8')!) ?? ''; - } - - if (line.isEmpty && defaultValue != null) { - line = defaultValue; - } - - if (toLower == true) { - line = line.toLowerCase(); - } - - try { - if (required) { - await const _AskRequired() - .validate(line, customErrorMessage: customErrorMessage); - } - verbose(() => 'ask: pre validation "$line"'); - line = await validator.validate(line, - customErrorMessage: customErrorMessage); - verbose(() => 'ask: post validation "$line"'); - valid = true; - } on AskValidatorException catch (e) { - print(e.message); - } - - verbose(() => 'ask: result $line'); - } while (!valid); - - return line; - } - - Future _readHidden() async { - final value = []; - - try { - stdin.echoMode = false; - stdin.lineMode = false; - int char; - do { - char = stdin.readByteSync(); - if (char != 10 && char != 13) { - if (char == _backspace) { - if (value.isNotEmpty) { - // move back a character, - // print a space an move back again. - // required to clear the current character - // move back one space. - stdout - ..writeCharCode(_) - ..writeCharCode(_space) - ..writeCharCode(_); - value.removeLast(); - } - } else { - // apparently flush isn't need - despite the doc. - stdout.write('*'); - value.add(char); - } - } - } while (char != 10 && char != 13); - } finally { - stdin.lineMode = true; - stdin.echoMode = true; - } - - // output a newline as we have suppressed it. - print(''); - - // return the entered value as a String. - return Encoding.getByName('utf-8')!.decode(value); - } - - static String defaultPrompt( - String prompt, - String? defaultValue, - // ignore: avoid_positional_boolean_parameters - bool hidden) { - var result = prompt; - - /// completely suppress the default value and the prompt if - /// the prompt is empty. - if (defaultValue != null && prompt.isNotEmpty) { - /// don't display the default value if hidden is true. - result = '$prompt [${hidden ? '******' : defaultValue}]'; - } - return result; - } - - /// The default validator that considers any input as valid - static const AskValidator dontCare = _AskDontCare(); - - /// Takes an array of validators. The input is considered valid if any one - /// of the validators returns true. - /// The validators are processed in order from left to right. - /// If none of the validators pass then the error from the first validator - /// that failed is returned. The implications is that the user will only - /// ever see the error from the first validator. - static AskValidator any(List validators) => - _AskValidatorAny(validators); - - /// Takes an array of validators. The input is considered valid only if - /// everyone of the validators pass. - /// - /// The validators are processed in order from left to right. - /// - /// The error from the first validator that failes is returned. - /// - /// It should be noted that the user input is passed to each validator in turn - /// and each validator has the opportunity to modify the input. As a result - /// a validators will be operating on a version of the input - /// that has been processed by all validators that appear - /// earlier in the list. - static AskValidator all(List validators) => - _AskValidatorAll(validators); - - /// Validates that input is a IP address - /// By default both v4 and v6 addresses are valid - /// Pass a [version] to limit the input to one or the - /// other. If passed [version] must be [AskValidatorIPAddress.ipv4] - /// or [AskValidatorIPAddress.ipv6]. - static AskValidator ipAddress({int version = AskValidatorIPAddress.either}) => - AskValidatorIPAddress(version: version); - - /// Validates the input against a regular expression - /// ```dart - /// ask('Variable Name:', validator: Ask.regExp(r'^[a-zA-Z_]+$')); - /// ``` - static AskValidator regExp(String regExp, {String? error}) => - _AskRegExp(regExp, error: error); - - /// Validates that the entered line is no longer - /// than [maxLength]. - static AskValidator lengthMax(int maxLength) => - _AskValidatorMaxLength(maxLength); - - /// Validates that the entered line is not less - /// than [minLength]. - static AskValidator lengthMin(int minLength) => - _AskValidatorMinLength(minLength); - - /// Validates that the length of the entered text - /// as at least [minLength] but no more than [maxLength]. - static AskValidator lengthRange(int minLength, int maxLength) => - _AskValidatorLength(minLength, maxLength); - - /// Validates that a number is between a minimum value (inclusive) - /// and a maximum value (inclusive). - static AskValidator valueRange(num minValue, num maxValue) => - _AskValidatorValueRange(minValue, maxValue); - - /// Checks that the input matches one of the - /// provided [validItems]. - /// If the validator fails it prints out the - /// list of available inputs. - /// By default [caseSensitive] matches are off. - static AskValidator inList( - List validItems, { - bool caseSensitive = false, - }) => - _AskValidatorList(validItems, caseSensitive: caseSensitive); - - /// The user must enter a non-empty string. - /// Whitespace will be trimmed before the string is tested. - static const AskValidator required = _AskRequired(); - - /// validates that the input is an email address - static const AskValidator email = _AskEmail(); - - /// validates that the input is a fully qualified domian name. - static const AskValidator fqdn = _AskFQDN(); - - /// Validates that the input is a valid url. - /// You may pass in a list of acceptable protocols. - /// By default only 'https' is allowed. - static AskValidator url({List protocols = const ['https']}) => - _AskURL(protocols: protocols); - - /// validates that the input is a date. - static const AskValidator date = _AskDate(); - - /// validates that the input is an integer - static const AskValidator integer = _AskInteger(); - - /// validates that the input is a decimal - static const AskValidator decimal = _AskDecimal(); - - /// validates that the input is only alpha characters - static const AskValidator alpha = _AskAlpha(); - - /// validates that the input is only alphanumeric characters. - static const AskValidator alphaNumeric = _AskAlphaNumeric(); -} - -/// Thrown when an [AskValidator] detects an invalid input. -class AskValidatorException extends DCliException { - /// validator with a [message] indicating the error. - AskValidatorException(super.message); -} - -/// Base class for all [AskValidator]s. -/// You can add your own by extending this class. -// ignore: one_member_abstracts -abstract class AskValidator { - /// allows us to make validators consts. - const AskValidator(); - - /// This method is called by [ask] to valiate the - /// string entered by the user. - /// It should throw an AskValidatorException if the input - /// is invalid. - /// The validate method is called when the user hits the enter key. - /// If the validation succeeds the validated line is returned. - @visibleForTesting - Future validate(String line, {String? customErrorMessage}); -} - -/// The default validator that considers any input as valid -class _AskDontCare extends AskValidator { - const _AskDontCare(); - @override - Future validate(String line, {String? customErrorMessage}) async => - line; -} - -/// The user must enter a non-empty string. -/// Whitespace will be trimmed before the string is tested. -/// -class _AskRequired extends AskValidator { - const _AskRequired(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim(); - if (finalLine.isEmpty) { - throw AskValidatorException( - red(customErrorMessage ?? 'You must enter a value.')); - } - return finalLine; - } -} - -class _AskEmail extends AskValidator { - const _AskEmail(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim(); - - if (!isEmail(finalLine)) { - throw AskValidatorException( - red(customErrorMessage ?? 'Invalid email address.')); - } - return finalLine; - } -} - -class _AskFQDN extends AskValidator { - const _AskFQDN(); - - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim().toLowerCase(); - - if (!isFQDN(finalLine)) { - throw AskValidatorException(red(customErrorMessage ?? 'Invalid FQDN.')); - } - return finalLine; - } -} - -class _AskURL extends AskValidator { - const _AskURL({this.protocols = const ['https']}); - - final List protocols; - - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim().toLowerCase(); - - if (!isURL(finalLine, protocols: protocols)) { - throw AskValidatorException(red(customErrorMessage ?? 'Invalid URL.')); - } - return finalLine; - } -} - -/// Validates the input against a regular expression -/// ```dart -/// ask('Variable Name:', validator: Ask.regExp(r'^[a-zA-Z_]+$')); -/// ``` -/// -class _AskRegExp extends AskValidator { - /// Creates a regular expression based validator. - /// You can customise the error message by providing a value for [error]. - _AskRegExp(this.regexp, {String? error}) { - _regexp = RegExp(regexp); - _error = error ?? 'Input does not match: $regexp'; - } - - final String regexp; - late final RegExp _regexp; - late final String _error; - - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim(); - - if (!_regexp.hasMatch(finalLine)) { - throw AskValidatorException(red(customErrorMessage ?? _error)); - } - return finalLine; - } -} - -class _AskDate extends AskValidator { - const _AskDate(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim(); - - if (!isDate(finalLine)) { - throw AskValidatorException(red(customErrorMessage ?? 'Invalid date.')); - } - return finalLine; - } -} - -class _AskInteger extends AskValidator { - const _AskInteger(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalLine = line.trim(); - verbose(() => 'AskInteger: $finalLine'); - - if (!isInt(finalLine)) { - throw AskValidatorException( - red(customErrorMessage ?? 'Invalid integer.')); - } - return finalLine; - } -} - -class _AskDecimal extends AskValidator { - const _AskDecimal(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - if (!isFloat(finalline)) { - throw AskValidatorException( - red(customErrorMessage ?? 'Invalid decimal number.')); - } - return finalline; - } -} - -class _AskAlpha extends AskValidator { - const _AskAlpha(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - if (!isAlpha(finalline)) { - throw AskValidatorException( - red(customErrorMessage ?? 'Alphabetical characters only.')); - } - return finalline; - } -} - -class _AskAlphaNumeric extends AskValidator { - const _AskAlphaNumeric(); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - if (!isAlphanumeric(finalline)) { - throw AskValidatorException( - red(customErrorMessage ?? 'Alphanumerical characters only.')); - } - return finalline; - } -} - -/// Validates that input is a IP address -/// By default both v4 and v6 addresses are valid -/// Pass a [version] to limit the input to one or the -/// other. If passed [version] must be [ipv4] or [ipv6]. -class AskValidatorIPAddress extends AskValidator { - /// Validates that input is a IP address - /// By default both v4 and v6 addresses are valid - /// Pass a [version] to limit the input to one or the - /// other. If passed [version] must be 4 or 6. - const AskValidatorIPAddress({this.version = either}); - - /// The ip address may be either [ipv4] or [ipv6]. - static const int either = 0; - - /// The ip address must be an ipv4 address. - static const int ipv4 = 4; - - /// The ip address must be an ipv6 address. - static const int ipv6 = 6; - - /// IP version (on 4 and 6 are valid versions.) - final int version; - - @override - Future validate(String line, {String? customErrorMessage}) async { - assert( - version == either || version == ipv4 || version == ipv6, - 'The version must be AskValidatorIPAddress.either or ' - 'AskValidatorIPAddress.ipv4 or AskValidatorIPAddress.ipv6', - ); - - final finalline = line.trim(); - - var validatorsVersion = IPVersion.any; - switch (version) { - case ipv4: - validatorsVersion = IPVersion.ipV4; - break; - case ipv6: - validatorsVersion = IPVersion.ipV6; - break; - } - - if (!isIP(finalline, version: validatorsVersion)) { - throw AskValidatorException( - red(customErrorMessage ?? 'Invalid IP Address.')); - } - return finalline; - } -} - -/// Validates that the entered line is no longer -/// than [maxLength]. -class _AskValidatorMaxLength extends AskValidator { - /// Validates that the entered line is no longer - /// than [maxLength]. - const _AskValidatorMaxLength(this.maxLength); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - if (finalline.length > maxLength) { - throw AskValidatorException( - red( - customErrorMessage ?? - 'You have exceeded the maximum length of $maxLength characters.', - ), - ); - } - return finalline; - } - - /// the maximum allows length for the entered string. - final int maxLength; -} - -/// Validates that the entered line is not less -/// than [minLength]. -class _AskValidatorMinLength extends AskValidator { - /// Validates that the entered line is not less - /// than [minLength]. - const _AskValidatorMinLength(this.minLength); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - if (finalline.length < minLength) { - throw AskValidatorException( - red(customErrorMessage ?? - 'You must enter at least $minLength characters.'), - ); - } - return finalline; - } - - /// the minimum allows length of the string. - final int minLength; -} - -/// Validates that the length of the entered text -// ignore: comment_references -/// as at least [minLength] but no more than [maxLength]. -class _AskValidatorLength extends AskValidator { - /// Validates that the length of the entered text - /// as at least [minLength] but no more than [maxLength]. - _AskValidatorLength(int minLength, int maxLength) { - _validator = _AskValidatorAll([ - _AskValidatorMinLength(minLength), - _AskValidatorMaxLength(maxLength), - ]); - } - - late _AskValidatorAll _validator; - - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - return _validator.validate(finalline, - customErrorMessage: customErrorMessage); - } -} - -class _AskValidatorValueRange extends AskValidator { - const _AskValidatorValueRange(this.minValue, this.maxValue); - - final num minValue; - final num maxValue; - - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - - final value = num.tryParse(finalline); - if (value == null) { - throw AskValidatorException( - red(customErrorMessage ?? 'Must be a number.')); - } - - if (value < minValue) { - throw AskValidatorException( - red(customErrorMessage ?? - 'The number must be greater than or equal to $minValue.'), - ); - } - - if (value > maxValue) { - throw AskValidatorException( - red(customErrorMessage ?? - 'The number must be less than or equal to $maxValue.'), - ); - } - - return finalline; - } -} - -/// Takes an array of validators. The input is considered valid only if -/// everyone of the validators pass. -/// -/// The validators are processed in order from left to right. -/// -/// The error from the first validator that failes is returned. -/// -/// It should be noted that the user input is passed to each validator in turn -/// and the validator has the opportunity to modify the input. As a result -/// a validators will be operating on a version of the input -/// that has been processed by all validators that appear earlier in the list. -class _AskValidatorAll extends AskValidator { - /// Takes an array of validators. The input is considered valid only if - /// everyone of the validators pass. - /// - /// The validators are processed in order from left to right. - /// - /// The error from the first validator that failes is returned. - /// - /// It should be noted that the user input is passed to each validator in turn - /// and the validator has the opportunity to modify the input. As a result - /// a validators will be operating on a version of the input - /// that has been processed by all validators that appear earlier - /// in the list. - _AskValidatorAll(this._validators); - - final List _validators; - - @override - Future validate(String line, {String? customErrorMessage}) async { - var finalline = line.trim(); - - for (final validator in _validators) { - finalline = await validator.validate(finalline, - customErrorMessage: customErrorMessage); - } - return finalline; - } -} - -/// Takes an array of validators. The input is considered valid if any one -/// of the validators returns true. -/// The validators are processed in order from left to right. -/// If none of the validators pass then the error from the first validator -/// that failed is returned. The implications is that the user will only -/// ever see the error from the first validator. -/// -/// It should be noted that the user input is passed to each validator in turn -/// and each validator has the opportunity to modify the input. As a result -/// a validators will be operating on a version of the input -/// that has been processed by all validators that appear earlier in the list. -class _AskValidatorAny extends AskValidator { - /// Takes an array of validators. The input is considered valid if any one - /// of the validators returns true. - /// The validators are processed in order from left to right. - /// If none of the validators pass then the error from the first validator - /// that failed is returned. The implications is that the user will only - /// ever see the error from the first validator. - /// - /// It should be noted that the user input is passed to each validator in turn - /// and each validator has the opportunity to modify the input. As a result - /// a validators will be operating on a version of the input - /// that has been processed by all successful validators - /// that appear earlier in the list. - /// - /// Validators that fail don't get an opportunity to modify the input. - _AskValidatorAny(this._validators); - - final List _validators; - - @override - Future validate(String line, {String? customErrorMessage}) async { - var finalline = line.trim(); - - AskValidatorException? firstFailure; - - var onePassed = false; - - for (final validator in _validators) { - try { - finalline = await validator.validate(finalline, - customErrorMessage: customErrorMessage); - onePassed = true; - } on AskValidatorException catch (e) { - firstFailure ??= e; - } - } - if (!onePassed) { - throw firstFailure!; - } - return finalline; - } -} - -/// Checks that the input matches one of the -/// provided [validItems]. -/// If the validator fails it prints out the -/// list of available inputs. -class _AskValidatorList extends AskValidator { - /// Checks that the input matches one of the - /// provided [validItems]. - /// If the validator fails it prints out the - /// list of available inputs. - /// By default [caseSensitive] matches are off. - _AskValidatorList(this.validItems, {this.caseSensitive = false}); - - /// The list of allowed values. - final List validItems; - final bool caseSensitive; - - @override - Future validate(String line, {String? customErrorMessage}) async { - var finalline = line.trim(); - - if (caseSensitive) { - finalline = finalline.toLowerCase(); - } - var found = false; - for (final item in validItems) { - var itemValue = item.toString(); - if (caseSensitive) { - itemValue = itemValue.toLowerCase(); - } - - if (finalline == itemValue) { - found = true; - break; - } - } - if (!found) { - throw AskValidatorException( - red(customErrorMessage ?? - 'The valid responses are ${validItems.join(' | ')}.'), - ); - } - - return finalline; - } -} diff --git a/packages/console/lib/src/input/confirm.dart b/packages/console/lib/src/input/confirm.dart deleted file mode 100644 index ff56424..0000000 --- a/packages/console/lib/src/input/confirm.dart +++ /dev/null @@ -1,90 +0,0 @@ -/* Copyright (C) S. Brett Sutton - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Brett Sutton , Jan 2022 - */ - -import 'package:protevus_console/terminal.dart'; - -import 'ask.dart'; - -typedef CustomConfirmPrompt = String Function( - String prompt, - // ignore: avoid_positional_boolean_parameters - bool? defaultValue); - -/// [confirm] is a specialized version of ask that returns true or -/// false based on the value entered. -/// -/// The user must enter a valid value or, if a [defaultValue] -/// is passed, the enter key. -/// -/// Accepted values are y|t|true|yes and n|f|false|no (case insenstiive). -/// -/// If the user enters an unknown value an error is printed -/// and they are reprompted. -/// -/// The [prompt] is displayed to the user with ' (y/n)' appended. -/// -/// If a [defaultValue] is passed then either the y or n will be capitalised -/// and if the user hits the enter key then the [defaultValue] will be returned. -/// -/// If the script is not attached to a terminal [Terminal().hasTerminal] -/// then confirm returns immediately with the [defaultValue]. -/// If there is no [defaultValue] then true is returned. -Future confirm(String prompt, - {bool? defaultValue, - CustomConfirmPrompt customPrompt = Confirm.defaultPrompt}) async { - var result = false; - var matched = false; - - if (!Terminal().hasTerminal) { - return defaultValue ?? true; - } - - while (!matched) { - final entered = await ask( - prompt, - toLower: true, - required: false, - customPrompt: (_, __, ___) => customPrompt(prompt, defaultValue), - ); - var lower = entered.trim().toLowerCase(); - - if (lower.isEmpty && defaultValue != null) { - lower = defaultValue ? 'true' : 'false'; - } - - if (['y', 't', 'true', 'yes'].contains(lower)) { - result = true; - matched = true; - break; - } - if (['n', 'f', 'false', 'no'].contains(lower)) { - result = false; - matched = true; - break; - } - print('Invalid value: $entered'); - } - return result; -} - -// ignore: avoid_classes_with_only_static_members -class Confirm { - // ignore: avoid_positional_boolean_parameters - static String defaultPrompt(String prompt, bool? defaultValue) { - var finalPrompt = prompt; - - if (defaultValue == null) { - finalPrompt += ' (y/n):'; - } else { - if (defaultValue == true) { - finalPrompt += ' (Y/n):'; - } else { - finalPrompt += ' (y/N):'; - } - } - return finalPrompt; - } -} diff --git a/packages/console/lib/src/input/echo.dart b/packages/console/lib/src/input/echo.dart deleted file mode 100644 index 0aab925..0000000 --- a/packages/console/lib/src/input/echo.dart +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright (C) S. Brett Sutton - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Brett Sutton , Jan 2022 - */ - -import 'dart:io'; -import 'package:protevus_console/core.dart'; - -/// Writes [text] to stdout including a newline. -/// -/// ```dart -/// echo("Hello world", newline=false); -/// ``` -/// -/// If [newline] is false then a newline will not be output. -/// -/// [newline] defaults to false. -Future echo(String text, {bool newline = false}) async => - _Echo().echo(text, newline: newline); - -class _Echo extends DCliFunction { - Future echo(String text, {required bool newline}) async { - if (newline) { - stdout.writeln(text); - } else { - stdout.write(text); - } - // ignore: discarded_futures - await stdout.flush(); - } -} diff --git a/packages/console/lib/src/input/menu.dart b/packages/console/lib/src/input/menu.dart deleted file mode 100644 index 5786c89..0000000 --- a/packages/console/lib/src/input/menu.dart +++ /dev/null @@ -1,185 +0,0 @@ -/* Copyright (C) S. Brett Sutton - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - * Written by Brett Sutton , Jan 2022 - */ - -import 'dart:math'; - -import 'package:protevus_console/terminal.dart'; - -import 'ask.dart'; - -String _noFormat(T option) => option.toString(); - -typedef CustomMenuPrompt = String Function( - String prompt, String? defaultOption); - -/// Displays a menu with each of the provided [options], prompts -/// the user to select an option and returns the selected option. -/// -/// e.g. -/// ```dart -/// var colors = [Color('Red'), Color('Green')]; -/// var color = menu( 'Please select a color', options: colors); -/// ``` -/// Results in: -///``` -/// 1) Red -/// 2) Green -/// Please select a color: -/// ``` -/// -/// [menu] will display an error if the user enters a non-valid -/// response and then redisplay the prompt. -/// -/// Once a user selects a valid option, that option is returned. -/// -/// You may provide a [limit] which will cause the -/// menu to only display the first [limit] options passed. -/// -/// If you pass a [format] lambda then the [format] function -/// will be called for for each option and the resulting format -/// used to display the option in the menu. -/// -/// e.g. -/// ```dart -/// -/// var colors = [Color('Red'), Color('Green')]; -/// var color = menu(prompt: 'Please select a color' -/// , options: colors, format: (color) => color.name); -/// ``` -/// -/// If [format] is not passed [option.toString()] will be used -/// as the format for the menu option. -/// -/// When a [limit] is applied the menu will display the first [limit] -/// options. If you specify [fromStart: false] then the menu will display the -/// last [limit] options. -/// -/// If you pass a [defaultOption] the matching option is highlighted -/// in green in the menu -/// and if the user hits enter without entering a value the [defaultOption] -/// is returned. -/// -/// If the [defaultOption] does not match any the supplied [options] -/// then an ArgumentError is thrown. -/// -/// If the app is not attached to a terminal then the menu will not be -/// displayed and the [defaultOption] will be returned. -/// If there is no [defaultOption] then the first [options] will be returned. -/// -Future menu( - String prompt, { - required List options, - T? defaultOption, - CustomMenuPrompt customPrompt = Menu.defaultPrompt, - int? limit, - String Function(T)? format, - bool fromStart = true, -}) async { - if (options.isEmpty) { - throw ArgumentError( - 'The list of [options] passed to menu(options: ) was empty.', - ); - } - limit ??= options.length; - // ignore: parameter_assignments - limit = min(options.length, limit); - format ??= _noFormat; - - if (!Terminal().hasTerminal) { - if (defaultOption == null) { - return options.first; - } - return defaultOption; - } - - var displayList = options; - if (fromStart == false) { - // get the last [limit] options - displayList = options.sublist(min(options.length, options.length - limit)); - } - - // on the way in we check that the default value actually exists in the list. - String? defaultAsString; - // display each option. - for (var i = 1; i <= limit; i++) { - final option = displayList[i - 1]; - - if (option == defaultOption) { - defaultAsString = i.toString(); - } - final desc = format(option); - final no = '$i'.padLeft(3); - if (defaultOption != null && defaultOption == option) { - /// highlight the default value. - print(green('$no) $desc')); - } else { - print('$no) $desc'); - } - } - - if (defaultOption != null && defaultAsString == null) { - throw ArgumentError( - "The [defaultOption] $defaultOption doesn't match any " - 'of the passed [options].' - ' Check the == operator for ${options[0].runtimeType}.', - ); - } - - var valid = false; - - var index = -1; - - // loop until the user enters a valid selection. - while (!valid) { - final selected = await ask(prompt, - defaultValue: defaultAsString, - validator: _MenuRange(limit), - customPrompt: (_, __, ___) => customPrompt(prompt, defaultAsString)); - if (selected.isEmpty) { - continue; - } - valid = true; - index = int.parse(selected); - } - - return options[index - 1]; -} - -// ignore: avoid_classes_with_only_static_members -class Menu { - static String defaultPrompt(String prompt, T? defaultValue) { - var result = prompt; - - /// completely suppress the default value and the prompt if - /// the prompt is empty. - if (defaultValue != null && prompt.isNotEmpty) { - result = '$prompt [$defaultValue]'; - } - return result; - } -} - -class _MenuRange extends AskValidator { - const _MenuRange(this.limit); - @override - Future validate(String line, {String? customErrorMessage}) async { - final finalline = line.trim(); - final value = num.tryParse(finalline); - if (value == null) { - throw AskValidatorException( - red(customErrorMessage ?? 'Value must be an integer from 1 to $limit'), - ); - } - - if (value < 1 || value > limit) { - throw AskValidatorException('Invalid selection.'); - } - - return finalline; - } - - final int limit; -} diff --git a/packages/console/lib/src/settings.dart b/packages/console/lib/src/settings.dart deleted file mode 100644 index 9deedf1..0000000 --- a/packages/console/lib/src/settings.dart +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:async'; - -import 'package:logging/logging.dart'; -import 'package:stack_trace/stack_trace.dart'; - -import 'package:protevus_console/core.dart'; - -class Settings { - /// Returns a singleton providing - /// access to DCli settings. - factory Settings() => _self ??= Settings._init(); - - Settings._init(); - - final logger = Logger('dcli'); - static Settings? _self; - - bool _verboseEnabled = false; - - /// returns true if the -v (verbose) flag was set on the - /// dcli command line. - /// e.g. - /// dcli -v clean - bool get isVerbose => _verboseEnabled; - - // ignore: cancel_subscriptions - static StreamSubscription? listener; - - /// Turns on verbose logging. - Future setVerbose({required bool enabled}) async { - _verboseEnabled = enabled; - - // ignore: flutter_style_todos - /// TODO(bsutton): this affects everyones logging so - /// I'm uncertain if this is a problem. - hierarchicalLoggingEnabled = true; - - if (enabled) { - logger.level = Level.INFO; - listener ??= logger.onRecord.listen((record) { - print('${record.level.name}: ${record.time}: ${record.message}'); - }); - } else { - logger.level = Level.OFF; - if (listener != null) { - await listener!.cancel(); - listener = null; - } - } - } - - /// Logs a message to the console if the verbose - /// settings are on. - void verbose(String? message, {Frame? frame}) { - final Frame calledBy; - if (frame == null) { - final st = Trace.current(); - calledBy = st.frames[1]; - } else { - calledBy = frame; - } - - /// We log at info level (as that is logger's default) - /// so that verbose messages will print when verbose - /// is enabled. - Logger('dcli').info('${calledBy.library}:${calledBy.line} $message'); - } - - Stream captureLogOutput() => logger.onRecord; - - void clearLogCapture() { - logger.clearListeners(); - } - - /// True if you are running on a Mac. - bool get isMacOS => DCliPlatform().isMacOS; - - /// True if you are running on a Linux system. - bool get isLinux => DCliPlatform().isLinux; - - /// True if you are running on a Window system. - bool get isWindows => DCliPlatform().isWindows; -} - -/// -/// If Settings.isVerbose is true then -/// this method will call [callback] to -/// get a String which will be logged to the -/// console or the log file set via the verbose command line -/// option. -/// -/// This method is more efficient than calling Settings.verbose -/// as it will only build the string if verbose is enabled. -/// -/// ```dart -/// verbose(() => 'Log the users name $user'); -/// -void verbose(String Function() callback) { - if (Settings().isVerbose) { - Settings().verbose(callback(), frame: Trace.current().frames[1]); - } -} diff --git a/packages/console/lib/src/terminal/ansi.dart b/packages/console/lib/src/terminal/ansi.dart deleted file mode 100644 index 09df6cc..0000000 --- a/packages/console/lib/src/terminal/ansi.dart +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'ansi_color.dart'; -import 'terminal.dart'; - -/// Helper class to assist in printing text to the console with a color. -/// -/// Use one of the color functions instead of this class. -/// -/// See: -/// * [AnsiColor] -/// * [Terminal] -/// ... -class Ansi { - /// Factory ctor - factory Ansi() => _self; - - const Ansi._internal(); - - static const _self = Ansi._internal(); - static bool? _emitAnsi; - - /// returns true if stdout supports ansi escape characters. - static bool get isSupported { - if (_emitAnsi == null) { - // We don't trust [stdout.supportsAnsiEscapes] except on Windows. - // [stdout] relies on the TERM environment variable - // which generates false negatives. - if (!Platform.isWindows) { - _emitAnsi = true; - } else { - _emitAnsi = stdout.supportsAnsiEscapes; - } - } - return _emitAnsi!; - } - - /// You can set [isSupported] to - /// override the detected ansi settings. - /// Dart doesn't do a great job of correctly detecting - /// ansi support so this give a way to override it. - /// If [isSupported] is true then escape charaters are emmitted - /// If [isSupported] is false escape characters are not emmited - /// By default the detected setting is used. - /// After setting emitAnsi you can reset back to the - /// default detected by calling [resetEmitAnsi]. - static set isSupported(bool emit) => _emitAnsi = emit; - - /// If you have called [isSupported] then calling - /// [resetEmitAnsi] will reset the emit - /// setting to the default detected. - static void get resetEmitAnsi => _emitAnsi = null; - - /// ANSI Control Sequence Introducer, signals the terminal for new settings. - static const esc = '\x1b['; - // static const esc = '\u001b['; - - /// Strip all ansi escape sequences from [line]. - /// - /// This method is useful when logging messages - /// or if you need to calculate the number of printable - /// characters in a message. - static String strip(String line) => - line.replaceAll(RegExp('\x1b\\[[0-9;]+m'), ''); -} diff --git a/packages/console/lib/src/terminal/ansi_color.dart b/packages/console/lib/src/terminal/ansi_color.dart deleted file mode 100644 index 2c06962..0000000 --- a/packages/console/lib/src/terminal/ansi_color.dart +++ /dev/null @@ -1,496 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'ansi.dart'; - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(red('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(red('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -/// -String red( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeRed, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(black('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(black('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -/// -String black( - String text, { - AnsiColor background = AnsiColor.white, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeBlack, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(green('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(green('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -/// -String green( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeGreen, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(blue('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(blue('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -/// -String blue( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeBlue, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(yellow('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(yellow('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -/// -String yellow( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeYellow, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(magenta('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(magenta('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -///xt. Defaults to none. -/// -String magenta( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeMagenta, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(cyan('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(cyan('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -///xt. Defaults to none. -/// -String cyan( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeCyan, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(white('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(white('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -///xt. Defaults to none. -/// -String white( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeWhite, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(orange('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(orange('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -///xt. Defaults to none. -/// -String orange( - String text, { - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor(AnsiColor.codeOrange, bold: bold), - text, - background: background, - ); - -/// Wraps the passed text with the ANSI escape sequence for -/// the color red. -/// Use this to control the color of text when printing to the -/// console. -/// -/// ``` -/// print(grey('a dark message')); -/// ``` -/// The [text] to wrap. -/// By default the color is [bold] however you can turn off bold -/// by setting the [bold] argment to false: -/// -/// ``` -/// print(grey('a dark message', bold: false)); -/// ``` -/// -/// [background] is the background color to use when printing the -/// text. Defaults to White. -///xt. Defaults to none. -/// -String grey( - String text, { - double level = 0.5, - AnsiColor background = AnsiColor.none, - bool bold = true, -}) => - AnsiColor._apply( - AnsiColor._grey(level: level, bold: bold), - text, - background: background, - ); - -/// Helper class to assist in printing text to the console with a color. -/// -/// Use one of the color functions instead of this class. -/// -/// See: -/// * [black] -/// * [white] -/// * [green] -/// * [orange] -/// ... -class AnsiColor { - /// - const AnsiColor( - int code, { - bool bold = true, - }) : _code = code, - _bold = bold; - - AnsiColor._grey({ - double level = 0.5, - bool bold = true, - }) : _code = codeGrey + (level.clamp(0.0, 1.0) * 23).round(), - _bold = bold; - - /// resets the color scheme. - static String reset() => _emit(_resetCode); - - /// resets the foreground color - static String fgReset() => _emit(_fgResetCode); - - /// resets the background color. - static String bgReset() => _emit(_bgResetCode); - - final int _code; - - final bool _bold; - - // - static String _emit(String ansicode) => '${Ansi.esc}${ansicode}m'; - - /// ansi code for this color. - int get code => _code; - - /// do we bold the color - bool get bold => _bold; - - /// writes the text to the terminal. - String apply(String text, {AnsiColor background = none}) => - _apply(this, text, background: background); - - static String _apply( - AnsiColor color, - String text, { - AnsiColor background = none, - }) { - String? output; - - if (Ansi.isSupported) { - output = '${_fg(color.code, bold: color.bold)}' - '${_bg(background.code)}$text$_reset'; - } else { - output = text; - } - return output; - } - - static String get _reset => '${Ansi.esc}${_resetCode}m'; - - static String _fg( - int code, { - bool bold = true, - }) { - String output; - - if (code == none.code) { - output = ''; - } else if (code > 39) { - output = '${Ansi.esc}$_fgColorCode$code${bold ? ';1' : ''}m'; - } else { - output = '${Ansi.esc}$code${bold ? ';1' : ''}m'; - } - return output; - } - - // background colors are fg color + 10 - static String _bg(int code) { - String output; - - if (code == none.code) { - output = ''; - } else if (code > 49) { - output = '${Ansi.esc}$_backgroundCode${code + 10}m'; - } else { - output = '${Ansi.esc}${code + 10}m'; - } - return output; - } - - /// Resets - - /// Reset fg and bg colors - static const String _resetCode = '0'; - - /// Defaults the terminal's fg color without altering the bg. - static const String _fgResetCode = '39'; - - /// Defaults the terminal's bg color without altering the fg. - static const String _bgResetCode = '49'; - - // emit this code followed by a color code to set the fg color - static const String _fgColorCode = '38;5;'; - -// emit this code followed by a color code to set the fg color - static const String _backgroundCode = '48;5;'; - - /// code for black - static const int codeBlack = 30; - - /// code for red - static const int codeRed = 31; - - /// code for green - static const int codeGreen = 32; - - /// code for yellow - static const int codeYellow = 33; - - /// code for blue - static const int codeBlue = 34; - - /// code for magenta - static const int codeMagenta = 35; - - /// code for cyan - static const int codeCyan = 36; - - /// code for white - static const int codeWhite = 37; - - /// code for orange - static const int codeOrange = 208; - - /// code for grey - static const int codeGrey = 232; - - /// Colors - /// black - static const AnsiColor black = AnsiColor(codeBlack); - - /// red - static const AnsiColor red = AnsiColor(codeRed); - - /// green - static const AnsiColor green = AnsiColor(codeGreen); - - /// yellow - static const AnsiColor yellow = AnsiColor(codeYellow); - - /// blue - static const AnsiColor blue = AnsiColor(codeBlue); - - /// magenta - static const AnsiColor magenta = AnsiColor(codeMagenta); - - /// cyan - static const AnsiColor cyan = AnsiColor(codeCyan); - - /// white - static const AnsiColor white = AnsiColor(codeWhite); - - /// orange - static const AnsiColor orange = AnsiColor(codeOrange); - - /// passing this as the background color will cause - /// the background code to be suppressed resulting - /// in the default background color. - static const AnsiColor none = AnsiColor(-1, bold: false); -} diff --git a/packages/console/lib/src/terminal/format.dart b/packages/console/lib/src/terminal/format.dart deleted file mode 100644 index 63245bb..0000000 --- a/packages/console/lib/src/terminal/format.dart +++ /dev/null @@ -1,190 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:math'; - -/// provides a random collection of formatters -/// EXPERIMENTAL -/// -class Format { - /// Factory constructor. - factory Format() => _self; - Format._internal(); - - static final _self = Format._internal(); - - /// [cols] is a list of strings (the columns) that - /// are to be formatted as a set of fixed with - /// columns. - /// - /// As this is a fixed width row colums that exceed the given - /// width will be clipped. - /// You can make any column variable width by passing -1 as the width. - /// - /// By default there is a single space between each column you - /// can pass a [delimiter] to modify this behaviour. You can - /// suppress the [delimiter] by passing an empty string ''. - /// - /// [widths] defines the width of each column in the row. - /// - /// If their are more [cols] than [widths] then the last width - /// is used repeatedly. - /// If [widths] is null then a default width of 20 is used. - /// - /// returns a string with each of the columns padded according to the - /// [widths]. - /// - /// - String row( - List cols, { - List? widths, - List? alignments, - String? delimiter, - }) { - var row = ''; - var i = 0; - widths ??= [20]; - var width = widths[0]; - - alignments ??= [TableAlignment.left]; - var alignment = alignments[0]; - - delimiter ??= ' '; - - for (var col in cols) { - if (row.isNotEmpty) { - row += delimiter; - } - - /// make row robust if a null col is passed. - col ??= ''; - var colwidth = col.length; - if (colwidth > width) { - colwidth = width; - if (width != -1) { - col = col.substring(0, width); - } - } - switch (alignment) { - case TableAlignment.left: - row += col.padRight(width); - break; - case TableAlignment.right: - row += col.padLeft(width); - break; - case TableAlignment.middle: - final padding = width = colwidth; - row += col.padLeft(padding); - break; - } - - i++; - - if (i < widths.length) { - width = widths[i]; - } - if (i < alignments.length) { - alignment = alignments[i]; - } - } - return row; - } - - /// Limits the [display] string's length to [width] by removing the centre - /// components of the string and replacing them with '...' - /// - /// Example: - /// var long = 'http://www.onepub.dev/some/long/url'; - /// print(limitString(long, width: 20)) - /// > http://...ong/url - String limitString(String display, {int width = 40}) { - if (display.length <= width) { - return display; - } - final elipses = width <= 2 ? 1 : 3; - final partLength = (width - elipses) ~/ 2; - // ignore: lines_longer_than_80_chars - return '${display.substring(0, partLength)}${'.' * elipses}${display.substring(display.length - partLength)}'; - } - - /// returns a double as a percentage to the given [precision] - /// e.g. 0.11 becomes 11% if [precision] is 0. - String percentage(double progress, int precision) => - '${(progress * 100).toStringAsFixed(precision)}%'; - - /// returns the the number of [bytes] in a human readable - /// form. e.g. 1e+5 3.000T, 10.00G, 100.0M, 20.00K, 10B - /// - /// Except for absurdly large no. (> 10^20) - /// the return is guarenteed to be 6 characters long. - /// For no. < 1000K we right pad the no. with spaces. - String bytesAsReadable(int bytes, {bool pad = true}) { - String human; - - if (bytes < 1000) { - human = _fiveDigits(bytes, 0, 'B', pad: pad); - } else if (bytes < 1000000) { - human = _fiveDigits(bytes, 3, 'K', pad: pad); - } else if (bytes < 1000000000) { - human = _fiveDigits(bytes, 6, 'M', pad: pad); - } else if (bytes < 1000000000000) { - human = _fiveDigits(bytes, 9, 'G', pad: pad); - } else if (bytes < 1000000000000000) { - human = _fiveDigits(bytes, 12, 'T', pad: pad); - } else { - human = bytes.toStringAsExponential(0); - } - return human; - } - - String _fiveDigits(int bytes, int exponent, String letter, - {bool pad = true}) { - final num result; - String human; - if (bytes < 1000) { - // less than 1K we display integers only - result = bytes ~/ pow(10, exponent); - human = '$result'.padLeft(pad ? 5 : 0); - } else { - // greater than 1K we display decimals - result = bytes / pow(10, exponent); - human = '$result'; - - if (human.length > 5) { - human = human.substring(0, 5); - } else { - /// add trailing zeros to maintain a fixed width of 5 chars. - if (pad) { - human = human.padRight(5, '0'); - } - } - } - - return '$human$letter'; - } - - // /// - // void colprint(String label, String value, {int pad = 25}) { - // print('${label.padRight(pad)}: ${value}'); - // } -} - -/// Used by [Format.row] to control the alignment of each -/// column in the table. -enum TableAlignment { - /// - left, - - /// - right, - - /// - middle -} diff --git a/packages/console/lib/src/terminal/terminal.dart b/packages/console/lib/src/terminal/terminal.dart deleted file mode 100644 index f49a1b0..0000000 --- a/packages/console/lib/src/terminal/terminal.dart +++ /dev/null @@ -1,245 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'package:dart_console/dart_console.dart'; - -import 'ansi.dart'; - -/// -/// Modes available when clearing a screen or line. -/// -/// When used with clearScreen: -/// [all] - clears the entire screen -/// [fromCursor] - clears from the cursor until the end of the screen -/// [toCursor] - clears from the start of the screen to the cursor. -/// -/// When used with clearLine: -/// [all] - clears the entire line -/// [fromCursor] - clears from the cursor until the end of the line. -/// [toCursor] - clears from the start of the line to the cursor. -/// -enum TerminalClearMode { - // scrollback, - /// clear whole screen - all, - - /// clear screen from the cursor to the bottom of the screen. - fromCursor, - - /// clear screen from the top of the screen to the cursor - toCursor -} - -/// Provides access to the Ansi Terminal. -class Terminal { - /// Factory ctor to get a Terminal - factory Terminal() => _self; - - // ignore: flutter_style_todos - /// TODO(bsutton): if we don't have a terminal or ansi isn't supported - /// we need to suppress any ansi codes being output. - - Terminal._internal(); - - static final _self = Terminal._internal(); - - final _console = Console(); - - /// Returns true if ansi escape characters are supported. - bool get isAnsi => Ansi.isSupported; - - /// Clears the screen. - /// If ansi escape sequences are not supported this is a no op. - /// This call does not update the cursor position so in most - /// cases you will want to call [home] after calling [clearScreen]. - /// ```dart - /// Terminal() - /// ..clearScreen() - /// ..home(); - /// ``` - void clearScreen({TerminalClearMode mode = TerminalClearMode.all}) { - switch (mode) { - // case AnsiClearMode.scrollback: - // write('${esc}3J', newline: false); - // break; - - case TerminalClearMode.all: - _console.clearScreen(); - break; - case TerminalClearMode.fromCursor: - write('${Ansi.esc}0Jm'); - break; - case TerminalClearMode.toCursor: - write('${Ansi.esc}1Jm'); - break; - } - } - - /// Clears the current line, moves the cursor to column 0 - /// and then prints [text] effectively overwriting the current - /// console line. - /// If the current console doesn't support ansi escape - /// sequences ([isAnsi] == false) then this call - /// will simply revert to calling [print]. - void overwriteLine(String text) { - clearLine(); - column = 0; - _console.write(text); - } - - /// Writes [text] to the terminal at the current - /// cursor location without appending a newline character. - void write(String text) { - _console.write(text); - } - - /// Writes [text] to the console followed by a newline. - /// You can control the alignment of [text] by passing the optional - /// [alignment] argument which defaults to left alignment. - /// The alignment is based on the current terminals width with - /// spaces inserted to the left of the string to facilitate the alignment. - /// Make certain the current line is clear and the cursor is at column 0 - /// before calling this method otherwise the alignment will not work - /// as expected. - void writeLine(String text, {TextAlignment alignment = TextAlignment.left}) => - _console.writeLine(text, alignment); - - /// Clears the current console line without moving the cursor. - /// If you want to write over the current line then - /// call [clearLine] followed by [startOfLine] and then - /// use [write] rather than print as it will leave - /// the cursor on the current line. - /// Alternatively use [overwriteLine]; - void clearLine({TerminalClearMode mode = TerminalClearMode.all}) { - switch (mode) { - // case AnsiClearMode.scrollback: - case TerminalClearMode.all: - _console.eraseLine(); - break; - case TerminalClearMode.fromCursor: - _console.eraseCursorToEnd(); - break; - case TerminalClearMode.toCursor: - write('${Ansi.esc}1K'); - break; - } - } - - /// show/hide the cursor - void showCursor({required bool show}) { - if (show) { - _console.showCursor(); - } else { - _console.hideCursor(); - } - } - - /// Moves the cursor to the start of previous line. - @Deprecated('Use [cursorUp]') - static void previousLine() { - Terminal().cursorUp(); - } - - /// Moves the cursor up one row - void cursorUp() => _console.cursorUp(); - - /// Moves the cursor down one row - void cursorDown() => _console.cursorDown(); - - /// Moves the cursor to the left one column - void cursorLeft() => _console.cursorUp(); - - /// Moves the cursor to the right one column - void cursorRight() => _console.cursorRight(); - - /// Returns the column location of the cursor - int get column => _cursor?.col ?? 0; - - /// moves the cursor to the given column - /// 0 is the first column - // ignore: avoid_setters_without_getters - set column(int column) { - _console.cursorPosition = Coordinate(row, column); - } - - /// Moves the cursor to the start of line. - void startOfLine() { - column = 0; - } - - /// The width of the terminal in columns. - /// Where a column is one character wide. - /// If no terminal is attached, a value of 80 is returned. - /// This value can change if the user resizes the console window. - int get columns { - if (hasTerminal) { - return _console.windowWidth; - } else { - return 80; - } - } - - /// Returns the row location of the cursor. - /// The first row is row 0. - int get row => _cursor?.row ?? 24; - - /// moves the cursor to the given row - /// 0 is the first row - set row(int row) { - _console.cursorPosition = Coordinate(row, column); - } - - /// Returns the current co-ordinates of the cursor. - /// If no terminal is attached we return null - /// as attempting to read from from stdin to - /// obtain the cursor will hang the app. - Coordinate? get _cursor { - if (hasTerminal && Ansi.isSupported) { - try { - return _console.cursorPosition; - // ignore: avoid_catching_errors - } on RangeError catch (_) { - // if stdin is closed (seems to be within docker) - // then the call to cursorPosition will fail. - // RangeError: Invalid value: Not in inclusive range 0..1114111: -1 - // new String.fromCharCode (dart:core-patch/string_patch.dart:45) - // Console.cursorPosition (package:dart_console/src/console.dart:304) - return null; - } - } else { - return null; - } - } - - /// Whether a terminal is attached to stdin. - bool get hasTerminal => stdin.hasTerminal; - - /// The height of the terminal in rows. - /// Where a row is one character high. - /// If no terminal is attached to stdout, a [StdoutException] is thrown. - /// This value can change if the users resizes the console window. - int get rows { - if (hasTerminal) { - return _console.windowHeight; - } else { - return 24; - } - } - - /// Sets the cursor to the top left corner - /// of the screen (0,0) - void home() => _console.resetCursorPosition(); - - /// Returns the current console height in rows. - @Deprecated('Use rows') - int get lines => rows; -} diff --git a/packages/console/lib/src/utils/async_circular_buffer.dart b/packages/console/lib/src/utils/async_circular_buffer.dart deleted file mode 100644 index e041a36..0000000 --- a/packages/console/lib/src/utils/async_circular_buffer.dart +++ /dev/null @@ -1,327 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import 'dart:async'; - -import 'dart:math'; - -/// A [AsyncCircularBuffer] with a fixed capacity supporting -/// all [List] operations -/// -/// ```dart -/// final buffer = CircularBuffer(3)..add(1)..add(2); -/// print(buffer.length); // 2 -/// print(buffer.first); // 1 -/// print(buffer.isFilled); // false -/// print(buffer.isUnfilled); // true -/// -/// buffer.add(3); -/// print(buffer.length); // 3 -/// print(buffer.isFilled); // true -/// print(buffer.isUnfilled); // false -/// -/// buffer.add(4); -/// print(buffer.first); // 4 -/// ``` -class AsyncCircularBuffer -// with IterableMixin> -// implements Iterable> -// with ListMixin -{ - /// Creates a [AsyncCircularBuffer] with a `capacity` - AsyncCircularBuffer(int capacity) - : assert(capacity > 1, 'capacity must be at least 1'), - _capacity = capacity, - _buf = [], - _end = -1, - _count = 0, - _threshold = max(1, (0.2 * capacity).toInt()); - - /// Creates a [AsyncCircularBuffer] based on another `list` - AsyncCircularBuffer.of(List list, [int? capacity]) - : assert(capacity == null || capacity >= list.length, - 'capacity must be null or greater than list'), - _capacity = capacity ?? list.length, - _buf = [...list], - _end = list.length - 1, - _count = list.length, - _threshold = max(1, 0.2 * (capacity ?? list.length) as int); - - final List _buf; - final int _capacity; - - final int _threshold; - - /// Space is available in the buffer. - /// Each time the buffer fills we won't mark - /// space available until the buffer has - /// bee read down to the [_threshold]. - var _spaceAvailable = Completer(); - - /// Elements are available in the buffer. - var _elementsAvailable = Completer(); - - /// indicates that the buffer has been closed by the provider. - /// We complete the future once the buffer closes. - final _closed = Completer(); - - /// indicates that the buffer has been closed and - /// all elements read. - final _done = Completer(); - - int _start = 0; - int _end; - int _count; - - /// Completes when the buffer has been closed - /// and all elements read. - Future get isDone => _done.future; - - /// The [AsyncCircularBuffer] is `reset` - void reset() { - if (_closed.isCompleted) { - throw BadStateException('Buffer has been closed'); - } - _start = 0; - _end = -1; - _count = 0; - - _spaceAvailable.complete(true); - _elementsAvailable = Completer(); - } - - /// Close the circular buffer indicating no more values will be added. - /// Calls to get will throw with an underflow exception if called - /// when there are no more elements in the buffer and [close] has bee - /// called. - void close() { - if (!_closed.isCompleted) { - _closed.complete(true); - } - - if (_isEmpty && !_done.isCompleted) { - _done.complete(true); - } - } - - /// Adds [element] to the buffer. - /// This method will wait if the buffer is full. - /// An [BadStateException] will be thrown if the buffer - /// has been closed. - Future add(T element) async { - if (_closed.isCompleted) { - throw BadStateException('Buffer has been closed'); - } - if (isFilled) { - /// wait until we have more space available. - /// If the buffer was closed after we start waiting - /// we still allow this last add to continue. - await _spaceAvailable.future; - } - - // Adding the next value - _end++; - if (_end == _capacity) { - _end = 0; - } - - if (_buf.length == _capacity) { - /// [_buf] is full grown so add at [_end] - _buf[_end] = element; - } else { - /// [_buf] isn't full grown yet so grow it. - _buf.add(element); - } - _count++; - - if (isFilled) { - /// we are full so block add - _spaceAvailable = Completer(); - } - // _incStart(); - if (!_elementsAvailable.isCompleted) { - /// we now have elements available. - _elementsAvailable.complete(true); - } - } - - void _incStart() { - _start++; - if (_start == _capacity) { - _start = 0; - } - } - - /// Returns the next element in the buffer. - /// If the buffer is closed and empty then returns null. - /// If the buffer is empty [get] waits until a new - /// element arrives before returning. - Future get() async { - if (_isEmpty) { - if (_closed.isCompleted) { - return throw UnderflowException(); - } else { - await Future.any([_elementsAvailable.future, _closed.future]); - if (_closed.isCompleted && _isEmpty) { - return throw UnderflowException(); - } - } - } - final element = this[0]; - _incStart(); - _count--; - - /// we have less items than threshold so allow more - /// items to be added. - if (length < _threshold && !_spaceAvailable.isCompleted) { - _spaceAvailable.complete(true); - } - - if (_isEmpty) { - /// no elements available so block futher gets. - _elementsAvailable = Completer(); - - /// we are closed and empty. - if (_closed.isCompleted) { - _done.complete(true); - } - } - - return element; - } - - /// iterator over the list of elements. - Iterator> get iterator => _CircularBufferIterator(this); - - /// Number of elements of [AsyncCircularBuffer] - int get length => _count; - - /// Maximum number of elements of [AsyncCircularBuffer] - int get capacity => _capacity; - - /// The [AsyncCircularBuffer] `isFilled` if the `length` - /// is equal to the `capacity` - bool get isFilled => _count == _capacity; - - /// The [AsyncCircularBuffer] `isUnfilled` if the `length` is - /// is less than the `capacity` - bool get isUnfilled => _count < _capacity; - - /// True if the buffer is closed and will not - /// accept any more calls to [add] - bool get isClosed => _closed.isCompleted; - - bool get _isEmpty => _count == 0; - - bool get _isNotEmpty => _count > 0; - - /// Access element at [index] - T operator [](int index) { - if (index >= 0 && index < _count) { - return _buf[(_start + index) % _buf.length]!; - } - throw RangeError.index(index, this); - } - - /// Assign an element at [index] - void operator []=(int index, T value) { - if (index >= 0 && index < _count) { - _buf[(_start + index) % _buf.length] = value; - } else { - throw RangeError.index(index, this); - } - } - - /// Returns a stream of the contained elements. - Stream stream() async* { - try { - while (!_closed.isCompleted) { - final element = await get(); - yield element; - } - } on UnderflowException catch (_) { - // if we are closed whilst waiting for get we get an [UnderFlowException] - // Nothing to do here as the stream will just end naturally. - } - } - - /// The `length` mutation is forbidden - set length(int newLength) { - throw UnsupportedError('Cannot resize immutable CircularBuffer.'); - } - - @override - String toString() { - final sb = StringBuffer()..write('['); - for (var i = 0; i < length; i++) { - if (sb.length != 1) { - sb.write(','); - } - sb.write('${this[i]}'); - } - sb.write(']'); - return sb.toString(); - } - - /// empties the buffer, discarding all elements. - Future drain() async { - while (_isNotEmpty) { - await get(); - } - } -} - -class _CircularBufferIterator implements Iterator> { - /// - _CircularBufferIterator(this._buffer); - // Iterate over odd numbers - final AsyncCircularBuffer _buffer; - - /// - @override - bool moveNext() => !(_buffer._closed.isCompleted && _buffer._isEmpty); - - /// will throw an [UnderflowException] if the buffer is - /// empty and closed. - @override - Future get current async { - final element = await _buffer.get(); - - if (element == null) { - throw UnderflowException(); - } - - return element; - } -} - -/// An attempt was made to access the buffer when it was closed. -class BadStateException implements Exception { - /// An attempt was made to access the buffer when it was closed. - BadStateException(this.message); - - /// The message. - String message; - - @override - String toString() => message; -} - -/// An attempt was made to access the buffer when -/// it was closed and empty. -class UnderflowException implements Exception { - /// An attempt was made to access the buffer when - /// it was closed and empty. - UnderflowException(); - - /// the error message. - String get message => 'The buffer is closed and empty'; - @override - String toString() => message; -} diff --git a/packages/console/lib/src/utils/dcli_exception.dart b/packages/console/lib/src/utils/dcli_exception.dart deleted file mode 100644 index 0bed212..0000000 --- a/packages/console/lib/src/utils/dcli_exception.dart +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:convert'; - -import 'package:stack_trace/stack_trace.dart'; - -/// Base class for all DCli exceptions. -class DCliException implements Exception { - /// - DCliException(this.message, [Trace? stackTrace]) - : cause = null, - stackTrace = stackTrace ?? Trace.current(2); - - // Factory method to create DCliException from a JSON string - factory DCliException.fromJson(String jsonStr) { - final jsonMap = jsonDecode(jsonStr) as Map; - - return DCliException._( - jsonMap['message'] as String, - jsonMap['cause'] as String, - Trace.parse(jsonMap['stackTrace'] as String), - ); - } - - DCliException._(this.message, this.cause, [Trace? stackTrace]) - : stackTrace = stackTrace ?? Trace.current(2); - - /// - DCliException.from(this.cause, this.stackTrace) : message = cause.toString(); - - /// - DCliException.fromException(this.cause) - : message = cause.toString(), - stackTrace = Trace.current(2); - - /// - final String message; - - /// If DCliException is wrapping another exception then this is the - /// exeception that is wrapped. - final Object? cause; - - /// - Trace stackTrace; - - // { - // return DCliException(this.message, stackTrace); - // } - - @override - String toString() => message; - - /// - void printStackTrace() { - print(stackTrace.terse); - } - - Map toJson() => { - 'message': message, - 'cause': cause?.toString(), - 'stackTrace': stackTrace.toString(), - }; - - // Method to convert DCliException to a JSON string - String toJsonString() => jsonEncode(toJson()); -} diff --git a/packages/console/lib/src/utils/dcli_platform.dart b/packages/console/lib/src/utils/dcli_platform.dart deleted file mode 100644 index a54a8a3..0000000 --- a/packages/console/lib/src/utils/dcli_platform.dart +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'package:scope/scope.dart'; - -/// [DCliPlatform] exists so we can scope -/// Platform in unit tests to return non-standard results -/// e.g. isWindows == true on a linux platform -class DCliPlatform { - /// Returns a singleton providing - /// access to DCli settings. - factory DCliPlatform() { - if (Scope.hasScopeKey(scopeKey)) { - return Scope.use(scopeKey); - } else { - return _self ??= DCliPlatform._internal(); - } - } - - /// To use this method create a [Scope] and inject this - /// as a value into the scope. - factory DCliPlatform.forScope({DCliPlatformOS? overriddenPlatform}) => - DCliPlatform._internal(overriddenPlatform: overriddenPlatform); - - DCliPlatform._internal({this.overriddenPlatform}); - - static ScopeKey scopeKey = ScopeKey(); - - static DCliPlatform? _self; - - DCliPlatformOS? overriddenPlatform; - - /// True if you are running on a Mac. - bool get isMacOS => overriddenPlatform == null - ? Platform.isMacOS - : overriddenPlatform == DCliPlatformOS.macos; - - /// True if you are running on a Linux system. - bool get isLinux => overriddenPlatform == null - ? Platform.isLinux - : overriddenPlatform == DCliPlatformOS.linux; - - /// True if you are running on a Window system. - bool get isWindows => overriddenPlatform == null - ? Platform.isWindows - : overriddenPlatform == DCliPlatformOS.windows; -} - -enum DCliPlatformOS { windows, linux, macos } diff --git a/packages/console/lib/src/utils/dev_null.dart b/packages/console/lib/src/utils/dev_null.dart deleted file mode 100644 index 09a6d59..0000000 --- a/packages/console/lib/src/utils/dev_null.dart +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// -/// devNull is a convenience function which you can use -/// if you want to ignore the output of a LineAction. -/// Its typical useage is a forEach where you don't want -/// to see any stdout but you still want to see errors -/// printed to stderr. -/// -/// ```dart -/// 'git pull'.forEach(devNull, stderr: (line) => printerr(line)); -/// ``` -/// -/// use this to consume the output. -void devNull(String? line) {} diff --git a/packages/console/lib/src/utils/file.dart b/packages/console/lib/src/utils/file.dart deleted file mode 100644 index a85de5f..0000000 --- a/packages/console/lib/src/utils/file.dart +++ /dev/null @@ -1,232 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:async'; -import 'dart:io'; - -import 'package:crypto/crypto.dart'; -import 'package:path/path.dart'; -import 'package:uuid/uuid.dart'; -import 'package:protevus_console/core.dart'; - -/// Opens a File and calls [action] passing in the open file. -/// When action completes the file is closed. -/// Use this method in preference to directly callling [FileSync()] -Future withOpenFile( - String pathToFile, - R Function(RandomAccessFile) action, { - FileMode fileMode = FileMode.writeOnlyAppend, -}) async { - final raf = File(pathToFile).openSync(mode: fileMode); - - R result; - try { - result = action(raf); - } finally { - await raf.close(); - } - return result; -} - -/// -/// Creates a link at [linkPath] which points to an -/// existing file or directory at [existingPath] -/// -/// On Windows you need to be in developer mode or running as an Administrator -/// to create a symlink. -/// -/// To enable developer mode see: -/// https://dcli.onepub.dev/getting-started/installing-on-windows -/// -/// To check if your script is running as an administrator use: -/// -/// [Shell.current.isPrivileged] -/// -void symlink( - String existingPath, - String linkPath, -) { - verbose(() => 'symlink existingPath: $existingPath linkPath $linkPath'); - Link(linkPath).createSync(existingPath); -} - -/// -/// Deletes the symlink at [linkPath] -/// -/// On Windows you need to be in developer mode or running as an Administrator -/// to delete a symlink. -/// -/// To enable developer mode see: -/// https://dcli.onepub.dev/getting-started/installing-on-windows -/// -/// To check if your script is running as an administrator use: -/// -/// [Shell.current.isPrivileged] -/// -void deleteSymlink(String linkPath) { - verbose(() => 'deleteSymlink linkPath: $linkPath'); - Link(linkPath).deleteSync(); -} - -/// -/// Resolves the a symbolic link [pathToLink] -/// to the ultimate target path. -/// -/// The return path will be canonicalized. -/// -/// e.g. -/// ```dart -/// resolveSymLink('/usr/bin/dart) == '/usr/lib/bin/dart' -/// ``` -/// -/// throws a FileSystemException if the target path does not exist. -String resolveSymLink(String pathToLink) { - final normalised = canonicalize(pathToLink); - - String resolved; - if (isDirectory(normalised)) { - resolved = Directory(normalised).resolveSymbolicLinksSync(); - } else { - resolved = canonicalize(File(normalised).resolveSymbolicLinksSync()); - } - - verbose(() => 'resolveSymLink $pathToLink resolved: $resolved'); - return resolved; -} - -/// -/// -/// Returns a FileStat instance describing the -/// file or directory located by [path]. -/// -FileStat stat(String path) => File(path).statSync(); - -/// Generates a temporary filename in [pathToTempDir] -/// or if inTempDir os not passed then in -/// the system temp directory. -/// The generated filename is is guaranteed to be globally unique. -/// -/// This method does NOT create the file. -/// -/// The temp file name will be .tmp -/// unless you provide a [suffix] in which -/// case the file name will be . -String createTempFilename({String? suffix, String? pathToTempDir}) { - var finalsuffix = suffix ?? 'tmp'; - - if (!finalsuffix.startsWith('.')) { - finalsuffix = '.$finalsuffix'; - } - pathToTempDir ??= Directory.systemTemp.path; - const uuid = Uuid(); - return '${join(pathToTempDir, uuid.v4())}$finalsuffix'; -} - -/// Generates a temporary filename in the system temp directory -/// that is guaranteed to be unique. -/// -/// This method does not create the file. -/// -/// The temp file name will be .tmp -/// unless you provide a [suffix] in which -/// case the file name will be . -String createTempFile({String? suffix}) { - final filename = createTempFilename(suffix: suffix); - touch(filename, create: true); - return filename; -} - -/// Returns the length of the file at [pathToFile] in bytes. -int fileLength(String pathToFile) => File(pathToFile).lengthSync(); - -/// Creates a temp file and then calls [action]. -/// -/// Once [action] completes the temporary file will be deleted. -/// -/// The [action]s return value [R] is returned from the [withTempFileAsync] -/// function. -/// -/// If [create] is true (default true) then the temp file will be -/// created. If [create] is false then just the name will be -/// generated. -/// -/// if [pathToTempDir] is passed then the file will be created in that -/// directory otherwise the file will be created in the system -/// temp directory. -/// -/// The temp file name will be .tmp -/// unless you provide a [suffix] in which -/// case the file name will be . -Future withTempFileAsync( - Future Function(String tempFile) action, { - String? suffix, - String? pathToTempDir, - bool create = true, - bool keep = false, -}) async { - final tmp = createTempFilename(suffix: suffix, pathToTempDir: pathToTempDir); - if (create) { - touch(tmp, create: true); - } - - R result; - try { - result = await action(tmp); - } finally { - if (exists(tmp) && !keep) { - delete(tmp); - } - } - return result; -} - -Digest calculateHash(String path) { - if (!exists(path)) { - throw FileNotFoundException(path); - } - final file = File(path); - var digest = Digest([0]); - - if (file.lengthSync() == 0) { - return digest; - } - - const blockSize = 8192; // Set the desired block size (e.g., 8 KB) - const hasher = sha256; - - final randomAccessFile = file.openSync(); - final chunk = List.filled(blockSize, 0); - int bytesRead; - - while ((bytesRead = randomAccessFile.readIntoSync(chunk)) > 0) { - digest = md5.convert([...digest.bytes, ...chunk.sublist(0, bytesRead)]); - } - - randomAccessFile.closeSync(); - - final digestAsString = hasher.toString(); - verbose(() => 'calculateHash($path) = $digestAsString'); - - return digest; -} - -/// Thrown when a file doesn't exist -class FileNotFoundException extends DCliException { - /// Thrown when a file doesn't exist - FileNotFoundException(String path) - : super('The file ${truepath(path)} does not exist.'); -} - -/// Thrown when a path is not a file. -class NotAFileException extends DCliException { - /// Thrown when a path is not a file. - NotAFileException(String path) - : super('The path ${truepath(path)} is not a file.'); -} diff --git a/packages/console/lib/src/utils/limited_stream_controller.dart b/packages/console/lib/src/utils/limited_stream_controller.dart deleted file mode 100644 index 32e3378..0000000 --- a/packages/console/lib/src/utils/limited_stream_controller.dart +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:async'; - -/// A specialized StreamController that limits the no. -/// of elements that can be in the stream. -class LimitedStreamController implements StreamController { - /// Creates a new [LimitedStreamController] that limits the no. - /// of elements that can be in the queue. - LimitedStreamController(this._limit, - {void Function()? onListen, void Function()? onCancel, bool sync = false}) - : _streamController = StreamController( - onListen: onListen, onCancel: onCancel, sync: sync); - - final StreamController _streamController; - - final int _limit; - - /// Tracks the no. of elements in the stream. - var _count = 0; - - /// Used to indicate when the stream is full - var _spaceAvailable = Completer(); - - /// Returns the no. of elements waiting in the stream. - int get length => _count; // _buffer.length; - - @override - bool get isClosed => _streamController.isClosed; - - @override - bool get hasListener => _streamController.hasListener; - - @override - bool get isPaused => _streamController.isPaused; - - @Deprecated('Use asyncAdd') - @override - void add(T event) { - throw UnsupportedError('Use asyncAdd'); - } - - /// Add an event to the stream. If the - /// stream is full then this method will - /// wait until there is room. - Future asyncAdd(T event) async { - if (_count >= _limit) { - await _spaceAvailable.future; - } - _count++; - _streamController.add(event); - - if (_count >= _limit) { - _spaceAvailable = Completer(); - } - } - - @override - Stream get stream async* { - /// return _buffer.stream(); - await for (final element in _streamController.stream) { - _count--; - - if (_count < _limit && !_spaceAvailable.isCompleted) { - /// notify that we have space available - _spaceAvailable.complete(true); - } - yield element; - } - } - - @override - void addError(Object error, [StackTrace? stackTrace]) { - _streamController.addError(error, stackTrace); - } - - @override - Future addStream(Stream source, {bool? cancelOnError = true}) { - throw UnsupportedError('Use asyncAdd'); - } - - @override - Future close() => _streamController.close(); - - @override - Future get done => _streamController.done; - - @override - StreamSink get sink => throw UnsupportedError('Use asyncAdd'); - - @override - set onListen(void Function()? onListenHandler) { - _streamController.onListen = onListenHandler; - } - - @override - ControllerCallback? get onListen => _streamController.onListen; - - @override - set onPause(void Function()? onPauseHandler) { - _streamController.onPause = onPauseHandler; - } - - @override - ControllerCallback? get onPause => _streamController.onPause; - - @override - set onResume(void Function()? onResumeHandler) { - _streamController.onResume = onResumeHandler; - } - - @override - ControllerCallback? get onResume => _streamController.onResume; - - @override - set onCancel(void Function()? onCancelHandler) { - _streamController.onCancel = onCancelHandler; - } - - @override - ControllerCancelCallback? get onCancel => _streamController.onCancel; -} diff --git a/packages/console/lib/src/utils/line_action.dart b/packages/console/lib/src/utils/line_action.dart deleted file mode 100644 index 73d9946..0000000 --- a/packages/console/lib/src/utils/line_action.dart +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// Typedef for LineActions -typedef LineAction = void Function(String line); - -/// Typedef for cancellable LineActions. -typedef CancelableLineAction = bool Function(String line); diff --git a/packages/console/lib/src/utils/line_file.dart b/packages/console/lib/src/utils/line_file.dart deleted file mode 100644 index 5d24e46..0000000 --- a/packages/console/lib/src/utils/line_file.dart +++ /dev/null @@ -1,188 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:convert'; -import 'dart:io'; - -import 'platform.dart'; - -/// Provide s collection of methods to make it easy -/// to read/write a file line by line. -class LineFile { - /// If you instantiate FileSync you MUST call [close]. - /// - /// We rececommend that you use withOpenFile in prefernce to directly - /// calling this method. - LineFile(String path, {FileMode fileMode = FileMode.writeOnlyAppend}) - : _fileMode = fileMode { - _file = File(path); - } - - final FileMode _fileMode; - late final File _file; - late final RandomAccessFile _raf = _open(_fileMode); - - RandomAccessFile _open(FileMode fileMode) => _file.openSync(mode: fileMode); - - /// - /// Flushes the contents of the file to disk. - void flush() => _raf.flushSync(); - - /// Returns the length of the file in bytes - /// The file does NOT have to be open - /// to determine its length. - int get length => _file.lengthSync(); - - /// Close and flushes the file to disk. - void close() => _raf.closeSync(); - - /// Read file line by line. - void readAll(bool Function(String) handleLine) { - final stream = _file.openSync(); - try { - final splitter = const LineSplitter() - .startChunkedConversion(_CallbackStringSync((str) { - if (!handleLine(str)) { - throw const _StopIteration(); - } - })); - - final decoder = const Utf8Decoder().startChunkedConversion(splitter); - - while (true) { - final bytes = stream.readSync(16 * 1024); - if (bytes.isEmpty) { - break; - } - decoder.add(bytes); - } - decoder.close(); - } on _StopIteration catch (_) { - // Ignore. - } finally { - stream.closeSync(); - } - } - - /// Truncates the file to zero bytes and - /// then writes the given text to the file. - /// If [newline] is null or isn't passed then the platform - /// end of line characters are appended as defined by - /// [Platform().eol]. - /// Pass null or an '' to [newline] to not add a line terminator. - void write(String line, {String? newline}) { - final finalline = line + (newline ?? eol); - _raf - ..truncateSync(0) - ..setPositionSync(0) - ..writeStringSync(finalline) - ..flushSync(); - } - - /// Appends the [line] to the file - /// Appends [newline] after the line. - /// If [newline] is null or isn't passed then the platform - /// end of line characters are appended as defined by - /// [Platform().eol]. - /// Pass null or an '' to [newline] to not add a line terminator. - void append(String line, {String? newline}) { - final finalline = line + (newline ?? eol); - - _raf - ..setPositionSync(_raf.lengthSync()) - ..writeStringSync(finalline); - } - - /// Reads a single line from the file. - /// [lineDelimiter] the end of line delimiter. - /// May be one or two characters long. - /// Defaults to the platform specific delimiter as - /// defined by [Platform().eol]. - /// - String? read({String? lineDelimiter}) { - lineDelimiter ??= eol; - final line = StringBuffer(); - int byte; - var priorChar = ''; - - var foundDelimiter = false; - - while ((byte = _raf.readByteSync()) != -1) { - final char = utf8.decode([byte]); - - if (_isLineDelimiter(priorChar, char, lineDelimiter)) { - foundDelimiter = true; - break; - } - - line.write(char); - priorChar = char; - } - final endOfFile = line.isEmpty && foundDelimiter == false; - return endOfFile ? null : line.toString(); - } - - /// Truncates the file to zero bytes in length. - void truncate() => _raf.truncateSync(0); - - bool _isLineDelimiter(String priorChar, String char, String lineDelimiter) { - if (lineDelimiter.length == 1) { - return char == lineDelimiter; - } else { - return priorChar + char == lineDelimiter; - } - } - - /// Opens the file for random access. - void open() { - /// accessing raf causes the file to open. - // ignore: unnecessary_statements - _raf; - } -} - -/// Opens a File and calls [action] passing in the open [LineFile]. -/// When action completes the file is closed. -/// Use this method in preference to directly callling [FileSync()] -R withOpenLineFile( - String pathToFile, - R Function(LineFile) action, { - FileMode fileMode = FileMode.writeOnlyAppend, -}) { - final file = LineFile(pathToFile, fileMode: fileMode)..open(); - - late R result; - try { - result = action(file); - } finally { - file - ..flush() - ..close(); - } - return result; -} - -class _StopIteration implements Exception { - const _StopIteration(); -} - -class _CallbackStringSync implements Sink { - _CallbackStringSync(this.callback); - - final void Function(String) callback; - - @override - void add(String data) { - callback(data); - } - - @override - void close() {} -} diff --git a/packages/console/lib/src/utils/platform.dart b/packages/console/lib/src/utils/platform.dart deleted file mode 100644 index 0e2f8c0..0000000 --- a/packages/console/lib/src/utils/platform.dart +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:io'; - -import 'package:protevus_console/core.dart'; - -/// Extensions for the Platform class -extension PlatformEx on Platform { - /// Returns the OS specific End Of Line (eol) character. - /// On Windows this is '\r\n' on all other platforms - /// it is '\n'. - /// Usage: Platform().eol - /// - /// Note: you must import both: - /// ```dart - /// import 'dart:io'; - /// import 'package:dcli/dcli.dart'; - /// ``` - String get eol => DCliPlatform().isWindows ? '\r\n' : '\n'; -} - -String get eol => DCliPlatform().isWindows ? '\r\n' : '\n'; diff --git a/packages/console/lib/src/utils/run_exception.dart b/packages/console/lib/src/utils/run_exception.dart deleted file mode 100644 index 950ba64..0000000 --- a/packages/console/lib/src/utils/run_exception.dart +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:convert'; - -import 'package:stack_trace/stack_trace.dart'; - -import 'dcli_exception.dart'; - -/// Thrown when any of the process related method -/// such as .run and .start fail. -class RunException extends DCliException { - /// - RunException( - this.cmdLine, - this.exitCode, - this.reason, { - Trace? stackTrace, - }) : super(reason, stackTrace); - - RunException.fromJson(Map json) - : cmdLine = json['cmdLine'] as String, - exitCode = json['exitCode'] as int, - reason = json['reason'] as String, - super(json['reason'] as String, json['stackTrace'] as Trace); - - factory RunException.fromJsonString(String jsonString) { - final jsonMap = jsonDecode(jsonString) as Map; - return RunException( - jsonMap['cmdLine'] as String, - jsonMap['exitCode'] as int, - jsonMap['reason'] as String, - stackTrace: jsonMap['stackTrace'] != null - ? Trace.parse(jsonMap['stackTrace'] as String) - : null, - ); - } - - /// - RunException.withArgs( - String? cmd, - List args, - this.exitCode, - this.reason, { - Trace? stackTrace, - }) : cmdLine = '$cmd ${args.join(' ')}', - super(reason, stackTrace); - - /// - RunException.fromException( - Object exception, - String? cmd, - List args, { - Trace? stackTrace, - }) : cmdLine = '$cmd ${args.join(' ')}', - reason = exception.toString(), - exitCode = -1, - super(exception.toString(), stackTrace); - - /// The command line that was being run. - String cmdLine; - - /// the exit code of the command. - int? exitCode; - - /// the error. - String reason; - - @override - String get message => ''' -$cmdLine -exit: $exitCode -reason: $reason'''; - - Map toJsonMap() => { - 'cmdLine': cmdLine, - 'exitCode': exitCode, - 'reason': reason, - 'stackTrace': stackTrace, - }; - - @override - String toJsonString() { - final jsonMap = { - 'cmdLine': cmdLine, - 'exitCode': exitCode, - 'reason': reason, - 'stackTrace': stackTrace.toString(), - }; - final json = jsonEncode(jsonMap); - print(json); - return json; - } -} diff --git a/packages/console/lib/src/utils/stack_list.dart b/packages/console/lib/src/utils/stack_list.dart deleted file mode 100644 index bfc14a2..0000000 --- a/packages/console/lib/src/utils/stack_list.dart +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:collection'; - -/// -/// A classic Stack of items with a push and pop method. -/// -class StackList { - /// - StackList(); - - /// - /// Creates a stack from [initialStack] - /// by pushing each element of the list - /// onto the stack from first to last. - StackList.fromList(List initialStack) { - initialStack.forEach(push); - } - - final _stack = Queue(); - - /// - bool get isEmpty => _stack.isEmpty; - - /// push an [item] onto th stack. - void push(T item) { - _stack.addFirst(item); - } - - /// return and remove an item from the stack. - T pop() => _stack.removeFirst(); - - /// returns the item onf the top of the stack - /// but does not remove the item. - T peek() => _stack.first; - - /// The of items in the stack - List asList() => _stack.toList(); -} diff --git a/packages/console/lib/src/utils/truepath.dart b/packages/console/lib/src/utils/truepath.dart deleted file mode 100644 index 469a1f6..0000000 --- a/packages/console/lib/src/utils/truepath.dart +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:path/path.dart'; - -import '../functions/env.dart'; -import '../functions/pwd.dart'; - -/// [truepath] creates an absolute and normalized path. -/// -/// True path provides a safe and consistent manner for -/// manipulating, accessing and displaying paths. -/// -/// Works like [join] in that it concatenates a set of directories -/// into a path. -/// [truepath] then goes on to create an absolute path which -/// is then normalize to remove any segments (.. or .). -/// -String truepath( - String part1, [ - String? part2, - String? part3, - String? part4, - String? part5, - String? part6, - String? part7, -]) => - normalize(absolute(join(part1, part2, part3, part4, part5, part6, part7))); - -/// Removes the users home directory from a path replacing it with ~ -String privatePath( - String part1, [ - String? part2, - String? part3, - String? part4, - String? part5, - String? part6, - String? part7, -]) { - final prefix = rootPrefix(HOME); - var tp = truepath(part1, part2, part3, part4, part5, part6, part7); - if (HOME != '.') { - tp = tp.replaceAll(HOME, '$prefix'); - } - return tp; -} - -/// Returns the root path of your file system. -/// -/// On Linux and MacOS this will be `/` -/// -/// On Windows this will be `'C:\` -/// -/// The drive letter will depend on the -/// drive of your present working directory (pwd). -String get rootPath => rootPrefix(pwd); diff --git a/packages/console/lib/terminal.dart b/packages/console/lib/terminal.dart deleted file mode 100644 index 4f0d5c2..0000000 --- a/packages/console/lib/terminal.dart +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * (C) S. Brett Sutton - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -library; - -export 'src/terminal/ansi.dart'; -export 'src/terminal/ansi_color.dart'; -export 'src/terminal/format.dart'; -export 'src/terminal/terminal.dart'; diff --git a/packages/console/pubspec.yaml b/packages/console/pubspec.yaml index 8a84446..97b662f 100644 --- a/packages/console/pubspec.yaml +++ b/packages/console/pubspec.yaml @@ -10,19 +10,7 @@ environment: # Add regular dependencies here. dependencies: - async: ^2.5.0 - circular_buffer: ^0.11.0 - collection: ^1.15.0 - crypto: ^3.0.1 - dart_console: ^4.0.0 - ffi: ^2.1.0 - logging: ^1.0.2 - meta: ">=1.11.0 <2.0.0" - path: ^1.8.0 - scope: ^4.0.0 - stack_trace: ^1.10.0 - uuid: ^4.1.0 - validators2: ^5.0.0 + # path: ^1.8.0 dev_dependencies: lints: ^3.0.0