Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
SandPod committed May 9, 2024
0 parents commit 22eb354
Show file tree
Hide file tree
Showing 32 changed files with 2,814 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.0.1

- Initial version.
28 changes: 28 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BSD 3-Clause License

Copyright (c) 2024, Serverpod

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
![Serverpod banner](https://github.com/serverpod/serverpod/raw/main/misc/images/github-header.webp)

# CLI Tools

This package contains tools for building great command line interfaces. These tools were developed for the Serverpod CLI but can be used in any Dart project.

## Contributing to the Project

We are happy to accept contributions. To contribute, please do the following:

1. Fork the repository
2. Create a feature branch
3. Commit your changes
4. Push to the branch
5. Create a pull request
6. Discuss and modify the pull request as necessary
7. The pull request will be accepted and merged by the repository owner

Tests are required to accept any pull requests.
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:serverpod_lints/cli.yaml
19 changes: 19 additions & 0 deletions example/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:cli_tools/cli_tools.dart';

void main() async {
/// Simple example of using the [StdOutLogger] class.
var logger = StdOutLogger(LogLevel.info);

logger.info('An info message');
logger.error('An error message');
logger.debug(
'A debug message that will not be shown because log level is info',
);
await logger.progress(
'A progress message',
() async => Future.delayed(
const Duration(seconds: 3),
() => true,
),
);
}
3 changes: 3 additions & 0 deletions lib/analytics.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library analytics;

export 'src/analytics/analytics.dart';
4 changes: 4 additions & 0 deletions lib/better_command_runner.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
library better_command_runner;

export 'src/better_command_runner/better_command_runner.dart';
export 'src/better_command_runner/exit_exception.dart';
7 changes: 7 additions & 0 deletions lib/cli_tools.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
library cli_tools;

export 'analytics.dart';
export 'better_command_runner.dart';
export 'local_storage_manager.dart';
export 'logger.dart';
export 'package_version.dart';
3 changes: 3 additions & 0 deletions lib/local_storage_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library local_storage_manager;

export 'src/local_storage_manager/local_storage_manager.dart';
6 changes: 6 additions & 0 deletions lib/logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
library logger;

export 'src/logger/logger.dart';
export 'src/logger/loggers/std_out_logger.dart';
export 'src/logger/loggers/void_logger.dart';
export 'src/logger/helpers/ansi_style.dart';
4 changes: 4 additions & 0 deletions lib/package_version.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
library package_version;

export 'src/package_version/package_version.dart';
export 'src/package_version/pub_api_client.dart';
81 changes: 81 additions & 0 deletions lib/src/analytics/analytics.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import 'dart:convert';
import 'dart:io';

import 'package:ci/ci.dart' as ci;
import 'package:http/http.dart' as http;

/// Interface for analytics services.
abstract interface class Analytics {
/// Clean up resources.
void cleanUp();

/// Track an event.
void track({
required String event,
});
}

/// Analytics service for MixPanel.
class MixPanelAnalytics implements Analytics {
final String _uniqueUserId;
final String _endpoint = 'https://api.mixpanel.com/track';
final String _projectToken;
final String _version;

MixPanelAnalytics({
required String uniqueUserId,
required String projectToken,
required String version,
}) : _uniqueUserId = uniqueUserId,
_projectToken = projectToken,
_version = version;

@override
void cleanUp() {}

@override
void track({
required String event,
}) {
var payload = jsonEncode({
'event': event,
'properties': {
'distinct_id': _uniqueUserId,
'token': _projectToken,
'platform': _getPlatform(),
'dart_version': Platform.version,
'is_ci': ci.isCI,
'version': _version,
}
});

_quietPost(payload);
}

String _getPlatform() {
if (Platform.isMacOS) {
return 'MacOS';
} else if (Platform.isWindows) {
return 'Windows';
} else if (Platform.isLinux) {
return 'Linux';
} else {
return 'Unknown';
}
}

Future<void> _quietPost(String payload) async {
try {
await http.post(
Uri.parse(_endpoint),
body: 'data=$payload',
headers: {
'Accept': 'text/plain',
'Content-Type': 'application/x-www-form-urlencoded',
},
).timeout(const Duration(seconds: 2));
} catch (e) {
return;
}
}
}
181 changes: 181 additions & 0 deletions lib/src/better_command_runner/better_command_runner.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:cli_tools/src/better_command_runner/exit_exception.dart';

/// A function type for executing code before running a command.
typedef OnBeforeRunCommand = Future<void> Function(
BetterCommandRunner runner,
);

/// A function type for passing log messages.
typedef PassMessage = void Function(String message);

/// A function type for setting the log level.
/// The [logLevel] is the log level to set.
/// The [commandName] is the name of the command if custom rules for log
/// levels are needed.
typedef SetLogLevel = void Function({
required CommandRunnerLogLevel parsedLogLevel,
String? commandName,
});

/// A function type for tracking events.
typedef OnAnalyticsEvent = void Function(String event);

/// A custom implementation of [CommandRunner] with additional features.
///
/// This class extends the [CommandRunner] class from the `args` package and adds
/// additional functionality such as logging, setting log levels, tracking events,
/// and handling analytics.
///
/// The [BetterCommandRunner] class provides a more enhanced command line interface
/// for running commands and handling command line arguments.
class BetterCommandRunner extends CommandRunner {
final PassMessage? _logError;
final PassMessage? _logInfo;
final SetLogLevel? _setLogLevel;
final OnBeforeRunCommand? _onBeforeRunCommand;
OnAnalyticsEvent? _onAnalyticsEvent;

final ArgParser _argParser;

/// Creates a new instance of [BetterCommandRunner].
///
/// The [executableName] is the name of the executable for the command line interface.
/// The [description] is a description of the command line interface.
/// The [logError] function is used to pass error log messages.
/// The [logInfo] function is used to pass informational log messages.
/// The [setLogLevel] function is used to set the log level.
/// The [onBeforeRunCommand] function is executed before running a command.
/// The [onAnalyticsEvent] function is used to track events.
/// The [wrapTextColumn] is the column width for wrapping text in the command line interface.
BetterCommandRunner(
super.executableName,
super.description, {
PassMessage? logError,
PassMessage? logInfo,
SetLogLevel? setLogLevel,
OnBeforeRunCommand? onBeforeRunCommand,
OnAnalyticsEvent? onAnalyticsEvent,
int? wrapTextColumn,
}) : _logError = logError,
_logInfo = logInfo,
_onBeforeRunCommand = onBeforeRunCommand,
_setLogLevel = setLogLevel,
_onAnalyticsEvent = onAnalyticsEvent,
_argParser = ArgParser(usageLineLength: wrapTextColumn) {
argParser.addFlag(
BetterCommandRunnerFlags.quiet,
abbr: BetterCommandRunnerFlags.quietAbbr,
defaultsTo: false,
negatable: false,
help: 'Suppress all cli output. Is overridden by '
' -${BetterCommandRunnerFlags.verboseAbbr}, --${BetterCommandRunnerFlags.verbose}.',
);

argParser.addFlag(
BetterCommandRunnerFlags.verbose,
abbr: BetterCommandRunnerFlags.verboseAbbr,
defaultsTo: false,
negatable: false,
help: 'Prints additional information useful for development. '
'Overrides --${BetterCommandRunnerFlags.quietAbbr}, --${BetterCommandRunnerFlags.quiet}.',
);

if (_onAnalyticsEvent != null) {
argParser.addFlag(
BetterCommandRunnerFlags.analytics,
abbr: BetterCommandRunnerFlags.analyticsAbbr,
defaultsTo: true,
negatable: true,
help: 'Toggles if analytics data is sent. ',
);
}
}

@override
ArgParser get argParser => _argParser;

/// Adds a list of commands to the command runner.
void addCommands(List<Command> commands) {
for (var command in commands) {
addCommand(command);
}
}

/// Checks if analytics is enabled.
bool analyticsEnabled() => _onAnalyticsEvent != null;

@override
ArgResults parse(Iterable<String> args) {
try {
return super.parse(args);
} on UsageException catch (e) {
_onAnalyticsEvent?.call(BetterCommandRunnerAnalyticsEvents.invalid);
_logError?.call(e.toString());
throw ExitException(ExitCodeType.commandNotFound);
}
}

@override
void printUsage() {
_logInfo?.call(usage);
}

@override
Future<void> runCommand(ArgResults topLevelResults) async {
_setLogLevel?.call(
parsedLogLevel: _parseLogLevel(topLevelResults),
commandName: topLevelResults.command?.name,
);

if (argParser.options.containsKey(BetterCommandRunnerFlags.analytics) &&
!topLevelResults[BetterCommandRunnerFlags.analytics]) {
_onAnalyticsEvent = null;
}

await _onBeforeRunCommand?.call(this);

try {
await super.runCommand(topLevelResults);
if (topLevelResults.command == null) {
_onAnalyticsEvent?.call(BetterCommandRunnerAnalyticsEvents.help);
} else {
_onAnalyticsEvent?.call(topLevelResults.command!.name!);
}
} on UsageException catch (e) {
_logError?.call(e.toString());
_onAnalyticsEvent?.call(BetterCommandRunnerAnalyticsEvents.invalid);
throw ExitException(ExitCodeType.commandNotFound);
}
}

CommandRunnerLogLevel _parseLogLevel(ArgResults topLevelResults) {
if (topLevelResults[BetterCommandRunnerFlags.verbose]) {
return CommandRunnerLogLevel.verbose;
} else if (topLevelResults[BetterCommandRunnerFlags.quiet]) {
return CommandRunnerLogLevel.quiet;
}

return CommandRunnerLogLevel.normal;
}
}

/// Constants for the command runner flags.
abstract class BetterCommandRunnerFlags {
static const quiet = 'quiet';
static const quietAbbr = 'q';
static const verbose = 'verbose';
static const verboseAbbr = 'v';
static const analytics = 'analytics';
static const analyticsAbbr = 'a';
}

/// Constants for the command runner analytics events.
abstract class BetterCommandRunnerAnalyticsEvents {
static const help = 'help';
static const invalid = 'invalid';
}

/// An enum for the command runner log levels.
enum CommandRunnerLogLevel { quiet, verbose, normal }
Loading

0 comments on commit 22eb354

Please sign in to comment.