Skip to content

Instantly share code, notes, and snippets.

@arturaz
Created May 22, 2020 05:45
Show Gist options
  • Save arturaz/51bbc46c381b51e398fa48033de17a00 to your computer and use it in GitHub Desktop.
Save arturaz/51bbc46c381b51e398fa48033de17a00 to your computer and use it in GitHub Desktop.
import 'dart:io';
import 'package:functional_data/functional_data.dart';
import 'package:meta/meta.dart';
import 'package:zowo_lib/lib.dart';
part 'Config.g.dart';
@immutable
@FunctionalData()
class Config extends $Config {
final HttpConfig http;
final DbConfig db;
final CpuConfig cpu;
const Config({@required this.http, @required this.db, @required this.cpu});
static final configs = DynamicConfigs.map.flatMapTry((map) => doTry(() =>
Config(
http: map.getAt("http", HttpConfig.configs),
db: map.getAt("db", DbConfig.configs),
cpu: map.getAt("cpu", CpuConfig.configs)
)
));
}
@immutable
@FunctionalData()
class HttpConfig extends $HttpConfig {
final String host;
final int port;
const HttpConfig({@required this.host, @required this.port});
static final configs = DynamicConfigs.map.flatMapTry((map) => doTry(() =>
HttpConfig(
host: map.getAt("host", DynamicConfigs.string),
port: map.getAt("port", DynamicConfigs.integer)
)
));
}
@immutable
@FunctionalData()
class DbConfig extends $DbConfig {
final File migrationsDir;
final DbAuthConfig auth;
const DbConfig({@required this.migrationsDir, @required this.auth});
static final configs = DynamicConfigs.map.flatMapTry((map) => doTry(() =>
DbConfig(
migrationsDir: map.getAt("migrationsDir", DynamicConfigs.file),
auth: map.getAt("auth", DbAuthConfig.configs)
)
));
}
@immutable
@FunctionalData()
class DbAuthConfig extends $DbAuthConfig {
final String username, password, database;
final int port;
const DbAuthConfig({@required this.username, @required this.password, @required this.database, @required this.port});
static final configs = DynamicConfigs.map.flatMapTry((map) => doTry(() =>
DbAuthConfig(
username: map.getAt("username", DynamicConfigs.string),
password: map.getAt("password", DynamicConfigs.string),
database: map.getAt("database", DynamicConfigs.string),
port: map.getAt("port", DynamicConfigs.integer)
)
));
}
@immutable
@FunctionalData()
class CpuConfig extends $CpuConfig {
final Option<int> threads;
const CpuConfig({@required this.threads});
static final configs = DynamicConfigs.map.flatMapTry((map) => doTry(() =>
CpuConfig(
threads: map.maybeAt("threads", DynamicConfigs.integer)
)
));
}
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import '../functional/Option.dart';
import '../data/collection.dart';
import '../functional/Try.dart';
import '../functional/functions.dart';
import '../functional/Either.dart';
import '../exts/exts.dart';
@immutable
class ConfigsError {
final SLList<String> path;
final String _fromString;
final Exception _fromException;
const ConfigsError._(this._fromString, this._fromException, this.path);
ConfigsError.fromString(String string)
: path = SLList<String>.nil(), _fromString = string, _fromException = null;
ConfigsError.fromRawException(Exception exception)
: path = SLList<String>.nil(), _fromString = null, _fromException = exception;
factory ConfigsError.fromException(Exception ex) =>
ex is ConfigsException ? ex.error : ConfigsError.fromRawException(ex);
R iswitch<R>({
@required R Function(String string) fromString,
@required R Function(Exception exception) fromException
}) =>
_fromString != null ? fromString(_fromString) : fromException(_fromException);
ConfigsError atPath(String s) => ConfigsError._(_fromString, _fromException, s.prepend(path));
@override String toString() =>
"ConfigsError[at '${path.join(".")}': ${iswitch(fromString: (v) => v, fromException: (e) => e.toString())}]";
}
class ConfigsException implements Exception {
final ConfigsError error;
ConfigsException(this.error);
}
@immutable
abstract class Configs<From, To> {
Either<ConfigsError, To> parse(From from);
const Configs();
Configs<From, To1> map<To1>(To1 Function(To from) parse) =>
FuncConfigs((from) => this.parse(from).map((to) => parse(to)));
Configs<From, To1> flatMap<To1>(Either<ConfigsError, To1> Function(To from) parse) =>
FuncConfigs((from) => this.parse(from).flatMap((to) => parse(to)));
Configs<From, To1> flatMapTry<To1>(Try<To1> Function(To from) parse) =>
FuncConfigs((from) => this.parse(from).flatMap((to) =>
parse(to).fold((a) => Right(a), (exception) => Left(ConfigsError.fromException(exception)))
));
}
@immutable
class FuncConfigs<From, To> extends Configs<From, To> {
final Either<ConfigsError, To> Function(From from) parser;
const FuncConfigs(this.parser);
@override Either<ConfigsError, To> parse(From from) => parser(from);
}
Configs<dynamic, To> castConfigs<To>() => FuncConfigs<dynamic, To>(
(dynamic yaml) => castToEither<To>(yaml).mapLeft((err) => ConfigsError.fromString(err))
);
class DynamicConfigs {
static final Configs<dynamic, bool> boolean = castConfigs<bool>();
static final Configs<dynamic, String> string = castConfigs<String>();
static final Configs<dynamic, File> file = string.map((from) => File(from));
static final Configs<dynamic, int> integer = castConfigs<int>();
static final Configs<dynamic, double> dbl = castConfigs<double>();
static final Configs<dynamic, List<dynamic>> list =
castConfigs<List<dynamic>>().map((from) => DynamicConfigsList(from));
static final Configs<dynamic, DynamicConfigsMap> map =
castConfigs<Map<dynamic, dynamic>>().map((from) => DynamicConfigsMap(from));
}
class DynamicConfigsList extends DelegatingList<dynamic> {
DynamicConfigsList(List<dynamic> base) : super(base);
}
class DynamicConfigsMap extends DelegatingMap<dynamic, dynamic> {
DynamicConfigsMap(Map<dynamic, dynamic> base) : super(base);
A getAt<A>(dynamic key, Configs<dynamic, A> configs) {
final dynamic maybeDynamic = this[key];
if (maybeDynamic == null) throw ConfigsException(ConfigsError.fromString("Can't find value by key '$key'"));
final either = configs.parse(maybeDynamic);
if (either.isLeft) throw ConfigsException(either.leftOrThrow.atPath(key.toString()));
return either.rightOrThrow;
}
Option<A> maybeAt<A>(dynamic key, Configs<dynamic, A> configs) {
final dynamic maybeDynamic = this[key];
if (maybeDynamic == null) return const Option.none();
final either = configs.parse(maybeDynamic);
if (either.isLeft) throw ConfigsException(either.leftOrThrow.atPath(key.toString()));
return Some(either.rightOrThrow);
}
}
Future<void> main(List<String> inputArgs) async {
final parser = args.ArgParser()
..addOption("config", abbr: "c", defaultsTo: "server.conf.yaml");
final result = parser.parse(inputArgs);
final confFile =
Option.check(castTo<String>(result['config'])).toRight(() => "--config option not specified")
.flatMap((path) =>
doTry(() => File(path).readAsStringSync()).toEither.mapLeft((err) => "Can't read file at '$path': $err")
)
.flatMap<dynamic>((yaml) =>
doTry<dynamic>(() => loadYaml(yaml)).toEither
.mapLeft((err) => "Can't parse config file yaml: $err\n\nYAML:\n$yaml")
)
.flatMap((dynamic yaml) => Config.configs.parse(yaml).mapLeft((err) => err.toString()));
return confFile.fold(
(err) {
stderr.writeln(err);
// 64: command line usage error
exit(64);
},
(conf) async {
final isolates = conf.cpu.threads.fold(() => SysInfo.processors.length, (v) => v);
final http = conf.http;
stdout.writeln('Launching $isolates isolates on ${http.host}:${http.port}.');
final isolateFutures =
List.generate(isolates - 1, (idx) => Isolate.spawn<ShelfArgs>(runShelf, ShelfArgs(idx, conf)));
await isolateFutures.sequence();
await runShelf(ShelfArgs(isolates - 1, conf));
stdout.writeln('Launched all isolates on ${http.host}:${http.port}.');
}
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment