Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sshnp gui core MVP #467

Merged
merged 113 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
d380637
fix: home screen and new config screen refactored to use sshnoports b…
CurtlyCritchlow Sep 4, 2023
eee0b31
chore: clean up sshnp params and implement merge and empty on the ful…
XavierChanth Sep 5, 2023
3fdac47
feat: implement sshnp config controller family
XavierChanth Sep 5, 2023
d57ff86
refactor: add sshnp params controller
XavierChanth Sep 5, 2023
93c4ead
fix: bugs due to previous refactoring
XavierChanth Sep 5, 2023
5ade3c7
chore: cleanup naming
XavierChanth Sep 5, 2023
28ebc14
chore: make sshnp partial params all final again
XavierChanth Sep 5, 2023
5a53354
Merge pull request #424 from atsign-foundation/fix/update-ssh-gui-xavier
CurtlyCritchlow Sep 5, 2023
b14259f
chore: reorganize widgets folder
XavierChanth Sep 7, 2023
aadcb27
chore: rename connection form to profile form
XavierChanth Sep 7, 2023
a2d3a87
chore: rename profile actions
XavierChanth Sep 7, 2023
f7cf687
chore: rename profile actions
XavierChanth Sep 7, 2023
dc4eccb
chore: update profile_actions
XavierChanth Sep 7, 2023
fad4ade
chore: rename profile form
XavierChanth Sep 7, 2023
6d8f87b
chore: rename profile form
XavierChanth Sep 7, 2023
05585b8
fix: profileName with spaces
XavierChanth Sep 7, 2023
5cab0d1
chore: ignore '.env'
XavierChanth Sep 7, 2023
91a3386
chore: clean up profile bar and profile form
XavierChanth Sep 7, 2023
9574146
chore: rename profile editor screen
XavierChanth Sep 7, 2023
7228445
chore: organize imports
XavierChanth Sep 7, 2023
d0cd618
chore: cleanup profile_form
XavierChanth Sep 7, 2023
3b94ad7
chore: cleanup navigation controller
XavierChanth Sep 7, 2023
c41ba32
chore: cleanup naming for params controllers
XavierChanth Sep 7, 2023
4036285
chore: reorder sshnp params controller
XavierChanth Sep 7, 2023
955cf3a
chore: cleanup sshnp params controller
XavierChanth Sep 7, 2023
06fccfc
feat: add terminal session controller
XavierChanth Sep 7, 2023
778ef9d
chore: cleanup some bugs
XavierChanth Sep 7, 2023
6c68865
chore: cleanup logging
XavierChanth Sep 7, 2023
b885e5c
fix: nav rail for non rail routes
XavierChanth Sep 7, 2023
a036fba
chore: final cleanup
XavierChanth Sep 7, 2023
23a75bc
feat: show the command being run by the terminal
XavierChanth Sep 7, 2023
4065e3e
chore: no autodispose on terminal controllers
XavierChanth Sep 7, 2023
1a17bca
feat: sort home screen profiles
XavierChanth Sep 7, 2023
0c7ae84
feat: default 10000 max lines for the terminal
XavierChanth Sep 7, 2023
8f08b9a
Merge pull request #432 from atsign-foundation/gui/xavierchanth-07-09…
CurtlyCritchlow Sep 7, 2023
e83e623
feat: new terminal shown on terminal screen when terminal icon is pre…
CurtlyCritchlow Sep 7, 2023
f58bc71
Merge branch 'feat/multiple-terminal' into sshnp-gui
XavierChanth Sep 8, 2023
24ef186
refactor: create settings actions
XavierChanth Sep 8, 2023
cc424db
deps: cleanup dependencies
XavierChanth Sep 8, 2023
10d5144
refactor: nav rail controller
XavierChanth Sep 8, 2023
be53df0
refactor: navigation and terminal tabs
XavierChanth Sep 8, 2023
1de21a2
chore: cleanup imports
XavierChanth Sep 8, 2023
71c0b81
fix: test widget pumper
XavierChanth Sep 8, 2023
3ed6ccc
feat: add background session controller
XavierChanth Sep 8, 2023
ab08f4f
chore: remove async from initState
XavierChanth Sep 8, 2023
2814ab4
docs: note the changes required for run_action
XavierChanth Sep 8, 2023
826c9a7
chore: disable run action for now
XavierChanth Sep 8, 2023
416868c
chore: add no-sessions prompt for terminal screen
XavierChanth Sep 8, 2023
37c46a0
feat: add callback for terminal tab to close on process exit
XavierChanth Sep 8, 2023
ac6c88b
chore: cleanup unused imports
XavierChanth Sep 8, 2023
a318e1f
style: make home title larger
XavierChanth Sep 8, 2023
b464dbc
chore: sort localizations
XavierChanth Sep 8, 2023
854ce83
fix: backup keys string
XavierChanth Sep 8, 2023
03906c1
Merge branch 'fix/sendSshPublicKey' into sshnp-gui
XavierChanth Sep 8, 2023
a2e3b94
fix: use SSHNPD.namespace as the default namespace for sshnp_gui
XavierChanth Sep 8, 2023
b0f44e0
Merge pull request #440 from atsign-foundation/fix/gui-namespace
gkc Sep 8, 2023
3a8fed6
feat: cleanup terminal behaviors
XavierChanth Sep 9, 2023
98a977f
Merge branch 'gui-terminal-behaviour' into sshnp-gui
XavierChanth Sep 9, 2023
e6888d9
chore: make cursorInvisible when doing exit countdown
XavierChanth Sep 9, 2023
01b6ed7
fix: minimum desktop size set other minor changes to all correct sizi…
CurtlyCritchlow Sep 11, 2023
0b25b1d
Merge pull request #447 from atsign-foundation/fix/sizing
XavierChanth Sep 12, 2023
10ed463
Merge remote-tracking branch 'origin/trunk' into sshnp-gui
XavierChanth Sep 12, 2023
0780a45
chore: rename sshnpparams to config in controller
XavierChanth Sep 12, 2023
65a52b1
chore: rename sshnp_params_controller.dart to config_controller.dart
XavierChanth Sep 12, 2023
3bc8be7
feat: add ConfigSource and covariants
XavierChanth Sep 12, 2023
9cee993
format: sort members of sshnp_params.dart
XavierChanth Sep 12, 2023
5c0ec6d
chore: move config_source to sshnoports package
XavierChanth Sep 12, 2023
3b3ddaa
chore: more cleanup
XavierChanth Sep 12, 2023
9b8332c
chore: checkin config file management
XavierChanth Sep 12, 2023
29bee9c
chore: revert configFamilyController
XavierChanth Sep 12, 2023
4a19820
feat: add config_repos to sshnoports core
XavierChanth Sep 12, 2023
ff3ce17
Merge remote-tracking branch 'origin/trunk' into gui/config-syncing
XavierChanth Sep 12, 2023
6ce836a
feat: allow deletion of corrupted configs
XavierChanth Sep 12, 2023
4327a4c
feat: implement profile menu button
XavierChanth Sep 12, 2023
98df3dc
chore: rename profile action button file
XavierChanth Sep 12, 2023
2118f4d
fix: Add macOS UTI specification for atKeys files
XavierChanth Sep 13, 2023
8b51edd
Merge remote-tracking branch 'origin/sshnp-gui' into gui/config-syncing
XavierChanth Sep 13, 2023
f8bea7c
fix: ensure config files are properly formed when putting them
XavierChanth Sep 13, 2023
85861f1
Merge remote-tracking branch 'origin/trunk' into sshnp-gui
XavierChanth Sep 14, 2023
3019bbb
Merge branch 'sshnp-gui' into gui/config-syncing
XavierChanth Sep 14, 2023
505573e
fix: use ConfigFileRepository helper function
XavierChanth Sep 14, 2023
d55ef78
fix: set clientAtSign within the constructor for SSHNPPartialParams
XavierChanth Sep 14, 2023
c843680
Merge branch 'gui/config-syncing' into sshnp-gui
XavierChanth Sep 14, 2023
7f74a2b
feat: implement idleTimeout and addForwardsToTunnel for Run action
XavierChanth Sep 14, 2023
221dcb4
feat: add export option
XavierChanth Sep 14, 2023
1ba1152
feat: add import profile feature
XavierChanth Sep 14, 2023
34347b8
Merge branch 'gui/config-syncing' into sshnp-gui
XavierChanth Sep 14, 2023
2cee553
Merge branch 'sshnp-gui' of github.com:atsign-foundation/sshnoports i…
XavierChanth Sep 14, 2023
5b136aa
Merge branch 'gui/config-syncing' into sshnp-gui
XavierChanth Sep 14, 2023
6b1db7a
Merge branch 'trunk' into sshnp-gui
XavierChanth Sep 14, 2023
7e435ff
feat: add background ssh session
XavierChanth Sep 14, 2023
31f4a02
chore: remove unused import
XavierChanth Sep 14, 2023
d4d16df
chore: cleanup
XavierChanth Sep 14, 2023
8e3d87c
chore: cleanup error messages
XavierChanth Sep 14, 2023
a2f2554
chore: change 3.5.0 references to 3.4.0
XavierChanth Sep 14, 2023
d93aeab
chore: add a hint to the SSHNPFailed for daemon timeouts
XavierChanth Sep 14, 2023
ac49b06
feat: add localSshOptions to SSHCommand.base
XavierChanth Sep 14, 2023
9885b79
Merge remote-tracking branch 'origin/trunk' into sshnp-gui
XavierChanth Sep 14, 2023
387ddbf
chore: set version to 4.0.0-rc.4
XavierChanth Sep 14, 2023
5d2a4c6
fix: resize computation
XavierChanth Sep 14, 2023
2ab9798
feat: force legacy daemon to false
XavierChanth Sep 14, 2023
0d4879a
feat: improve form consistency
XavierChanth Sep 14, 2023
651fef4
fix: better handle failed profile editing
XavierChanth Sep 14, 2023
1336e4a
fix: note that legacy daemons are actually <4.0.0 not <3.4.0
XavierChanth Sep 14, 2023
2aac517
fix: normalize all paths
XavierChanth Sep 14, 2023
ee2da0e
format: dart format
XavierChanth Sep 14, 2023
6890368
feat: use pure-dart clients
XavierChanth Sep 14, 2023
f5df86c
format: dart format
XavierChanth Sep 14, 2023
e8359fe
fix: flip addForwardsToTunnel
XavierChanth Sep 14, 2023
831babe
chore: update pubspec.lock
XavierChanth Sep 14, 2023
016dd2c
chore: increase version
XavierChanth Sep 14, 2023
aa0c591
fix: use stricter form validation
XavierChanth Sep 14, 2023
072adf9
fix: profileName validator
XavierChanth Sep 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/sshnoports/bin/activate_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Future<void> main(List<String> args) async {
try {
await activate_cli.main(args);
} catch (e) {
print(e.toString());
stdout.writeln(e.toString());
}
exit(0);
}
18 changes: 8 additions & 10 deletions packages/sshnoports/bin/sshnp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,18 @@ void main(List<String> args) async {

await runZonedGuarded(() async {
if (params.listDevices) {
print('Searching for devices...');
stdout.writeln('Searching for devices...');
var (active, off, info) = await sshnp.listDevices();
if (active.isEmpty && off.isEmpty) {
print('[X] No devices found\n');
print(
'Note: only devices with sshnpd version 3.4.0 or higher are supported by this command.');
print(
'Please update your devices to sshnpd version >= 3.4.0 and try again.');
stdout.writeln('[X] No devices found\n');
stdout.writeln('Note: only devices with sshnpd version 3.4.0 or higher are supported by this command.');
stdout.writeln('Please update your devices to sshnpd version >= 3.4.0 and try again.');
exit(0);
}

print('Active Devices:');
stdout.writeln('Active Devices:');
_printDevices(active, info);
print('Inactive Devices:');
stdout.writeln('Inactive Devices:');
_printDevices(off, info);
exit(0);
}
Expand Down Expand Up @@ -80,10 +78,10 @@ void main(List<String> args) async {

void _printDevices(Iterable<String> devices, Map<String, dynamic> info) {
if (devices.isEmpty) {
print(' [X] No devices found');
stdout.writeln(' [X] No devices found');
return;
}
for (var device in devices) {
print(' $device - v${info[device]?['version']}');
stdout.writeln(' $device - v${info[device]?['version']}');
}
}
2 changes: 1 addition & 1 deletion packages/sshnoports/bin/sshrv.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:sshnoports/sshrv/sshrv.dart';

Future<void> main(List<String> args) async {
if (args.length < 2 || args.length > 3) {
print('sshrv <host> <port> [localhost sshd port, defaults to 22]');
stdout.writeln('sshrv <host> <port> [localhost sshd port, defaults to 22]');
exit(-1);
}

Expand Down
16 changes: 6 additions & 10 deletions packages/sshnoports/lib/common/create_at_client_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:io';
import 'package:at_client/at_client.dart';
import 'package:at_onboarding_cli/at_onboarding_cli.dart';
import 'package:version/version.dart';

import 'package:path/path.dart' as path;
import 'service_factories.dart';

Future<AtClient> createAtClientCli({
Expand All @@ -21,22 +21,18 @@ Future<AtClient> createAtClientCli({
pathBase += '$pathExtension${Platform.pathSeparator}';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed how paths are normalized in this file

}
AtOnboardingPreference atOnboardingConfig = AtOnboardingPreference()
..hiveStoragePath =
'$pathBase/storage'.replaceAll('/', Platform.pathSeparator)
..hiveStoragePath = path.normalize('$pathBase/storage')
..namespace = namespace
..downloadPath = '$homeDirectory/$subDirectory/files'
.replaceAll('/', Platform.pathSeparator)
..downloadPath = path.normalize('$homeDirectory/$subDirectory/files')
..isLocalStoreRequired = true
..commitLogPath =
'$pathBase/storage/commitLog'.replaceAll('/', Platform.pathSeparator)
..commitLogPath = path.normalize('$pathBase/storage/commitLog')
..fetchOfflineNotifications = false
..atKeysFilePath = atKeysFilePath
..atProtocolEmitted = Version(2, 0, 0)
..rootDomain = rootDomain;

AtOnboardingService onboardingService = AtOnboardingServiceImpl(
atsign, atOnboardingConfig,
atServiceFactory: ServiceFactoryWithNoOpSyncService());
AtOnboardingService onboardingService =
AtOnboardingServiceImpl(atsign, atOnboardingConfig, atServiceFactory: ServiceFactoryWithNoOpSyncService());

await onboardingService.authenticate();

Expand Down
55 changes: 27 additions & 28 deletions packages/sshnoports/lib/common/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';
import 'package:at_chops/at_chops.dart';
import 'package:at_client/at_client.dart';
import 'package:at_utils/at_utils.dart';
import 'package:path/path.dart' as path;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file normalization in this file too

/// Get the home directory or null if unknown.
String? getHomeDirectory({bool throwIfNull = false}) {
Expand Down Expand Up @@ -56,20 +57,19 @@ bool checkNonAscii(String test) {

String getDefaultAtKeysFilePath(String homeDirectory, String? atSign) {
if (atSign == null) return '';
return '$homeDirectory/.atsign/keys/${atSign}_key.atKeys'
.replaceAll('/', Platform.pathSeparator);
return path.normalize('$homeDirectory/.atsign/keys/${atSign}_key.atKeys');
}

String getDefaultSshDirectory(String homeDirectory) {
return '$homeDirectory/.ssh/'.replaceAll('/', Platform.pathSeparator);
return path.normalize('$homeDirectory/.ssh/');
}

String getDefaultSshnpDirectory(String homeDirectory) {
return '$homeDirectory/.sshnp/'.replaceAll('/', Platform.pathSeparator);
return path.normalize('$homeDirectory/.sshnp/');
}

String getDefaultSshnpConfigDirectory(String homeDirectory) {
return '$homeDirectory/.sshnp/config'.replaceAll('/', Platform.pathSeparator);
return path.normalize('$homeDirectory/.sshnp/config');
}

/// Checks if the provided atSign's atServer has been properly activated with a public RSA key.
Expand Down Expand Up @@ -144,9 +144,9 @@ Future<(String, String)> generateSshKeys(
}

String sshPublicKey =
await File('$sshHomeDirectory${sessionId}_sshnp.pub').readAsString();
await File('$sshHomeDirectory/${sessionId}_sshnp.pub').readAsString();
String sshPrivateKey =
await File('$sshHomeDirectory${sessionId}_sshnp').readAsString();
await File('$sshHomeDirectory/${sessionId}_sshnp').readAsString();

return (sshPublicKey, sshPrivateKey);
}
Expand All @@ -162,16 +162,15 @@ Future<void> addEphemeralKeyToAuthorizedKeys(
}

String homeDirectory = getHomeDirectory(throwIfNull: true)!;
var sshHomeDirectory = getDefaultSshDirectory(homeDirectory);

var sshHomeDirectory =
'$homeDirectory/.ssh/'.replaceAll('/', Platform.pathSeparator);
if (!Directory(sshHomeDirectory).existsSync()) {
Directory(sshHomeDirectory).createSync();
}

// Check to see if the ssh Publickey is already in the authorized_keys file.
// If not, then append it.
var authKeys = File('${sshHomeDirectory}authorized_keys');
var authKeys = File(path.normalize('$sshHomeDirectory/authorized_keys'));

var authKeysContent = await authKeys.readAsString();
if (!authKeysContent.endsWith('\n')) {
Expand All @@ -184,14 +183,16 @@ Future<void> addEphemeralKeyToAuthorizedKeys(
}
// Set up a safe authorized_keys file, for the ssh tunnel
await authKeys.writeAsString(
'command="echo \\"ssh session complete\\";sleep 20"'
',PermitOpen="localhost:$localSshdPort"'
'$permissions'
' '
'${sshPublicKey.trim()}'
' '
'sshnp_ephemeral_$sessionId\n',
mode: FileMode.append);
'command="echo \\"ssh session complete\\";sleep 20"'
',PermitOpen="localhost:$localSshdPort"'
'$permissions'
' '
'${sshPublicKey.trim()}'
' '
'sshnp_ephemeral_$sessionId\n',
mode: FileMode.append,
flush: true,
);
}
}

Expand All @@ -201,7 +202,7 @@ Future<void> removeEphemeralKeyFromAuthorizedKeys(
try {
sshHomeDirectory ??=
getDefaultSshDirectory(getHomeDirectory(throwIfNull: true)!);
final File file = File('${sshHomeDirectory}authorized_keys');
final File file = File(path.normalize('$sshHomeDirectory/authorized_keys'));
logger.info('Removing ephemeral key for session $sessionId'
' from ${file.absolute.path}');
// read into List of strings
Expand All @@ -212,7 +213,8 @@ Future<void> removeEphemeralKeyFromAuthorizedKeys(
await file.writeAsString(lines.join('\n'));
await file.writeAsString('\n', mode: FileMode.writeOnlyAppend);
} catch (e) {
logger.severe('Unable to tidy up ${sshHomeDirectory}authorized_keys');
logger.severe(
'Unable to tidy up ${path.normalize('$sshHomeDirectory/authorized_keys')}');
}
}

Expand Down Expand Up @@ -289,9 +291,8 @@ Future<String?> _fetchFromLocalPKCache(
AtClient atClient, String atSign, bool useFileStorage) async {
String dontAtMe = atSign.substring(1);
if (useFileStorage) {
String fn = '${getHomeDirectory(throwIfNull: true)}'
'/.atsign/sshnp/cached_pks/$dontAtMe'
.replaceAll('/', Platform.pathSeparator);
String fn = path.normalize(
'${getHomeDirectory(throwIfNull: true)}/.atsign/sshnp/cached_pks/$dontAtMe');
File f = File(fn);
if (await f.exists()) {
return (await f.readAsString()).trim();
Expand All @@ -314,11 +315,9 @@ Future<bool> _storeToLocalPKCache(
String pk, AtClient atClient, String atSign, bool useFileStorage) async {
String dontAtMe = atSign.substring(1);
if (useFileStorage) {
String dirName =
'${getHomeDirectory(throwIfNull: true)}/.atsign/sshnp/cached_pks'
.replaceAll('/', Platform.pathSeparator);
String fileName =
'$dirName/$dontAtMe'.replaceAll('/', Platform.pathSeparator);
String dirName = path.normalize(
'${getHomeDirectory(throwIfNull: true)}/.atsign/sshnp/cached_pks');
String fileName = path.normalize('$dirName/$dontAtMe');

File f = File(fileName);
if (!await f.exists()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import 'dart:io';

import 'package:sshnoports/common/utils.dart';
import 'package:sshnoports/sshnp/sshnp.dart';
import 'package:sshnoports/sshnp/sshnp_arg.dart';
import 'package:path/path.dart' as path;

class ConfigFileRepository {
static String toProfileName(String fileName, {bool replaceSpaces = true}) {
var profileName = path.basenameWithoutExtension(fileName);
if (replaceSpaces) profileName = profileName.replaceAll('_', ' ');
return profileName;
}

static String fromProfileName(String profileName,
{String? directory, bool replaceSpaces = true, bool basenameOnly = false}) {
var fileName = profileName;
if (replaceSpaces) fileName = fileName.replaceAll(' ', '_');
final basename = '$fileName.env';
if (basenameOnly) return basename;
return path.join(
directory ?? getDefaultSshnpConfigDirectory(getHomeDirectory(throwIfNull: true)!),
basename,
);
}

static Future<Directory> createConfigDirectory({String? directory}) async {
directory ??= getDefaultSshnpConfigDirectory(getHomeDirectory(throwIfNull: true)!);
var dir = Directory(directory);
if (!await dir.exists()) {
await dir.create(recursive: true);
}
return dir;
}

static Future<Iterable<String>> listProfiles({String? directory}) async {
var profileNames = <String>{};

var homeDirectory = getHomeDirectory(throwIfNull: true)!;
directory ??= getDefaultSshnpConfigDirectory(homeDirectory);
var files = Directory(directory).list();

await files.forEach((file) {
if (file is! File) return;
if (path.extension(file.path) != '.env') return;
if (path.basenameWithoutExtension(file.path).isEmpty) return; // ignore '.env' file - empty profileName
profileNames.add(toProfileName(file.path));
});
return profileNames;
}

static Future<SSHNPParams> getParams(String profileName, {String? directory}) async {
var fileName = fromProfileName(profileName, directory: directory);
return SSHNPParams.fromFile(fileName);
}

static Future<File> putParams(SSHNPParams params, {String? directory, bool overwrite = false}) async {
if (params.profileName == null || params.profileName!.isEmpty) {
throw Exception('profileName is null or empty');
}

var fileName = fromProfileName(params.profileName!, directory: directory);
var file = File(fileName);

var exists = await file.exists();

if (exists && !overwrite) {
throw Exception('Failed to write config file: ${file.path} already exists');
}

// FileMode.write will create the file if it does not exist
// and overwrite existing files if it does exist
return file.writeAsString(params.toConfig(), mode: FileMode.write);
}

static Future<FileSystemEntity> deleteParams(SSHNPParams params, {String? directory}) async {
if (params.profileName == null || params.profileName!.isEmpty) {
throw Exception('profileName is null or empty');
}

var fileName = fromProfileName(params.profileName!, directory: directory);
var file = File(fileName);

var exists = await file.exists();

if (!exists) {
throw Exception('Cannot delete ${file.path}, file does not exist');
}

return file.delete();
}

static Map<String, dynamic> parseConfigFile(String fileName) {
File file = File(fileName);

if (!file.existsSync()) {
throw Exception('Config file does not exist: $fileName');
}
try {
List<String> lines = file.readAsLinesSync();
return parseConfigFileContents(lines);
} on FileSystemException {
throw Exception('Error reading config file: $fileName');
}
}

static Map<String, dynamic> parseConfigFileContents(List<String> lines) {
Map<String, dynamic> args = <String, dynamic>{};

try {
for (String line in lines) {
if (line.startsWith('#')) continue;

var parts = line.split('=');
if (parts.length != 2) continue;

var key = parts[0].trim();
var value = parts[1].trim();

SSHNPArg arg = SSHNPArg.fromBashName(key);
if (arg.name.isEmpty) continue;

switch (arg.format) {
case ArgFormat.flag:
if (value.toLowerCase() == 'true') {
args[arg.name] = true;
}
continue;
case ArgFormat.multiOption:
var values = value.split(',');
args.putIfAbsent(arg.name, () => <String>[]);
for (String val in values) {
if (val.isEmpty) continue;
args[arg.name].add(val);
}
continue;
case ArgFormat.option:
if (value.isEmpty) continue;
if (arg.type == ArgType.integer) {
args[arg.name] = int.tryParse(value);
} else {
args[arg.name] = value;
}
continue;
}
}
return args;
} catch (e) {
throw Exception('Error parsing config file');
}
}
}
Loading