diff --git a/.github/workflows/format-fix.yml b/.github/workflows/format-fix.yml new file mode 100644 index 00000000..f3597d58 --- /dev/null +++ b/.github/workflows/format-fix.yml @@ -0,0 +1,49 @@ +name: Format Fix + +on: + push: + branches: + - chatgpt/unitflow-dart-fixes-20260819 + +permissions: + contents: write + +jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + with: + ref: chatgpt/unitflow-dart-fixes-20260819 + fetch-depth: 0 + + - name: Install Rust formatter + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Format Rust + run: cargo fmt --all + + - name: Format Flutter sources and tests + run: dart format apps/unitflow_app/lib apps/unitflow_app/test + + - name: Commit formatter output + shell: bash + run: | + if git diff --quiet; then + echo "No formatter changes required." + exit 0 + fi + git config user.name "Sanskar" + git config user.email "sanskarin@outlook.in" + git add -A + git commit -m "style: apply automated formatters" + git push origin HEAD:chatgpt/unitflow-dart-fixes-20260819 diff --git a/apps/unitflow_app/lib/app/app_controller.dart b/apps/unitflow_app/lib/app/app_controller.dart index 49f316cf..99b0bccc 100644 --- a/apps/unitflow_app/lib/app/app_controller.dart +++ b/apps/unitflow_app/lib/app/app_controller.dart @@ -7,7 +7,8 @@ import '../features/converter/domain/conversion_engine.dart'; import '../features/converter/domain/unit_models.dart'; final class AppController extends ChangeNotifier { - AppController({required UserStateRepository repository}) : _repository = repository; + AppController({required UserStateRepository repository}) + : _repository = repository; final UserStateRepository _repository; UserState _state = UserState(); @@ -76,7 +77,10 @@ final class AppController extends ChangeNotifier { Future togglePinnedPair(PinnedPair pair) { final from = _engine.catalog.byId(pair.fromUnitId); final to = _engine.catalog.byId(pair.toUnitId); - if (from == null || to == null || from.category != pair.category || to.category != pair.category) { + if (from == null || + to == null || + from.category != pair.category || + to.category != pair.category) { throw ArgumentError('Pinned pair references invalid units.'); } final next = _state.pinnedPairs.toList(); @@ -127,7 +131,11 @@ final class AppController extends ChangeNotifier { Future addCustomUnit(CustomUnitData customUnit) { final definition = customUnit.toUnitDefinition(); if (_engine.catalog.byId(definition.id) != null) { - throw ArgumentError.value(definition.id, 'id', 'unit identifier already exists'); + throw ArgumentError.value( + definition.id, + 'id', + 'unit identifier already exists', + ); } final next = [..._state.customUnits, customUnit]; final newState = _state.copyWith(customUnits: next); @@ -140,8 +148,12 @@ final class AppController extends ChangeNotifier { if (existing.isEmpty) { return Future.value(); } - final nextCustom = _state.customUnits.where((item) => item.id != id).toList(); - final nextFavorites = _state.favoriteUnitIds.where((item) => item != id).toSet(); + final nextCustom = _state.customUnits + .where((item) => item.id != id) + .toList(); + final nextFavorites = _state.favoriteUnitIds + .where((item) => item != id) + .toSet(); final nextPins = _state.pinnedPairs .where((pair) => pair.fromUnitId != id && pair.toUnitId != id) .toList(); diff --git a/apps/unitflow_app/lib/app/app_shell.dart b/apps/unitflow_app/lib/app/app_shell.dart index ce384976..c34cd050 100644 --- a/apps/unitflow_app/lib/app/app_shell.dart +++ b/apps/unitflow_app/lib/app/app_shell.dart @@ -36,12 +36,18 @@ final class _AppShellState extends State { animation: widget.appController, builder: (context, _) => CallbackShortcuts( bindings: { - const SingleActivator(LogicalKeyboardKey.digit1, control: true): () => _select(0), - const SingleActivator(LogicalKeyboardKey.digit2, control: true): () => _select(1), - const SingleActivator(LogicalKeyboardKey.comma, control: true): () => _select(2), - const SingleActivator(LogicalKeyboardKey.digit1, meta: true): () => _select(0), - const SingleActivator(LogicalKeyboardKey.digit2, meta: true): () => _select(1), - const SingleActivator(LogicalKeyboardKey.comma, meta: true): () => _select(2), + const SingleActivator(LogicalKeyboardKey.digit1, control: true): () => + _select(0), + const SingleActivator(LogicalKeyboardKey.digit2, control: true): () => + _select(1), + const SingleActivator(LogicalKeyboardKey.comma, control: true): () => + _select(2), + const SingleActivator(LogicalKeyboardKey.digit1, meta: true): () => + _select(0), + const SingleActivator(LogicalKeyboardKey.digit2, meta: true): () => + _select(1), + const SingleActivator(LogicalKeyboardKey.comma, meta: true): () => + _select(2), }, child: Focus( autofocus: true, @@ -54,7 +60,10 @@ final class _AppShellState extends State { title: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.swap_calls, color: Theme.of(context).colorScheme.primary), + Icon( + Icons.swap_calls, + color: Theme.of(context).colorScheme.primary, + ), const SizedBox(width: AppSpacing.xs), const Text('UnitFlow'), ], @@ -149,10 +158,7 @@ final class _AppShellState extends State { index: _selectedIndex, children: [ ConverterScreen(controller: _converterController), - LibraryScreen( - appController: widget.appController, - onOpenPair: _openPair, - ), + LibraryScreen(appController: widget.appController, onOpenPair: _openPair), SettingsScreen( appController: widget.appController, onOpenAbout: _openAbout, @@ -173,6 +179,8 @@ final class _AppShellState extends State { } Future _openAbout() => Navigator.of(context).push( - MaterialPageRoute(builder: (_) => const Scaffold(body: SafeArea(child: AboutScreen()))), + MaterialPageRoute( + builder: (_) => const Scaffold(body: SafeArea(child: AboutScreen())), + ), ); } diff --git a/apps/unitflow_app/lib/app/theme/app_theme.dart b/apps/unitflow_app/lib/app/theme/app_theme.dart index fef05a01..0f581aab 100644 --- a/apps/unitflow_app/lib/app/theme/app_theme.dart +++ b/apps/unitflow_app/lib/app/theme/app_theme.dart @@ -29,7 +29,10 @@ abstract final class AppTheme { static ThemeData dark() => _build(Brightness.dark); static ThemeData _build(Brightness brightness) { - final colors = ColorScheme.fromSeed(seedColor: _seed, brightness: brightness); + final colors = ColorScheme.fromSeed( + seedColor: _seed, + brightness: brightness, + ); final base = ThemeData( useMaterial3: true, brightness: brightness, @@ -80,7 +83,9 @@ abstract final class AppTheme { ), tooltipTheme: TooltipThemeData( waitDuration: const Duration(milliseconds: 450), - textStyle: base.textTheme.bodySmall?.copyWith(color: colors.onInverseSurface), + textStyle: base.textTheme.bodySmall?.copyWith( + color: colors.onInverseSurface, + ), ), ); } diff --git a/apps/unitflow_app/lib/core/format/decimal_format.dart b/apps/unitflow_app/lib/core/format/decimal_format.dart index bf599494..582c555c 100644 --- a/apps/unitflow_app/lib/core/format/decimal_format.dart +++ b/apps/unitflow_app/lib/core/format/decimal_format.dart @@ -69,12 +69,16 @@ final class DecimalDisplayFormatter { whole = chunks.join(symbols.GROUP_SEP); } - final fraction = parts.length == 2 ? '${symbols.DECIMAL_SEP}${parts[1]}' : ''; + final fraction = parts.length == 2 + ? '${symbols.DECIMAL_SEP}${parts[1]}' + : ''; return '${negative ? '-' : ''}$whole$fraction'; } String _localizeMantissa(String formatted, String localeName) { - final separator = NumberFormat.decimalPattern(localeName).symbols.DECIMAL_SEP; + final separator = NumberFormat.decimalPattern(localeName) + .symbols + .DECIMAL_SEP; if (separator == '.') { return formatted; } @@ -82,7 +86,9 @@ final class DecimalDisplayFormatter { if (exponentIndex < 0) { return formatted.replaceFirst('.', separator); } - final mantissa = formatted.substring(0, exponentIndex).replaceFirst('.', separator); + final mantissa = formatted + .substring(0, exponentIndex) + .replaceFirst('.', separator); return '$mantissa${formatted.substring(exponentIndex)}'; } diff --git a/apps/unitflow_app/lib/core/math/exact_decimal.dart b/apps/unitflow_app/lib/core/math/exact_decimal.dart index b8a02c0e..f2f57ca6 100644 --- a/apps/unitflow_app/lib/core/math/exact_decimal.dart +++ b/apps/unitflow_app/lib/core/math/exact_decimal.dart @@ -27,9 +27,8 @@ final class ExactDecimal implements Comparable { throw const FormatException('Invalid decimal input'); } - final match = RegExp( - r'^([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$', - ).firstMatch(input); + final match = RegExp(r'^([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$') + .firstMatch(input); if (match == null) { throw const FormatException('Invalid decimal input'); } @@ -61,7 +60,8 @@ final class ExactDecimal implements Comparable { const ExactDecimal._(this.coefficient, this.scale); - static const zero = ExactDecimal._(BigInt.zero, 0); + /// Canonical zero. `BigInt.zero` is a runtime getter, so this cannot be a Dart `const`. + static final ExactDecimal zero = ExactDecimal._(BigInt.zero, 0); final BigInt coefficient; final int scale; diff --git a/apps/unitflow_app/lib/core/persistence/user_state.dart b/apps/unitflow_app/lib/core/persistence/user_state.dart index 34373cbb..23107ac8 100644 --- a/apps/unitflow_app/lib/core/persistence/user_state.dart +++ b/apps/unitflow_app/lib/core/persistence/user_state.dart @@ -32,11 +32,17 @@ final class RecentConversion { final from = value['fromUnitId']; final to = value['toUnitId']; final created = value['createdAt']; - if (input is! String || from is! String || to is! String || created is! String) { + if (input is! String || + from is! String || + to is! String || + created is! String) { return null; } final timestamp = DateTime.tryParse(created); - if (timestamp == null || from.isEmpty || to.isEmpty || input.length > 1024) { + if (timestamp == null || + from.isEmpty || + to.isEmpty || + input.length > 1024) { return null; } return RecentConversion( @@ -79,7 +85,8 @@ final class CustomUnitData { if (symbol.trim().isEmpty || symbol.length > 32) { throw const FormatException('Custom unit symbol is invalid.'); } - if (aliases.length > 32 || aliases.any((value) => value.isEmpty || value.length > 64)) { + if (aliases.length > 32 || + aliases.any((value) => value.isEmpty || value.length > 64)) { throw const FormatException('Custom unit aliases are invalid.'); } if (description.length > 512) { @@ -87,7 +94,9 @@ final class CustomUnitData { } final parsedScale = ExactDecimal.parse(scale); if (parsedScale.compareTo(ExactDecimal.zero) <= 0) { - throw const FormatException('Custom unit scale must be greater than zero.'); + throw const FormatException( + 'Custom unit scale must be greater than zero.', + ); } return UnitDefinition( id: id, @@ -176,10 +185,18 @@ final class UserState { List? pinnedPairs, List? recents, List? customUnits, - }) : favoriteUnitIds = Set.unmodifiable(favoriteUnitIds ?? {}), - pinnedPairs = List.unmodifiable(pinnedPairs ?? const []), - recents = List.unmodifiable(recents ?? const []), - customUnits = List.unmodifiable(customUnits ?? const []); + }) : favoriteUnitIds = Set.unmodifiable( + favoriteUnitIds ?? {}, + ), + pinnedPairs = List.unmodifiable( + pinnedPairs ?? const [], + ), + recents = List.unmodifiable( + recents ?? const [], + ), + customUnits = List.unmodifiable( + customUnits ?? const [], + ); static const schemaVersion = 1; @@ -223,9 +240,13 @@ final class UserState { 'useGrouping': useGrouping, 'onboardingComplete': onboardingComplete, 'favoriteUnitIds': favoriteUnitIds.toList(growable: false), - 'pinnedPairs': pinnedPairs.map((pair) => pair.storageValue).toList(growable: false), + 'pinnedPairs': pinnedPairs + .map((pair) => pair.storageValue) + .toList(growable: false), 'recents': recents.map((recent) => recent.toJson()).toList(growable: false), - 'customUnits': customUnits.map((unit) => unit.toJson()).toList(growable: false), + 'customUnits': customUnits + .map((unit) => unit.toJson()) + .toList(growable: false), }; static UserState fromJson(Map json) { @@ -245,8 +266,12 @@ final class UserState { throw const FormatException('Invalid UnitFlow preferences.'); } - final theme = ThemePreference.values.where((item) => item.name == json['theme']).firstOrNull; - final notation = DecimalNotation.values.where((item) => item.name == json['notation']).firstOrNull; + final theme = ThemePreference.values + .where((item) => item.name == json['theme']) + .firstOrNull; + final notation = DecimalNotation.values + .where((item) => item.name == json['notation']) + .firstOrNull; if (theme == null || notation == null) { throw const FormatException('Invalid UnitFlow appearance settings.'); } diff --git a/apps/unitflow_app/lib/core/persistence/user_state_repository.dart b/apps/unitflow_app/lib/core/persistence/user_state_repository.dart index 36ceb475..e48d720d 100644 --- a/apps/unitflow_app/lib/core/persistence/user_state_repository.dart +++ b/apps/unitflow_app/lib/core/persistence/user_state_repository.dart @@ -16,7 +16,8 @@ abstract interface class UserStateRepository { UserState importJson(String content); } -final class SharedPreferencesUserStateRepository implements UserStateRepository { +final class SharedPreferencesUserStateRepository + implements UserStateRepository { SharedPreferencesUserStateRepository({SharedPreferencesAsync? preferences}) : _preferences = preferences ?? SharedPreferencesAsync(); @@ -77,7 +78,8 @@ final class SharedPreferencesUserStateRepository implements UserStateRepository } final class MemoryUserStateRepository implements UserStateRepository { - MemoryUserStateRepository([UserState? initial]) : _state = initial ?? UserState(); + MemoryUserStateRepository([UserState? initial]) + : _state = initial ?? UserState(); UserState _state; diff --git a/apps/unitflow_app/lib/features/converter/data/unit_catalog.dart b/apps/unitflow_app/lib/features/converter/data/unit_catalog.dart index ae62534d..522ffd6d 100644 --- a/apps/unitflow_app/lib/features/converter/data/unit_catalog.dart +++ b/apps/unitflow_app/lib/features/converter/data/unit_catalog.dart @@ -43,142 +43,856 @@ final class UnitCatalog { return matches.take(limit).map((entry) => entry.$2).toList(growable: false); } - static final List builtInUnits = List.unmodifiable([ - // Length. - _u('meter', UnitCategory.length, 'Meter', 'm', '1', aliases: ['metre', 'meters', 'metres']), - _u('kilometer', UnitCategory.length, 'Kilometer', 'km', '1000', aliases: ['kilometre', 'kilo meter']), - _u('centimeter', UnitCategory.length, 'Centimeter', 'cm', '0.01', aliases: ['centimetre']), - _u('millimeter', UnitCategory.length, 'Millimeter', 'mm', '0.001', aliases: ['millimetre']), - _u('micrometer', UnitCategory.length, 'Micrometer', 'µm', '0.000001', aliases: ['micrometre', 'um', 'micron']), - _u('nanometer', UnitCategory.length, 'Nanometer', 'nm', '0.000000001', aliases: ['nanometre']), - _u('inch', UnitCategory.length, 'Inch', 'in', '0.0254', aliases: ['inches']), - _u('foot', UnitCategory.length, 'Foot', 'ft', '0.3048', aliases: ['feet']), - _u('yard', UnitCategory.length, 'Yard', 'yd', '0.9144', aliases: ['yards']), - _u('mile', UnitCategory.length, 'Mile', 'mi', '1609.344', aliases: ['miles', 'statute mile']), - _u('nautical_mile', UnitCategory.length, 'Nautical Mile', 'nmi', '1852', aliases: ['nautical miles']), - - // Area. - _u('square_meter', UnitCategory.area, 'Square Meter', 'm²', '1', aliases: ['m2', 'square metre']), - _u('square_kilometer', UnitCategory.area, 'Square Kilometer', 'km²', '1000000', aliases: ['km2', 'square kilometre']), - _u('square_centimeter', UnitCategory.area, 'Square Centimeter', 'cm²', '0.0001', aliases: ['cm2']), - _u('square_millimeter', UnitCategory.area, 'Square Millimeter', 'mm²', '0.000001', aliases: ['mm2']), - _u('hectare', UnitCategory.area, 'Hectare', 'ha', '10000', aliases: ['hectares']), - _u('acre', UnitCategory.area, 'Acre', 'ac', '4046.8564224', aliases: ['acres']), - _u('square_foot', UnitCategory.area, 'Square Foot', 'ft²', '0.09290304', aliases: ['ft2', 'square feet']), - _u('square_inch', UnitCategory.area, 'Square Inch', 'in²', '0.00064516', aliases: ['in2', 'square inches']), - _u('square_mile', UnitCategory.area, 'Square Mile', 'mi²', '2589988.110336', aliases: ['mi2']), - - // Volume. - _u('liter', UnitCategory.volume, 'Liter', 'L', '1', aliases: ['litre', 'liters', 'litres']), - _u('milliliter', UnitCategory.volume, 'Milliliter', 'mL', '0.001', aliases: ['millilitre', 'ml']), - _u('cubic_meter', UnitCategory.volume, 'Cubic Meter', 'm³', '1000', aliases: ['m3', 'cubic metre']), - _u('cubic_centimeter', UnitCategory.volume, 'Cubic Centimeter', 'cm³', '0.001', aliases: ['cm3', 'cc']), - _u('teaspoon_us', UnitCategory.volume, 'US Teaspoon', 'tsp', '0.00492892159375', aliases: ['teaspoon']), - _u('tablespoon_us', UnitCategory.volume, 'US Tablespoon', 'tbsp', '0.01478676478125', aliases: ['tablespoon']), - _u('fluid_ounce_us', UnitCategory.volume, 'US Fluid Ounce', 'fl oz', '0.0295735295625', aliases: ['fluid ounce']), - _u('cup_us', UnitCategory.volume, 'US Cup', 'cup', '0.2365882365', aliases: ['cups']), - _u('pint_us', UnitCategory.volume, 'US Pint', 'pt', '0.473176473', aliases: ['pints']), - _u('quart_us', UnitCategory.volume, 'US Quart', 'qt', '0.946352946', aliases: ['quarts']), - _u('gallon_us', UnitCategory.volume, 'US Gallon', 'gal', '3.785411784', aliases: ['us gallon', 'gallons']), - _u('gallon_imperial', UnitCategory.volume, 'Imperial Gallon', 'imp gal', '4.54609', aliases: ['uk gallon']), - - // Mass. - _u('kilogram', UnitCategory.mass, 'Kilogram', 'kg', '1', aliases: ['kilograms', 'kilo']), - _u('gram', UnitCategory.mass, 'Gram', 'g', '0.001', aliases: ['grams']), - _u('milligram', UnitCategory.mass, 'Milligram', 'mg', '0.000001', aliases: ['milligrams']), - _u('microgram', UnitCategory.mass, 'Microgram', 'µg', '0.000000001', aliases: ['ug', 'micrograms']), - _u('metric_tonne', UnitCategory.mass, 'Metric Tonne', 't', '1000', aliases: ['tonne', 'metric ton']), - _u('pound', UnitCategory.mass, 'Pound', 'lb', '0.45359237', aliases: ['pounds', 'lbs']), - _u('ounce', UnitCategory.mass, 'Ounce', 'oz', '0.028349523125', aliases: ['ounces']), - _u('stone', UnitCategory.mass, 'Stone', 'st', '6.35029318', aliases: ['stones']), - - // Speed. - _u('meter_per_second', UnitCategory.speed, 'Meter per Second', 'm/s', '1', aliases: ['mps', 'meters per second']), - _u('kilometer_per_hour', UnitCategory.speed, 'Kilometer per Hour', 'km/h', '0.2777777777777777777777777778', aliases: ['kph', 'kmph']), - _u('mile_per_hour', UnitCategory.speed, 'Mile per Hour', 'mph', '0.44704', aliases: ['miles per hour']), - _u('foot_per_second', UnitCategory.speed, 'Foot per Second', 'ft/s', '0.3048', aliases: ['fps', 'feet per second']), - _u('knot', UnitCategory.speed, 'Knot', 'kn', '0.5144444444444444444444444444', aliases: ['knots']), - - // Pressure. - _u('pascal', UnitCategory.pressure, 'Pascal', 'Pa', '1', aliases: ['pascals']), - _u('kilopascal', UnitCategory.pressure, 'Kilopascal', 'kPa', '1000', aliases: ['kilopascals']), - _u('megapascal', UnitCategory.pressure, 'Megapascal', 'MPa', '1000000', aliases: ['megapascals']), - _u('bar', UnitCategory.pressure, 'Bar', 'bar', '100000', aliases: ['bars']), - _u('millibar', UnitCategory.pressure, 'Millibar', 'mbar', '100', aliases: ['millibars', 'hectopascal']), - _u('standard_atmosphere', UnitCategory.pressure, 'Standard Atmosphere', 'atm', '101325', aliases: ['atmosphere']), - _u('psi', UnitCategory.pressure, 'Pound per Square Inch', 'psi', '6894.757293168361', aliases: ['pounds per square inch']), - _u('torr', UnitCategory.pressure, 'Torr', 'Torr', '133.3223684210526315789473684', aliases: ['mmhg']), - - // Energy. - _u('joule', UnitCategory.energy, 'Joule', 'J', '1', aliases: ['joules']), - _u('kilojoule', UnitCategory.energy, 'Kilojoule', 'kJ', '1000', aliases: ['kilojoules']), - _u('calorie', UnitCategory.energy, 'Calorie', 'cal', '4.184', aliases: ['thermochemical calorie']), - _u('kilocalorie', UnitCategory.energy, 'Kilocalorie', 'kcal', '4184', aliases: ['food calorie']), - _u('watt_hour', UnitCategory.energy, 'Watt-hour', 'Wh', '3600', aliases: ['watt hour']), - _u('kilowatt_hour', UnitCategory.energy, 'Kilowatt-hour', 'kWh', '3600000', aliases: ['kilowatt hour']), - _u('btu_it', UnitCategory.energy, 'British Thermal Unit (IT)', 'BTU', '1055.05585262', aliases: ['btu']), - _u('electronvolt', UnitCategory.energy, 'Electronvolt', 'eV', '0.0000000000000000001602176634', aliases: ['electron volt']), - - // Power. - _u('watt', UnitCategory.power, 'Watt', 'W', '1', aliases: ['watts']), - _u('kilowatt', UnitCategory.power, 'Kilowatt', 'kW', '1000', aliases: ['kilowatts']), - _u('megawatt', UnitCategory.power, 'Megawatt', 'MW', '1000000', aliases: ['megawatts']), - _u('gigawatt', UnitCategory.power, 'Gigawatt', 'GW', '1000000000', aliases: ['gigawatts']), - _u('horsepower_mechanical', UnitCategory.power, 'Mechanical Horsepower', 'hp', '745.69987158227022', aliases: ['horsepower']), - _u('horsepower_metric', UnitCategory.power, 'Metric Horsepower', 'PS', '735.49875', aliases: ['metric hp']), - - // Angle. - _u('radian', UnitCategory.angle, 'Radian', 'rad', '1', aliases: ['radians']), - _u('degree', UnitCategory.angle, 'Degree', '°', '0.0174532925199432957692369077', aliases: ['degrees', 'deg']), - _u('gradian', UnitCategory.angle, 'Gradian', 'gon', '0.0157079632679489661923132169', aliases: ['grad']), - _u('turn', UnitCategory.angle, 'Turn', 'turn', '6.2831853071795864769252867666', aliases: ['revolution', 'cycle']), - _u('arcminute', UnitCategory.angle, 'Arcminute', '′', '0.0002908882086657215961539485', aliases: ['arcmin']), - _u('arcsecond', UnitCategory.angle, 'Arcsecond', '″', '0.0000048481368110953599358991', aliases: ['arcsec']), - - // Data size. - _u('byte', UnitCategory.dataSize, 'Byte', 'B', '1', aliases: ['bytes', 'octet']), - _u('bit', UnitCategory.dataSize, 'Bit', 'bit', '0.125', aliases: ['bits']), - _u('kilobyte', UnitCategory.dataSize, 'Kilobyte', 'kB', '1000', aliases: ['kb decimal']), - _u('megabyte', UnitCategory.dataSize, 'Megabyte', 'MB', '1000000', aliases: ['mb decimal']), - _u('gigabyte', UnitCategory.dataSize, 'Gigabyte', 'GB', '1000000000', aliases: ['gb decimal']), - _u('terabyte', UnitCategory.dataSize, 'Terabyte', 'TB', '1000000000000', aliases: ['tb decimal']), - _u('kibibyte', UnitCategory.dataSize, 'Kibibyte', 'KiB', '1024', aliases: ['kib']), - _u('mebibyte', UnitCategory.dataSize, 'Mebibyte', 'MiB', '1048576', aliases: ['mib']), - _u('gibibyte', UnitCategory.dataSize, 'Gibibyte', 'GiB', '1073741824', aliases: ['gib']), - _u('tebibyte', UnitCategory.dataSize, 'Tebibyte', 'TiB', '1099511627776', aliases: ['tib']), - - // Frequency. - _u('hertz', UnitCategory.frequency, 'Hertz', 'Hz', '1', aliases: ['hz', 'cycles per second']), - _u('kilohertz', UnitCategory.frequency, 'Kilohertz', 'kHz', '1000', aliases: ['khz']), - _u('megahertz', UnitCategory.frequency, 'Megahertz', 'MHz', '1000000', aliases: ['mhz']), - _u('gigahertz', UnitCategory.frequency, 'Gigahertz', 'GHz', '1000000000', aliases: ['ghz']), - _u('revolution_per_minute', UnitCategory.frequency, 'Revolution per Minute', 'rpm', '0.0166666666666666666666666667', aliases: ['revolutions per minute']), - - // Time. - _u('second', UnitCategory.time, 'Second', 's', '1', aliases: ['seconds', 'sec']), - _u('millisecond', UnitCategory.time, 'Millisecond', 'ms', '0.001', aliases: ['milliseconds']), - _u('microsecond', UnitCategory.time, 'Microsecond', 'µs', '0.000001', aliases: ['us', 'microseconds']), - _u('nanosecond', UnitCategory.time, 'Nanosecond', 'ns', '0.000000001', aliases: ['nanoseconds']), - _u('minute', UnitCategory.time, 'Minute', 'min', '60', aliases: ['minutes']), - _u('hour', UnitCategory.time, 'Hour', 'h', '3600', aliases: ['hours', 'hr']), - _u('day', UnitCategory.time, 'Day', 'd', '86400', aliases: ['days']), - _u('week', UnitCategory.time, 'Week', 'wk', '604800', aliases: ['weeks']), - _u('julian_year', UnitCategory.time, 'Julian Year', 'a', '31557600', aliases: ['year 365.25 days']), - - // Temperature. - _u('kelvin', UnitCategory.temperature, 'Kelvin', 'K', '1', aliases: ['kelvins']), - _u('celsius', UnitCategory.temperature, 'Celsius', '°C', '1', offset: '273.15', aliases: ['centigrade', 'degrees celsius']), - _u('fahrenheit', UnitCategory.temperature, 'Fahrenheit', '°F', '0.5555555555555555555555555556', offset: '255.3722222222222222222222222', aliases: ['degrees fahrenheit']), - _u('rankine', UnitCategory.temperature, 'Rankine', '°R', '0.5555555555555555555555555556', aliases: ['degrees rankine']), - ]); + static final List builtInUnits = + List.unmodifiable([ + // Length. + _u( + 'meter', + UnitCategory.length, + 'Meter', + 'm', + '1', + aliases: ['metre', 'meters', 'metres'], + ), + _u( + 'kilometer', + UnitCategory.length, + 'Kilometer', + 'km', + '1000', + aliases: ['kilometre', 'kilo meter'], + ), + _u( + 'centimeter', + UnitCategory.length, + 'Centimeter', + 'cm', + '0.01', + aliases: ['centimetre'], + ), + _u( + 'millimeter', + UnitCategory.length, + 'Millimeter', + 'mm', + '0.001', + aliases: ['millimetre'], + ), + _u( + 'micrometer', + UnitCategory.length, + 'Micrometer', + 'µm', + '0.000001', + aliases: ['micrometre', 'um', 'micron'], + ), + _u( + 'nanometer', + UnitCategory.length, + 'Nanometer', + 'nm', + '0.000000001', + aliases: ['nanometre'], + ), + _u( + 'inch', + UnitCategory.length, + 'Inch', + 'in', + '0.0254', + aliases: ['inches'], + ), + _u( + 'foot', + UnitCategory.length, + 'Foot', + 'ft', + '0.3048', + aliases: ['feet'], + ), + _u( + 'yard', + UnitCategory.length, + 'Yard', + 'yd', + '0.9144', + aliases: ['yards'], + ), + _u( + 'mile', + UnitCategory.length, + 'Mile', + 'mi', + '1609.344', + aliases: ['miles', 'statute mile'], + ), + _u( + 'nautical_mile', + UnitCategory.length, + 'Nautical Mile', + 'nmi', + '1852', + aliases: ['nautical miles'], + ), + + // Area. + _u( + 'square_meter', + UnitCategory.area, + 'Square Meter', + 'm²', + '1', + aliases: ['m2', 'square metre'], + ), + _u( + 'square_kilometer', + UnitCategory.area, + 'Square Kilometer', + 'km²', + '1000000', + aliases: ['km2', 'square kilometre'], + ), + _u( + 'square_centimeter', + UnitCategory.area, + 'Square Centimeter', + 'cm²', + '0.0001', + aliases: ['cm2'], + ), + _u( + 'square_millimeter', + UnitCategory.area, + 'Square Millimeter', + 'mm²', + '0.000001', + aliases: ['mm2'], + ), + _u( + 'hectare', + UnitCategory.area, + 'Hectare', + 'ha', + '10000', + aliases: ['hectares'], + ), + _u( + 'acre', + UnitCategory.area, + 'Acre', + 'ac', + '4046.8564224', + aliases: ['acres'], + ), + _u( + 'square_foot', + UnitCategory.area, + 'Square Foot', + 'ft²', + '0.09290304', + aliases: ['ft2', 'square feet'], + ), + _u( + 'square_inch', + UnitCategory.area, + 'Square Inch', + 'in²', + '0.00064516', + aliases: ['in2', 'square inches'], + ), + _u( + 'square_mile', + UnitCategory.area, + 'Square Mile', + 'mi²', + '2589988.110336', + aliases: ['mi2'], + ), + + // Volume. + _u( + 'liter', + UnitCategory.volume, + 'Liter', + 'L', + '1', + aliases: ['litre', 'liters', 'litres'], + ), + _u( + 'milliliter', + UnitCategory.volume, + 'Milliliter', + 'mL', + '0.001', + aliases: ['millilitre', 'ml'], + ), + _u( + 'cubic_meter', + UnitCategory.volume, + 'Cubic Meter', + 'm³', + '1000', + aliases: ['m3', 'cubic metre'], + ), + _u( + 'cubic_centimeter', + UnitCategory.volume, + 'Cubic Centimeter', + 'cm³', + '0.001', + aliases: ['cm3', 'cc'], + ), + _u( + 'teaspoon_us', + UnitCategory.volume, + 'US Teaspoon', + 'tsp', + '0.00492892159375', + aliases: ['teaspoon'], + ), + _u( + 'tablespoon_us', + UnitCategory.volume, + 'US Tablespoon', + 'tbsp', + '0.01478676478125', + aliases: ['tablespoon'], + ), + _u( + 'fluid_ounce_us', + UnitCategory.volume, + 'US Fluid Ounce', + 'fl oz', + '0.0295735295625', + aliases: ['fluid ounce'], + ), + _u( + 'cup_us', + UnitCategory.volume, + 'US Cup', + 'cup', + '0.2365882365', + aliases: ['cups'], + ), + _u( + 'pint_us', + UnitCategory.volume, + 'US Pint', + 'pt', + '0.473176473', + aliases: ['pints'], + ), + _u( + 'quart_us', + UnitCategory.volume, + 'US Quart', + 'qt', + '0.946352946', + aliases: ['quarts'], + ), + _u( + 'gallon_us', + UnitCategory.volume, + 'US Gallon', + 'gal', + '3.785411784', + aliases: ['us gallon', 'gallons'], + ), + _u( + 'gallon_imperial', + UnitCategory.volume, + 'Imperial Gallon', + 'imp gal', + '4.54609', + aliases: ['uk gallon'], + ), + + // Mass. + _u( + 'kilogram', + UnitCategory.mass, + 'Kilogram', + 'kg', + '1', + aliases: ['kilograms', 'kilo'], + ), + _u( + 'gram', + UnitCategory.mass, + 'Gram', + 'g', + '0.001', + aliases: ['grams'], + ), + _u( + 'milligram', + UnitCategory.mass, + 'Milligram', + 'mg', + '0.000001', + aliases: ['milligrams'], + ), + _u( + 'microgram', + UnitCategory.mass, + 'Microgram', + 'µg', + '0.000000001', + aliases: ['ug', 'micrograms'], + ), + _u( + 'metric_tonne', + UnitCategory.mass, + 'Metric Tonne', + 't', + '1000', + aliases: ['tonne', 'metric ton'], + ), + _u( + 'pound', + UnitCategory.mass, + 'Pound', + 'lb', + '0.45359237', + aliases: ['pounds', 'lbs'], + ), + _u( + 'ounce', + UnitCategory.mass, + 'Ounce', + 'oz', + '0.028349523125', + aliases: ['ounces'], + ), + _u( + 'stone', + UnitCategory.mass, + 'Stone', + 'st', + '6.35029318', + aliases: ['stones'], + ), + + // Speed. + _u( + 'meter_per_second', + UnitCategory.speed, + 'Meter per Second', + 'm/s', + '1', + aliases: ['mps', 'meters per second'], + ), + _u( + 'kilometer_per_hour', + UnitCategory.speed, + 'Kilometer per Hour', + 'km/h', + '0.2777777777777777777777777778', + aliases: ['kph', 'kmph'], + ), + _u( + 'mile_per_hour', + UnitCategory.speed, + 'Mile per Hour', + 'mph', + '0.44704', + aliases: ['miles per hour'], + ), + _u( + 'foot_per_second', + UnitCategory.speed, + 'Foot per Second', + 'ft/s', + '0.3048', + aliases: ['fps', 'feet per second'], + ), + _u( + 'knot', + UnitCategory.speed, + 'Knot', + 'kn', + '0.5144444444444444444444444444', + aliases: ['knots'], + ), + + // Pressure. + _u( + 'pascal', + UnitCategory.pressure, + 'Pascal', + 'Pa', + '1', + aliases: ['pascals'], + ), + _u( + 'kilopascal', + UnitCategory.pressure, + 'Kilopascal', + 'kPa', + '1000', + aliases: ['kilopascals'], + ), + _u( + 'megapascal', + UnitCategory.pressure, + 'Megapascal', + 'MPa', + '1000000', + aliases: ['megapascals'], + ), + _u( + 'bar', + UnitCategory.pressure, + 'Bar', + 'bar', + '100000', + aliases: ['bars'], + ), + _u( + 'millibar', + UnitCategory.pressure, + 'Millibar', + 'mbar', + '100', + aliases: ['millibars', 'hectopascal'], + ), + _u( + 'standard_atmosphere', + UnitCategory.pressure, + 'Standard Atmosphere', + 'atm', + '101325', + aliases: ['atmosphere'], + ), + _u( + 'psi', + UnitCategory.pressure, + 'Pound per Square Inch', + 'psi', + '6894.757293168361', + aliases: ['pounds per square inch'], + ), + _u( + 'torr', + UnitCategory.pressure, + 'Torr', + 'Torr', + '133.3223684210526315789473684', + aliases: ['mmhg'], + ), + + // Energy. + _u( + 'joule', + UnitCategory.energy, + 'Joule', + 'J', + '1', + aliases: ['joules'], + ), + _u( + 'kilojoule', + UnitCategory.energy, + 'Kilojoule', + 'kJ', + '1000', + aliases: ['kilojoules'], + ), + _u( + 'calorie', + UnitCategory.energy, + 'Calorie', + 'cal', + '4.184', + aliases: ['thermochemical calorie'], + ), + _u( + 'kilocalorie', + UnitCategory.energy, + 'Kilocalorie', + 'kcal', + '4184', + aliases: ['food calorie'], + ), + _u( + 'watt_hour', + UnitCategory.energy, + 'Watt-hour', + 'Wh', + '3600', + aliases: ['watt hour'], + ), + _u( + 'kilowatt_hour', + UnitCategory.energy, + 'Kilowatt-hour', + 'kWh', + '3600000', + aliases: ['kilowatt hour'], + ), + _u( + 'btu_it', + UnitCategory.energy, + 'British Thermal Unit (IT)', + 'BTU', + '1055.05585262', + aliases: ['btu'], + ), + _u( + 'electronvolt', + UnitCategory.energy, + 'Electronvolt', + 'eV', + '0.0000000000000000001602176634', + aliases: ['electron volt'], + ), + + // Power. + _u( + 'watt', + UnitCategory.power, + 'Watt', + 'W', + '1', + aliases: ['watts'], + ), + _u( + 'kilowatt', + UnitCategory.power, + 'Kilowatt', + 'kW', + '1000', + aliases: ['kilowatts'], + ), + _u( + 'megawatt', + UnitCategory.power, + 'Megawatt', + 'MW', + '1000000', + aliases: ['megawatts'], + ), + _u( + 'gigawatt', + UnitCategory.power, + 'Gigawatt', + 'GW', + '1000000000', + aliases: ['gigawatts'], + ), + _u( + 'horsepower_mechanical', + UnitCategory.power, + 'Mechanical Horsepower', + 'hp', + '745.69987158227022', + aliases: ['horsepower'], + ), + _u( + 'horsepower_metric', + UnitCategory.power, + 'Metric Horsepower', + 'PS', + '735.49875', + aliases: ['metric hp'], + ), + + // Angle. + _u( + 'radian', + UnitCategory.angle, + 'Radian', + 'rad', + '1', + aliases: ['radians'], + ), + _u( + 'degree', + UnitCategory.angle, + 'Degree', + '°', + '0.0174532925199432957692369077', + aliases: ['degrees', 'deg'], + ), + _u( + 'gradian', + UnitCategory.angle, + 'Gradian', + 'gon', + '0.0157079632679489661923132169', + aliases: ['grad'], + ), + _u( + 'turn', + UnitCategory.angle, + 'Turn', + 'turn', + '6.2831853071795864769252867666', + aliases: ['revolution', 'cycle'], + ), + _u( + 'arcminute', + UnitCategory.angle, + 'Arcminute', + '′', + '0.0002908882086657215961539485', + aliases: ['arcmin'], + ), + _u( + 'arcsecond', + UnitCategory.angle, + 'Arcsecond', + '″', + '0.0000048481368110953599358991', + aliases: ['arcsec'], + ), + + // Data size. + _u( + 'byte', + UnitCategory.dataSize, + 'Byte', + 'B', + '1', + aliases: ['bytes', 'octet'], + ), + _u( + 'bit', + UnitCategory.dataSize, + 'Bit', + 'bit', + '0.125', + aliases: ['bits'], + ), + _u( + 'kilobyte', + UnitCategory.dataSize, + 'Kilobyte', + 'kB', + '1000', + aliases: ['kb decimal'], + ), + _u( + 'megabyte', + UnitCategory.dataSize, + 'Megabyte', + 'MB', + '1000000', + aliases: ['mb decimal'], + ), + _u( + 'gigabyte', + UnitCategory.dataSize, + 'Gigabyte', + 'GB', + '1000000000', + aliases: ['gb decimal'], + ), + _u( + 'terabyte', + UnitCategory.dataSize, + 'Terabyte', + 'TB', + '1000000000000', + aliases: ['tb decimal'], + ), + _u( + 'kibibyte', + UnitCategory.dataSize, + 'Kibibyte', + 'KiB', + '1024', + aliases: ['kib'], + ), + _u( + 'mebibyte', + UnitCategory.dataSize, + 'Mebibyte', + 'MiB', + '1048576', + aliases: ['mib'], + ), + _u( + 'gibibyte', + UnitCategory.dataSize, + 'Gibibyte', + 'GiB', + '1073741824', + aliases: ['gib'], + ), + _u( + 'tebibyte', + UnitCategory.dataSize, + 'Tebibyte', + 'TiB', + '1099511627776', + aliases: ['tib'], + ), + + // Frequency. + _u( + 'hertz', + UnitCategory.frequency, + 'Hertz', + 'Hz', + '1', + aliases: ['hz', 'cycles per second'], + ), + _u( + 'kilohertz', + UnitCategory.frequency, + 'Kilohertz', + 'kHz', + '1000', + aliases: ['khz'], + ), + _u( + 'megahertz', + UnitCategory.frequency, + 'Megahertz', + 'MHz', + '1000000', + aliases: ['mhz'], + ), + _u( + 'gigahertz', + UnitCategory.frequency, + 'Gigahertz', + 'GHz', + '1000000000', + aliases: ['ghz'], + ), + _u( + 'revolution_per_minute', + UnitCategory.frequency, + 'Revolution per Minute', + 'rpm', + '0.0166666666666666666666666667', + aliases: ['revolutions per minute'], + ), + + // Time. + _u( + 'second', + UnitCategory.time, + 'Second', + 's', + '1', + aliases: ['seconds', 'sec'], + ), + _u( + 'millisecond', + UnitCategory.time, + 'Millisecond', + 'ms', + '0.001', + aliases: ['milliseconds'], + ), + _u( + 'microsecond', + UnitCategory.time, + 'Microsecond', + 'µs', + '0.000001', + aliases: ['us', 'microseconds'], + ), + _u( + 'nanosecond', + UnitCategory.time, + 'Nanosecond', + 'ns', + '0.000000001', + aliases: ['nanoseconds'], + ), + _u( + 'minute', + UnitCategory.time, + 'Minute', + 'min', + '60', + aliases: ['minutes'], + ), + _u( + 'hour', + UnitCategory.time, + 'Hour', + 'h', + '3600', + aliases: ['hours', 'hr'], + ), + _u( + 'day', + UnitCategory.time, + 'Day', + 'd', + '86400', + aliases: ['days'], + ), + _u( + 'week', + UnitCategory.time, + 'Week', + 'wk', + '604800', + aliases: ['weeks'], + ), + _u( + 'julian_year', + UnitCategory.time, + 'Julian Year', + 'a', + '31557600', + aliases: ['year 365.25 days'], + ), + + // Temperature. + _u( + 'kelvin', + UnitCategory.temperature, + 'Kelvin', + 'K', + '1', + aliases: ['kelvins'], + ), + _u( + 'celsius', + UnitCategory.temperature, + 'Celsius', + '°C', + '1', + offset: '273.15', + aliases: ['centigrade', 'degrees celsius'], + ), + _u( + 'fahrenheit', + UnitCategory.temperature, + 'Fahrenheit', + '°F', + '0.5555555555555555555555555556', + offset: '255.3722222222222222222222222', + aliases: ['degrees fahrenheit'], + ), + _u( + 'rankine', + UnitCategory.temperature, + 'Rankine', + '°R', + '0.5555555555555555555555555556', + aliases: ['degrees rankine'], + ), + ]); } int _score(UnitDefinition unit, String query) { if (query.isEmpty) { return 3; } - final fields = [unit.id, unit.name, unit.symbol, ...unit.aliases] - .map((value) => value.toLowerCase()); + final fields = [ + unit.id, + unit.name, + unit.symbol, + ...unit.aliases, + ].map((value) => value.toLowerCase()); var best = 3; for (final field in fields) { if (field == query) { diff --git a/apps/unitflow_app/lib/features/converter/domain/conversion_engine.dart b/apps/unitflow_app/lib/features/converter/domain/conversion_engine.dart index bf54347b..ecacd10d 100644 --- a/apps/unitflow_app/lib/features/converter/domain/conversion_engine.dart +++ b/apps/unitflow_app/lib/features/converter/domain/conversion_engine.dart @@ -26,7 +26,8 @@ abstract interface class ConversionEngine { /// Native production integration can implement [ConversionEngine] with generated Rust bindings /// without changing presentation code. final class ExactConversionEngine implements ConversionEngine { - ExactConversionEngine({UnitCatalog? catalog}) : catalog = catalog ?? UnitCatalog(); + ExactConversionEngine({UnitCatalog? catalog}) + : catalog = catalog ?? UnitCatalog(); @override final UnitCatalog catalog; diff --git a/apps/unitflow_app/lib/features/converter/domain/unit_models.dart b/apps/unitflow_app/lib/features/converter/domain/unit_models.dart index 816587f4..515281a6 100644 --- a/apps/unitflow_app/lib/features/converter/domain/unit_models.dart +++ b/apps/unitflow_app/lib/features/converter/domain/unit_models.dart @@ -50,32 +50,22 @@ extension UnitCategoryInfo on UnitCategory { }; String get explanation => switch (this) { - UnitCategory.length => - 'Length measures distance between points. UnitFlow uses the meter as its base.', - UnitCategory.area => - 'Area measures two-dimensional surface size. UnitFlow uses the square meter as its base.', - UnitCategory.volume => - 'Volume measures three-dimensional capacity. UnitFlow uses the liter as its base.', - UnitCategory.mass => - 'Mass measures the amount of matter. UnitFlow uses the kilogram as its base.', - UnitCategory.speed => - 'Speed measures distance traveled per unit time. UnitFlow uses meters per second as its base.', + UnitCategory.length => 'Length measures distance between points. UnitFlow uses the meter as its base.', + UnitCategory.area => 'Area measures two-dimensional surface size. UnitFlow uses the square meter as its base.', + UnitCategory.volume => 'Volume measures three-dimensional capacity. UnitFlow uses the liter as its base.', + UnitCategory.mass => 'Mass measures the amount of matter. UnitFlow uses the kilogram as its base.', + UnitCategory.speed => 'Speed measures distance traveled per unit time. UnitFlow uses meters per second as its base.', UnitCategory.pressure => 'Pressure measures force per area. UnitFlow uses the pascal as its base.', - UnitCategory.energy => - 'Energy measures capacity to do work. UnitFlow uses the joule as its base.', - UnitCategory.power => - 'Power measures energy transferred per unit time. UnitFlow uses the watt as its base.', + UnitCategory.energy => 'Energy measures capacity to do work. UnitFlow uses the joule as its base.', + UnitCategory.power => 'Power measures energy transferred per unit time. UnitFlow uses the watt as its base.', UnitCategory.angle => 'Plane angle describes rotation. UnitFlow uses the radian as its base.', - UnitCategory.dataSize => - 'Data size measures digital information. UnitFlow uses the byte as its base and distinguishes decimal from binary prefixes.', - UnitCategory.frequency => - 'Frequency counts repeated events per second. UnitFlow uses the hertz as its base.', + UnitCategory.dataSize => 'Data size measures digital information. UnitFlow uses the byte as its base and distinguishes decimal from binary prefixes.', + UnitCategory.frequency => 'Frequency counts repeated events per second. UnitFlow uses the hertz as its base.', UnitCategory.time => 'Time measures duration. UnitFlow uses the second as its base.', - UnitCategory.temperature => - 'Temperature conversions are affine rather than purely multiplicative. UnitFlow uses kelvin as its base.', + UnitCategory.temperature => 'Temperature conversions are affine rather than purely multiplicative. UnitFlow uses kelvin as its base.', }; String get example => switch (this) { @@ -96,17 +86,17 @@ extension UnitCategoryInfo on UnitCategory { } final class UnitDefinition { - const UnitDefinition({ + UnitDefinition({ required this.id, required this.category, required this.name, required this.symbol, required this.scale, - this.offset = ExactDecimal.zero, + ExactDecimal? offset, this.aliases = const [], this.description = '', this.isBuiltIn = true, - }); + }) : offset = offset ?? ExactDecimal.zero; final String id; final UnitCategory category; diff --git a/apps/unitflow_app/lib/features/converter/presentation/converter_controller.dart b/apps/unitflow_app/lib/features/converter/presentation/converter_controller.dart index f14b0157..681bc219 100644 --- a/apps/unitflow_app/lib/features/converter/presentation/converter_controller.dart +++ b/apps/unitflow_app/lib/features/converter/presentation/converter_controller.dart @@ -36,7 +36,8 @@ final class ConverterController extends ChangeNotifier { List get categoryUnits => _appController.engine.catalog.forCategory(_category); - UnitDefinition? get fromUnit => _appController.engine.catalog.byId(_fromUnitId); + UnitDefinition? get fromUnit => + _appController.engine.catalog.byId(_fromUnitId); UnitDefinition? get toUnit => _appController.engine.catalog.byId(_toUnitId); PinnedPair get currentPair => PinnedPair( @@ -180,7 +181,10 @@ final class ConverterController extends ChangeNotifier { void applyPinnedPair(PinnedPair pair) { final from = _appController.engine.catalog.byId(pair.fromUnitId); final to = _appController.engine.catalog.byId(pair.toUnitId); - if (from == null || to == null || from.category != pair.category || to.category != pair.category) { + if (from == null || + to == null || + from.category != pair.category || + to.category != pair.category) { return; } _category = pair.category; diff --git a/apps/unitflow_app/lib/features/converter/presentation/converter_screen.dart b/apps/unitflow_app/lib/features/converter/presentation/converter_screen.dart index 733952f3..84990ff4 100644 --- a/apps/unitflow_app/lib/features/converter/presentation/converter_screen.dart +++ b/apps/unitflow_app/lib/features/converter/presentation/converter_screen.dart @@ -22,7 +22,9 @@ final class _ConverterScreenState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - widget.controller.setLocale(Localizations.localeOf(context).toLanguageTag()); + widget.controller.setLocale( + Localizations.localeOf(context).toLanguageTag(), + ); } @override @@ -101,7 +103,10 @@ final class _ConverterScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text('Batch conversion', style: Theme.of(context).textTheme.headlineSmall), + Text( + 'Batch conversion', + style: Theme.of(context).textTheme.headlineSmall, + ), const SizedBox(height: AppSpacing.xs), Text( 'From ${widget.controller.fromUnit?.name ?? 'selected unit'}', @@ -161,7 +166,10 @@ final class _ConverterCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Convert units', style: theme.textTheme.headlineMedium), + Text( + 'Convert units', + style: theme.textTheme.headlineMedium, + ), const SizedBox(height: AppSpacing.xxs), Text( 'Precise, local, and distraction-free.', @@ -173,7 +181,9 @@ final class _ConverterCard extends StatelessWidget { ), ), IconButton.filledTonal( - tooltip: controller.isCurrentPairPinned ? 'Unpin unit pair' : 'Pin unit pair', + tooltip: controller.isCurrentPairPinned + ? 'Unpin unit pair' + : 'Pin unit pair', onPressed: () => controller.toggleCurrentPairPinned(), icon: Icon( controller.isCurrentPairPinned @@ -220,7 +230,9 @@ final class _ConverterCard extends StatelessWidget { value: controller.fromUnitId, items: units.map((unit) => unit.id).toList(growable: false), itemLabel: (id) { - final unit = units.firstWhere((candidate) => candidate.id == id); + final unit = units.firstWhere( + (candidate) => candidate.id == id, + ); return '${unit.name} (${unit.symbol})'; }, onChanged: (id) { @@ -234,7 +246,9 @@ final class _ConverterCard extends StatelessWidget { value: controller.toUnitId, items: units.map((unit) => unit.id).toList(growable: false), itemLabel: (id) { - final unit = units.firstWhere((candidate) => candidate.id == id); + final unit = units.firstWhere( + (candidate) => candidate.id == id, + ); return '${unit.name} (${unit.symbol})'; }, onChanged: (id) { @@ -249,7 +263,9 @@ final class _ConverterCard extends StatelessWidget { children: [ source, Padding( - padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs), + padding: const EdgeInsets.symmetric( + vertical: AppSpacing.xs, + ), child: IconButton.filledTonal( tooltip: 'Swap source and target units', onPressed: controller.swapUnits, @@ -264,7 +280,9 @@ final class _ConverterCard extends StatelessWidget { children: [ Expanded(child: source), Padding( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.sm, + ), child: IconButton.filledTonal( tooltip: 'Swap source and target units', onPressed: controller.swapUnits, @@ -351,9 +369,9 @@ final class _ConverterCard extends StatelessWidget { if (!context.mounted) { return; } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Conversion result copied.')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Conversion result copied.'))); } } @@ -375,7 +393,10 @@ final class _ConverterSidePanel extends StatelessWidget { children: [ Row( children: [ - Icon(Icons.school_outlined, color: theme.colorScheme.primary), + Icon( + Icons.school_outlined, + color: theme.colorScheme.primary, + ), const SizedBox(width: AppSpacing.xs), Text('Learn', style: theme.textTheme.titleLarge), ], @@ -413,7 +434,9 @@ final class _ConverterSidePanel extends StatelessWidget { ? Icons.push_pin : Icons.push_pin_outlined, ), - label: Text(controller.isCurrentPairPinned ? 'Unpin pair' : 'Pin pair'), + label: Text( + controller.isCurrentPairPinned ? 'Unpin pair' : 'Pin pair', + ), ), ], ), diff --git a/apps/unitflow_app/lib/features/library/presentation/custom_unit_dialog.dart b/apps/unitflow_app/lib/features/library/presentation/custom_unit_dialog.dart index ef858858..d9c8d854 100644 --- a/apps/unitflow_app/lib/features/library/presentation/custom_unit_dialog.dart +++ b/apps/unitflow_app/lib/features/library/presentation/custom_unit_dialog.dart @@ -100,7 +100,8 @@ final class _CustomUnitDialogState extends State<_CustomUnitDialog> { decoration: const InputDecoration(labelText: 'Name'), textInputAction: TextInputAction.next, maxLength: 128, - validator: (value) => (value?.trim().isEmpty ?? true) ? 'Enter a name.' : null, + validator: (value) => + (value?.trim().isEmpty ?? true) ? 'Enter a name.' : null, ), const SizedBox(height: AppSpacing.sm), TextFormField( @@ -108,7 +109,8 @@ final class _CustomUnitDialogState extends State<_CustomUnitDialog> { decoration: const InputDecoration(labelText: 'Symbol'), textInputAction: TextInputAction.next, maxLength: 32, - validator: (value) => (value?.trim().isEmpty ?? true) ? 'Enter a symbol.' : null, + validator: (value) => + (value?.trim().isEmpty ?? true) ? 'Enter a symbol.' : null, ), const SizedBox(height: AppSpacing.sm), Row( @@ -122,7 +124,8 @@ final class _CustomUnitDialogState extends State<_CustomUnitDialog> { decimal: true, signed: true, ), - validator: (value) => (value?.trim().isEmpty ?? true) ? 'Required.' : null, + validator: (value) => + (value?.trim().isEmpty ?? true) ? 'Required.' : null, ), ), const SizedBox(width: AppSpacing.sm), @@ -134,7 +137,8 @@ final class _CustomUnitDialogState extends State<_CustomUnitDialog> { decimal: true, signed: true, ), - validator: (value) => (value?.trim().isEmpty ?? true) ? 'Required.' : null, + validator: (value) => + (value?.trim().isEmpty ?? true) ? 'Required.' : null, ), ), ], @@ -171,10 +175,7 @@ final class _CustomUnitDialogState extends State<_CustomUnitDialog> { onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), - FilledButton( - onPressed: _submit, - child: const Text('Create unit'), - ), + FilledButton(onPressed: _submit, child: const Text('Create unit')), ], ); diff --git a/apps/unitflow_app/lib/features/library/presentation/library_screen.dart b/apps/unitflow_app/lib/features/library/presentation/library_screen.dart index 5ee99b8a..9f297c08 100644 --- a/apps/unitflow_app/lib/features/library/presentation/library_screen.dart +++ b/apps/unitflow_app/lib/features/library/presentation/library_screen.dart @@ -40,8 +40,10 @@ final class _LibraryScreenState extends State { limit: 200, ); results.sort((left, right) { - final leftFavorite = widget.appController.state.favoriteUnitIds.contains(left.id); - final rightFavorite = widget.appController.state.favoriteUnitIds.contains(right.id); + final leftFavorite = widget.appController.state.favoriteUnitIds + .contains(left.id); + final rightFavorite = widget.appController.state.favoriteUnitIds + .contains(right.id); if (leftFavorite != rightFavorite) { return leftFavorite ? -1 : 1; } @@ -121,16 +123,18 @@ final class _LibraryScreenState extends State { ), sliver: SliverList.separated( itemCount: results.length, - separatorBuilder: (_, _) => const SizedBox(height: AppSpacing.xs), + separatorBuilder: (_, _) => + const SizedBox(height: AppSpacing.xs), itemBuilder: (context, index) => Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 1000), child: _UnitTile( unit: results[index], - isFavorite: widget.appController.state.favoriteUnitIds.contains( + isFavorite: widget.appController.state.favoriteUnitIds + .contains(results[index].id), + onFavorite: () => widget.appController.toggleFavorite( results[index].id, ), - onFavorite: () => widget.appController.toggleFavorite(results[index].id), onDeleteCustom: results[index].isBuiltIn ? null : () => _deleteCustomUnit(results[index]), @@ -158,17 +162,16 @@ final class _LibraryScreenState extends State { if (!mounted) { return; } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Could not create unit: $error')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Could not create unit: $error'))); return; } if (!mounted) { return; } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${data.name} added.')), - ); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('${data.name} added.'))); } Future _deleteCustomUnit(UnitDefinition unit) async { @@ -208,7 +211,10 @@ final class _Header extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Unit library', style: Theme.of(context).textTheme.headlineMedium), + Text( + 'Unit library', + style: Theme.of(context).textTheme.headlineMedium, + ), const SizedBox(height: AppSpacing.xxs), Text( 'Search built-in units, favorites, and your own validated custom units.', @@ -245,23 +251,30 @@ final class _PinnedPairs extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Pinned pairs', style: Theme.of(context).textTheme.titleMedium), + Text( + 'Pinned pairs', + style: Theme.of(context).textTheme.titleMedium, + ), const SizedBox(height: AppSpacing.xs), Wrap( spacing: AppSpacing.xs, runSpacing: AppSpacing.xs, - children: pairs.map((pair) { - final from = appController.engine.catalog.byId(pair.fromUnitId); - final to = appController.engine.catalog.byId(pair.toUnitId); - if (from == null || to == null) { - return const SizedBox.shrink(); - } - return ActionChip( - avatar: const Icon(Icons.push_pin_outlined, size: 18), - label: Text('${from.symbol} → ${to.symbol}'), - onPressed: () => onOpenPair(pair), - ); - }).toList(growable: false), + children: pairs + .map((pair) { + final from = appController.engine.catalog.byId( + pair.fromUnitId, + ); + final to = appController.engine.catalog.byId(pair.toUnitId); + if (from == null || to == null) { + return const SizedBox.shrink(); + } + return ActionChip( + avatar: const Icon(Icons.push_pin_outlined, size: 18), + label: Text('${from.symbol} → ${to.symbol}'), + onPressed: () => onOpenPair(pair), + ); + }) + .toList(growable: false), ), ], ), @@ -324,9 +337,13 @@ final class _UnitTile extends StatelessWidget { horizontal: AppSpacing.md, vertical: AppSpacing.xs, ), - leading: CircleAvatar(child: Text(unit.symbol, textAlign: TextAlign.center)), + leading: CircleAvatar( + child: Text(unit.symbol, textAlign: TextAlign.center), + ), title: Text(unit.name), - subtitle: Text('${unit.category.label} • ${unit.id}${unit.isBuiltIn ? '' : ' • Custom'}'), + subtitle: Text( + '${unit.category.label} • ${unit.id}${unit.isBuiltIn ? '' : ' • Custom'}', + ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -363,7 +380,10 @@ final class _EmptyLibrary extends StatelessWidget { color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(height: AppSpacing.md), - Text('No units match this search.', style: Theme.of(context).textTheme.titleMedium), + Text( + 'No units match this search.', + style: Theme.of(context).textTheme.titleMedium, + ), ], ), ), diff --git a/apps/unitflow_app/lib/features/onboarding/presentation/onboarding_screen.dart b/apps/unitflow_app/lib/features/onboarding/presentation/onboarding_screen.dart index 2c4852e1..7d17e25a 100644 --- a/apps/unitflow_app/lib/features/onboarding/presentation/onboarding_screen.dart +++ b/apps/unitflow_app/lib/features/onboarding/presentation/onboarding_screen.dart @@ -66,7 +66,9 @@ final class _OnboardingScreenState extends State { final item = _pages[index]; return Center( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xl), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xl, + ), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 620), child: Column( @@ -77,7 +79,9 @@ final class _OnboardingScreenState extends State { height: 120, decoration: BoxDecoration( color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(AppRadii.large), + borderRadius: BorderRadius.circular( + AppRadii.large, + ), ), child: Icon( item.icon, @@ -123,7 +127,9 @@ final class _OnboardingScreenState extends State { _pages.length, (index) => AnimatedContainer( duration: const Duration(milliseconds: 180), - margin: const EdgeInsets.symmetric(horizontal: AppSpacing.xxs), + margin: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxs, + ), width: index == _page ? 28 : 8, height: 8, decoration: BoxDecoration( @@ -140,7 +146,11 @@ final class _OnboardingScreenState extends State { width: double.infinity, child: FilledButton( onPressed: _next, - child: Text(_page == _pages.length - 1 ? 'Start converting' : 'Next'), + child: Text( + _page == _pages.length - 1 + ? 'Start converting' + : 'Next', + ), ), ), const SizedBox(height: AppSpacing.sm), diff --git a/apps/unitflow_app/lib/features/settings/presentation/about_screen.dart b/apps/unitflow_app/lib/features/settings/presentation/about_screen.dart index 20454511..3718a848 100644 --- a/apps/unitflow_app/lib/features/settings/presentation/about_screen.dart +++ b/apps/unitflow_app/lib/features/settings/presentation/about_screen.dart @@ -40,19 +40,25 @@ final class AboutScreen extends StatelessWidget { icon: Icons.support_agent, title: 'Support email', subtitle: 'supportramsandesh@gmail.com', - uri: Uri.parse('mailto:supportramsandesh@gmail.com?subject=UnitFlow%20Support'), + uri: Uri.parse( + 'mailto:supportramsandesh@gmail.com?subject=UnitFlow%20Support', + ), ), _ExternalTile( icon: Icons.business_outlined, title: 'Business email', subtitle: 'sanskarin@outlook.in', - uri: Uri.parse('mailto:sanskarin@outlook.in?subject=UnitFlow'), + uri: Uri.parse( + 'mailto:sanskarin@outlook.in?subject=UnitFlow', + ), ), _ExternalTile( icon: Icons.alternate_email, title: 'Business email (alternate)', subtitle: 'sanskarin.business@gmail.com', - uri: Uri.parse('mailto:sanskarin.business@gmail.com?subject=UnitFlow'), + uri: Uri.parse( + 'mailto:sanskarin.business@gmail.com?subject=UnitFlow', + ), ), ], ), @@ -63,7 +69,10 @@ final class AboutScreen extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Privacy', style: Theme.of(context).textTheme.titleLarge), + Text( + 'Privacy', + style: Theme.of(context).textTheme.titleLarge, + ), const SizedBox(height: AppSpacing.sm), const Text( 'Static conversions work offline and do not require an account. Preferences, favorites, history, pinned pairs, and custom units are designed to remain on this device unless you explicitly export them.', @@ -105,7 +114,10 @@ final class _IdentityCard extends StatelessWidget { const SizedBox(height: AppSpacing.md), Text('UnitFlow', style: theme.textTheme.headlineMedium), const SizedBox(height: AppSpacing.xs), - Text('Version ${AboutScreen.appVersion}', style: theme.textTheme.bodyMedium), + Text( + 'Version ${AboutScreen.appVersion}', + style: theme.textTheme.bodyMedium, + ), const SizedBox(height: AppSpacing.md), const Text( 'A precise, offline-first unit converter with a Rust domain core and Flutter interface.', @@ -114,7 +126,9 @@ final class _IdentityCard extends StatelessWidget { const SizedBox(height: AppSpacing.lg), Text( 'Made by the Sanskar', - style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700), + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + ), ), const SizedBox(height: AppSpacing.xs), const Text('Open source under the MIT License.'), @@ -181,9 +195,8 @@ final class _ExternalTile extends StatelessWidget { if (!context.mounted) { return; } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Could not open $title.')), - ); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('Could not open $title.'))); } } } diff --git a/apps/unitflow_app/lib/features/settings/presentation/settings_screen.dart b/apps/unitflow_app/lib/features/settings/presentation/settings_screen.dart index 3105e2cf..91d5b996 100644 --- a/apps/unitflow_app/lib/features/settings/presentation/settings_screen.dart +++ b/apps/unitflow_app/lib/features/settings/presentation/settings_screen.dart @@ -29,7 +29,10 @@ final class SettingsScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: AppSpacing.md), - Text('Settings', style: Theme.of(context).textTheme.headlineMedium), + Text( + 'Settings', + style: Theme.of(context).textTheme.headlineMedium, + ), const SizedBox(height: AppSpacing.lg), _SectionCard( title: 'Appearance', @@ -87,7 +90,9 @@ final class SettingsScreen extends StatelessWidget { SwitchListTile.adaptive( contentPadding: EdgeInsets.zero, title: const Text('Digit grouping'), - subtitle: const Text('Use locale-aware grouping separators in displayed results.'), + subtitle: const Text( + 'Use locale-aware grouping separators in displayed results.', + ), value: appController.state.useGrouping, onChanged: appController.setUseGrouping, ), @@ -133,7 +138,9 @@ final class SettingsScreen extends StatelessWidget { ListTile( contentPadding: EdgeInsets.zero, title: const Text('UnitFlow'), - subtitle: const Text('License, privacy, support, GitHub, funding, and credits'), + subtitle: const Text( + 'License, privacy, support, GitHub, funding, and credits', + ), trailing: const Icon(Icons.chevron_right), onTap: onOpenAbout, ), @@ -173,7 +180,9 @@ final class SettingsScreen extends StatelessWidget { } if (content == null || content.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('The clipboard does not contain backup JSON.')), + const SnackBar( + content: Text('The clipboard does not contain backup JSON.'), + ), ); return; } @@ -184,17 +193,16 @@ final class SettingsScreen extends StatelessWidget { if (!context.mounted) { return; } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Import rejected: $error')), - ); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('Import rejected: $error'))); return; } if (!context.mounted) { return; } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('UnitFlow backup imported.')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('UnitFlow backup imported.'))); } Future _confirmReset(BuildContext context) async { diff --git a/apps/unitflow_app/test/core/exact_decimal_test.dart b/apps/unitflow_app/test/core/exact_decimal_test.dart index eaa52943..468932d9 100644 --- a/apps/unitflow_app/test/core/exact_decimal_test.dart +++ b/apps/unitflow_app/test/core/exact_decimal_test.dart @@ -21,12 +21,21 @@ void main() { group('ExactDecimal arithmetic', () { test('adds and subtracts different scales', () { - expect((ExactDecimal.parse('1.2') + ExactDecimal.parse('0.03')).toString(), '1.23'); - expect((ExactDecimal.parse('1.2') - ExactDecimal.parse('0.03')).toString(), '1.17'); + expect( + (ExactDecimal.parse('1.2') + ExactDecimal.parse('0.03')).toString(), + '1.23', + ); + expect( + (ExactDecimal.parse('1.2') - ExactDecimal.parse('0.03')).toString(), + '1.17', + ); }); test('multiplies exactly', () { - expect((ExactDecimal.parse('0.1') * ExactDecimal.parse('0.2')).toString(), '0.02'); + expect( + (ExactDecimal.parse('0.1') * ExactDecimal.parse('0.2')).toString(), + '0.02', + ); }); test('divides to requested precision', () { diff --git a/apps/unitflow_app/test/features/conversion_engine_test.dart b/apps/unitflow_app/test/features/conversion_engine_test.dart index 5125f78f..ad1f4994 100644 --- a/apps/unitflow_app/test/features/conversion_engine_test.dart +++ b/apps/unitflow_app/test/features/conversion_engine_test.dart @@ -47,6 +47,10 @@ void main() { toUnitIds: const ['centimeter', 'kilometer', 'inch'], decimalPlaces: 6, ); - expect(results.map((result) => result.to.id), ['centimeter', 'kilometer', 'inch']); + expect(results.map((result) => result.to.id), [ + 'centimeter', + 'kilometer', + 'inch', + ]); }); } diff --git a/crates/unitflow_core/src/catalog.rs b/crates/unitflow_core/src/catalog.rs index db6201e1..2353c05b 100644 --- a/crates/unitflow_core/src/catalog.rs +++ b/crates/unitflow_core/src/catalog.rs @@ -28,131 +28,1028 @@ impl UnitCatalog { pub fn built_in() -> Result { let units = vec![ // Length — base meter. - u("meter", Category::Length, "Meter", "m", &["metre", "meters", "metres"], "SI base unit of length.", "1", "0")?, - u("kilometer", Category::Length, "Kilometer", "km", &["kilometre", "kilometers", "kilometres"], "One thousand meters.", "1000", "0")?, - u("centimeter", Category::Length, "Centimeter", "cm", &["centimetre", "centimeters"], "One hundredth of a meter.", "0.01", "0")?, - u("millimeter", Category::Length, "Millimeter", "mm", &["millimetre", "millimeters"], "One thousandth of a meter.", "0.001", "0")?, - u("micrometer", Category::Length, "Micrometer", "µm", &["micrometre", "um", "micron"], "One millionth of a meter.", "0.000001", "0")?, - u("nanometer", Category::Length, "Nanometer", "nm", &["nanometre"], "One billionth of a meter.", "0.000000001", "0")?, - u("inch", Category::Length, "Inch", "in", &["inches"], "International inch, exactly 0.0254 meter.", "0.0254", "0")?, - u("foot", Category::Length, "Foot", "ft", &["feet"], "International foot, exactly 0.3048 meter.", "0.3048", "0")?, - u("yard", Category::Length, "Yard", "yd", &["yards"], "International yard, exactly 0.9144 meter.", "0.9144", "0")?, - u("mile", Category::Length, "Mile", "mi", &["miles", "statute mile"], "International mile, exactly 1609.344 meters.", "1609.344", "0")?, - u("nautical_mile", Category::Length, "Nautical Mile", "nmi", &["nautical miles"], "International nautical mile, exactly 1852 meters.", "1852", "0")?, - + u( + "meter", + Category::Length, + "Meter", + "m", + &["metre", "meters", "metres"], + "SI base unit of length.", + "1", + "0", + )?, + u( + "kilometer", + Category::Length, + "Kilometer", + "km", + &["kilometre", "kilometers", "kilometres"], + "One thousand meters.", + "1000", + "0", + )?, + u( + "centimeter", + Category::Length, + "Centimeter", + "cm", + &["centimetre", "centimeters"], + "One hundredth of a meter.", + "0.01", + "0", + )?, + u( + "millimeter", + Category::Length, + "Millimeter", + "mm", + &["millimetre", "millimeters"], + "One thousandth of a meter.", + "0.001", + "0", + )?, + u( + "micrometer", + Category::Length, + "Micrometer", + "µm", + &["micrometre", "um", "micron"], + "One millionth of a meter.", + "0.000001", + "0", + )?, + u( + "nanometer", + Category::Length, + "Nanometer", + "nm", + &["nanometre"], + "One billionth of a meter.", + "0.000000001", + "0", + )?, + u( + "inch", + Category::Length, + "Inch", + "in", + &["inches"], + "International inch, exactly 0.0254 meter.", + "0.0254", + "0", + )?, + u( + "foot", + Category::Length, + "Foot", + "ft", + &["feet"], + "International foot, exactly 0.3048 meter.", + "0.3048", + "0", + )?, + u( + "yard", + Category::Length, + "Yard", + "yd", + &["yards"], + "International yard, exactly 0.9144 meter.", + "0.9144", + "0", + )?, + u( + "mile", + Category::Length, + "Mile", + "mi", + &["miles", "statute mile"], + "International mile, exactly 1609.344 meters.", + "1609.344", + "0", + )?, + u( + "nautical_mile", + Category::Length, + "Nautical Mile", + "nmi", + &["nautical miles"], + "International nautical mile, exactly 1852 meters.", + "1852", + "0", + )?, // Area — base square meter. - u("square_meter", Category::Area, "Square Meter", "m²", &["m2", "square metre"], "Area of a one-meter by one-meter square.", "1", "0")?, - u("square_kilometer", Category::Area, "Square Kilometer", "km²", &["km2", "square kilometre"], "One million square meters.", "1000000", "0")?, - u("square_centimeter", Category::Area, "Square Centimeter", "cm²", &["cm2", "square centimetre"], "One ten-thousandth of a square meter.", "0.0001", "0")?, - u("square_millimeter", Category::Area, "Square Millimeter", "mm²", &["mm2", "square millimetre"], "One millionth of a square meter.", "0.000001", "0")?, - u("hectare", Category::Area, "Hectare", "ha", &["hectares"], "Exactly 10,000 square meters.", "10000", "0")?, - u("acre", Category::Area, "Acre", "ac", &["acres"], "International acre.", "4046.8564224", "0")?, - u("square_foot", Category::Area, "Square Foot", "ft²", &["ft2", "square feet"], "Area of a one-foot square.", "0.09290304", "0")?, - u("square_inch", Category::Area, "Square Inch", "in²", &["in2", "square inches"], "Area of a one-inch square.", "0.00064516", "0")?, - u("square_mile", Category::Area, "Square Mile", "mi²", &["mi2", "square miles"], "Area of a one-mile square.", "2589988.110336", "0")?, - + u( + "square_meter", + Category::Area, + "Square Meter", + "m²", + &["m2", "square metre"], + "Area of a one-meter by one-meter square.", + "1", + "0", + )?, + u( + "square_kilometer", + Category::Area, + "Square Kilometer", + "km²", + &["km2", "square kilometre"], + "One million square meters.", + "1000000", + "0", + )?, + u( + "square_centimeter", + Category::Area, + "Square Centimeter", + "cm²", + &["cm2", "square centimetre"], + "One ten-thousandth of a square meter.", + "0.0001", + "0", + )?, + u( + "square_millimeter", + Category::Area, + "Square Millimeter", + "mm²", + &["mm2", "square millimetre"], + "One millionth of a square meter.", + "0.000001", + "0", + )?, + u( + "hectare", + Category::Area, + "Hectare", + "ha", + &["hectares"], + "Exactly 10,000 square meters.", + "10000", + "0", + )?, + u( + "acre", + Category::Area, + "Acre", + "ac", + &["acres"], + "International acre.", + "4046.8564224", + "0", + )?, + u( + "square_foot", + Category::Area, + "Square Foot", + "ft²", + &["ft2", "square feet"], + "Area of a one-foot square.", + "0.09290304", + "0", + )?, + u( + "square_inch", + Category::Area, + "Square Inch", + "in²", + &["in2", "square inches"], + "Area of a one-inch square.", + "0.00064516", + "0", + )?, + u( + "square_mile", + Category::Area, + "Square Mile", + "mi²", + &["mi2", "square miles"], + "Area of a one-mile square.", + "2589988.110336", + "0", + )?, // Volume — base liter. - u("liter", Category::Volume, "Liter", "L", &["litre", "liters", "litres"], "Metric unit of volume used as UnitFlow's volume base.", "1", "0")?, - u("milliliter", Category::Volume, "Milliliter", "mL", &["millilitre", "ml"], "One thousandth of a liter.", "0.001", "0")?, - u("cubic_meter", Category::Volume, "Cubic Meter", "m³", &["m3", "cubic metre"], "Exactly one thousand liters.", "1000", "0")?, - u("cubic_centimeter", Category::Volume, "Cubic Centimeter", "cm³", &["cm3", "cc"], "Exactly one milliliter.", "0.001", "0")?, - u("teaspoon_us", Category::Volume, "US Teaspoon", "tsp", &["teaspoon"], "US customary teaspoon.", "0.00492892159375", "0")?, - u("tablespoon_us", Category::Volume, "US Tablespoon", "tbsp", &["tablespoon"], "US customary tablespoon.", "0.01478676478125", "0")?, - u("fluid_ounce_us", Category::Volume, "US Fluid Ounce", "fl oz", &["fluid ounce"], "US customary fluid ounce.", "0.0295735295625", "0")?, - u("cup_us", Category::Volume, "US Cup", "cup", &["cups"], "US customary cup.", "0.2365882365", "0")?, - u("pint_us", Category::Volume, "US Pint", "pt", &["pints"], "US liquid pint.", "0.473176473", "0")?, - u("quart_us", Category::Volume, "US Quart", "qt", &["quarts"], "US liquid quart.", "0.946352946", "0")?, - u("gallon_us", Category::Volume, "US Gallon", "gal", &["us gallon", "gallons"], "US liquid gallon.", "3.785411784", "0")?, - u("gallon_imperial", Category::Volume, "Imperial Gallon", "imp gal", &["uk gallon", "imperial gallons"], "Imperial gallon.", "4.54609", "0")?, - + u( + "liter", + Category::Volume, + "Liter", + "L", + &["litre", "liters", "litres"], + "Metric unit of volume used as UnitFlow's volume base.", + "1", + "0", + )?, + u( + "milliliter", + Category::Volume, + "Milliliter", + "mL", + &["millilitre", "ml"], + "One thousandth of a liter.", + "0.001", + "0", + )?, + u( + "cubic_meter", + Category::Volume, + "Cubic Meter", + "m³", + &["m3", "cubic metre"], + "Exactly one thousand liters.", + "1000", + "0", + )?, + u( + "cubic_centimeter", + Category::Volume, + "Cubic Centimeter", + "cm³", + &["cm3", "cc"], + "Exactly one milliliter.", + "0.001", + "0", + )?, + u( + "teaspoon_us", + Category::Volume, + "US Teaspoon", + "tsp", + &["teaspoon"], + "US customary teaspoon.", + "0.00492892159375", + "0", + )?, + u( + "tablespoon_us", + Category::Volume, + "US Tablespoon", + "tbsp", + &["tablespoon"], + "US customary tablespoon.", + "0.01478676478125", + "0", + )?, + u( + "fluid_ounce_us", + Category::Volume, + "US Fluid Ounce", + "fl oz", + &["fluid ounce"], + "US customary fluid ounce.", + "0.0295735295625", + "0", + )?, + u( + "cup_us", + Category::Volume, + "US Cup", + "cup", + &["cups"], + "US customary cup.", + "0.2365882365", + "0", + )?, + u( + "pint_us", + Category::Volume, + "US Pint", + "pt", + &["pints"], + "US liquid pint.", + "0.473176473", + "0", + )?, + u( + "quart_us", + Category::Volume, + "US Quart", + "qt", + &["quarts"], + "US liquid quart.", + "0.946352946", + "0", + )?, + u( + "gallon_us", + Category::Volume, + "US Gallon", + "gal", + &["us gallon", "gallons"], + "US liquid gallon.", + "3.785411784", + "0", + )?, + u( + "gallon_imperial", + Category::Volume, + "Imperial Gallon", + "imp gal", + &["uk gallon", "imperial gallons"], + "Imperial gallon.", + "4.54609", + "0", + )?, // Mass — base kilogram. - u("kilogram", Category::Mass, "Kilogram", "kg", &["kilograms", "kilo"], "SI base unit of mass.", "1", "0")?, - u("gram", Category::Mass, "Gram", "g", &["grams"], "One thousandth of a kilogram.", "0.001", "0")?, - u("milligram", Category::Mass, "Milligram", "mg", &["milligrams"], "One millionth of a kilogram.", "0.000001", "0")?, - u("microgram", Category::Mass, "Microgram", "µg", &["ug", "micrograms"], "One billionth of a kilogram.", "0.000000001", "0")?, - u("metric_tonne", Category::Mass, "Metric Tonne", "t", &["tonne", "metric ton"], "Exactly one thousand kilograms.", "1000", "0")?, - u("pound", Category::Mass, "Pound", "lb", &["pounds", "lbs"], "International avoirdupois pound.", "0.45359237", "0")?, - u("ounce", Category::Mass, "Ounce", "oz", &["ounces"], "International avoirdupois ounce.", "0.028349523125", "0")?, - u("stone", Category::Mass, "Stone", "st", &["stones"], "Exactly fourteen international pounds.", "6.35029318", "0")?, - + u( + "kilogram", + Category::Mass, + "Kilogram", + "kg", + &["kilograms", "kilo"], + "SI base unit of mass.", + "1", + "0", + )?, + u( + "gram", + Category::Mass, + "Gram", + "g", + &["grams"], + "One thousandth of a kilogram.", + "0.001", + "0", + )?, + u( + "milligram", + Category::Mass, + "Milligram", + "mg", + &["milligrams"], + "One millionth of a kilogram.", + "0.000001", + "0", + )?, + u( + "microgram", + Category::Mass, + "Microgram", + "µg", + &["ug", "micrograms"], + "One billionth of a kilogram.", + "0.000000001", + "0", + )?, + u( + "metric_tonne", + Category::Mass, + "Metric Tonne", + "t", + &["tonne", "metric ton"], + "Exactly one thousand kilograms.", + "1000", + "0", + )?, + u( + "pound", + Category::Mass, + "Pound", + "lb", + &["pounds", "lbs"], + "International avoirdupois pound.", + "0.45359237", + "0", + )?, + u( + "ounce", + Category::Mass, + "Ounce", + "oz", + &["ounces"], + "International avoirdupois ounce.", + "0.028349523125", + "0", + )?, + u( + "stone", + Category::Mass, + "Stone", + "st", + &["stones"], + "Exactly fourteen international pounds.", + "6.35029318", + "0", + )?, // Speed — base meter per second. - u("meter_per_second", Category::Speed, "Meter per Second", "m/s", &["meters per second", "metres per second", "mps"], "SI coherent speed unit.", "1", "0")?, - u("kilometer_per_hour", Category::Speed, "Kilometer per Hour", "km/h", &["kilometre per hour", "kph", "kmph"], "Metric road-speed unit.", "0.2777777777777777777777777778", "0")?, - u("mile_per_hour", Category::Speed, "Mile per Hour", "mph", &["miles per hour"], "International miles per hour.", "0.44704", "0")?, - u("foot_per_second", Category::Speed, "Foot per Second", "ft/s", &["feet per second", "fps"], "International feet per second.", "0.3048", "0")?, - u("knot", Category::Speed, "Knot", "kn", &["knots", "nautical mile per hour"], "One nautical mile per hour.", "0.5144444444444444444444444444", "0")?, - + u( + "meter_per_second", + Category::Speed, + "Meter per Second", + "m/s", + &["meters per second", "metres per second", "mps"], + "SI coherent speed unit.", + "1", + "0", + )?, + u( + "kilometer_per_hour", + Category::Speed, + "Kilometer per Hour", + "km/h", + &["kilometre per hour", "kph", "kmph"], + "Metric road-speed unit.", + "0.2777777777777777777777777778", + "0", + )?, + u( + "mile_per_hour", + Category::Speed, + "Mile per Hour", + "mph", + &["miles per hour"], + "International miles per hour.", + "0.44704", + "0", + )?, + u( + "foot_per_second", + Category::Speed, + "Foot per Second", + "ft/s", + &["feet per second", "fps"], + "International feet per second.", + "0.3048", + "0", + )?, + u( + "knot", + Category::Speed, + "Knot", + "kn", + &["knots", "nautical mile per hour"], + "One nautical mile per hour.", + "0.5144444444444444444444444444", + "0", + )?, // Pressure — base pascal. - u("pascal", Category::Pressure, "Pascal", "Pa", &["pascals"], "SI pressure unit, one newton per square meter.", "1", "0")?, - u("kilopascal", Category::Pressure, "Kilopascal", "kPa", &["kilopascals"], "One thousand pascals.", "1000", "0")?, - u("megapascal", Category::Pressure, "Megapascal", "MPa", &["megapascals"], "One million pascals.", "1000000", "0")?, - u("bar", Category::Pressure, "Bar", "bar", &["bars"], "Exactly 100,000 pascals.", "100000", "0")?, - u("millibar", Category::Pressure, "Millibar", "mbar", &["millibars", "hectopascal"], "Exactly 100 pascals.", "100", "0")?, - u("standard_atmosphere", Category::Pressure, "Standard Atmosphere", "atm", &["atmosphere"], "Standard atmosphere, exactly 101325 pascals.", "101325", "0")?, - u("psi", Category::Pressure, "Pound per Square Inch", "psi", &["pounds per square inch"], "Pressure from one pound-force per square inch.", "6894.757293168361", "0")?, - u("torr", Category::Pressure, "Torr", "Torr", &["mmhg", "millimeter mercury"], "Approximately one 760th of a standard atmosphere.", "133.3223684210526315789473684", "0")?, - + u( + "pascal", + Category::Pressure, + "Pascal", + "Pa", + &["pascals"], + "SI pressure unit, one newton per square meter.", + "1", + "0", + )?, + u( + "kilopascal", + Category::Pressure, + "Kilopascal", + "kPa", + &["kilopascals"], + "One thousand pascals.", + "1000", + "0", + )?, + u( + "megapascal", + Category::Pressure, + "Megapascal", + "MPa", + &["megapascals"], + "One million pascals.", + "1000000", + "0", + )?, + u( + "bar", + Category::Pressure, + "Bar", + "bar", + &["bars"], + "Exactly 100,000 pascals.", + "100000", + "0", + )?, + u( + "millibar", + Category::Pressure, + "Millibar", + "mbar", + &["millibars", "hectopascal"], + "Exactly 100 pascals.", + "100", + "0", + )?, + u( + "standard_atmosphere", + Category::Pressure, + "Standard Atmosphere", + "atm", + &["atmosphere"], + "Standard atmosphere, exactly 101325 pascals.", + "101325", + "0", + )?, + u( + "psi", + Category::Pressure, + "Pound per Square Inch", + "psi", + &["pounds per square inch"], + "Pressure from one pound-force per square inch.", + "6894.757293168361", + "0", + )?, + u( + "torr", + Category::Pressure, + "Torr", + "Torr", + &["mmhg", "millimeter mercury"], + "Approximately one 760th of a standard atmosphere.", + "133.3223684210526315789473684", + "0", + )?, // Energy — base joule. - u("joule", Category::Energy, "Joule", "J", &["joules"], "SI energy unit.", "1", "0")?, - u("kilojoule", Category::Energy, "Kilojoule", "kJ", &["kilojoules"], "One thousand joules.", "1000", "0")?, - u("calorie", Category::Energy, "Calorie", "cal", &["thermochemical calorie"], "Thermochemical calorie, exactly 4.184 joules.", "4.184", "0")?, - u("kilocalorie", Category::Energy, "Kilocalorie", "kcal", &["food calorie", "calorie food"], "Thermochemical kilocalorie.", "4184", "0")?, - u("watt_hour", Category::Energy, "Watt-hour", "Wh", &["watt hour"], "Exactly 3600 joules.", "3600", "0")?, - u("kilowatt_hour", Category::Energy, "Kilowatt-hour", "kWh", &["kilowatt hour"], "Exactly 3.6 megajoules.", "3600000", "0")?, - u("btu_it", Category::Energy, "British Thermal Unit (IT)", "BTU", &["btu"], "International Table British thermal unit.", "1055.05585262", "0")?, - u("electronvolt", Category::Energy, "Electronvolt", "eV", &["electron volt"], "Energy acquired by one elementary charge through one volt.", "0.0000000000000000001602176634", "0")?, - + u( + "joule", + Category::Energy, + "Joule", + "J", + &["joules"], + "SI energy unit.", + "1", + "0", + )?, + u( + "kilojoule", + Category::Energy, + "Kilojoule", + "kJ", + &["kilojoules"], + "One thousand joules.", + "1000", + "0", + )?, + u( + "calorie", + Category::Energy, + "Calorie", + "cal", + &["thermochemical calorie"], + "Thermochemical calorie, exactly 4.184 joules.", + "4.184", + "0", + )?, + u( + "kilocalorie", + Category::Energy, + "Kilocalorie", + "kcal", + &["food calorie", "calorie food"], + "Thermochemical kilocalorie.", + "4184", + "0", + )?, + u( + "watt_hour", + Category::Energy, + "Watt-hour", + "Wh", + &["watt hour"], + "Exactly 3600 joules.", + "3600", + "0", + )?, + u( + "kilowatt_hour", + Category::Energy, + "Kilowatt-hour", + "kWh", + &["kilowatt hour"], + "Exactly 3.6 megajoules.", + "3600000", + "0", + )?, + u( + "btu_it", + Category::Energy, + "British Thermal Unit (IT)", + "BTU", + &["btu"], + "International Table British thermal unit.", + "1055.05585262", + "0", + )?, + u( + "electronvolt", + Category::Energy, + "Electronvolt", + "eV", + &["electron volt"], + "Energy acquired by one elementary charge through one volt.", + "0.0000000000000000001602176634", + "0", + )?, // Power — base watt. - u("watt", Category::Power, "Watt", "W", &["watts"], "SI power unit, one joule per second.", "1", "0")?, - u("kilowatt", Category::Power, "Kilowatt", "kW", &["kilowatts"], "One thousand watts.", "1000", "0")?, - u("megawatt", Category::Power, "Megawatt", "MW", &["megawatts"], "One million watts.", "1000000", "0")?, - u("gigawatt", Category::Power, "Gigawatt", "GW", &["gigawatts"], "One billion watts.", "1000000000", "0")?, - u("horsepower_mechanical", Category::Power, "Mechanical Horsepower", "hp", &["horsepower", "imperial horsepower"], "Mechanical horsepower.", "745.69987158227022", "0")?, - u("horsepower_metric", Category::Power, "Metric Horsepower", "PS", &["metric hp"], "Metric horsepower.", "735.49875", "0")?, - + u( + "watt", + Category::Power, + "Watt", + "W", + &["watts"], + "SI power unit, one joule per second.", + "1", + "0", + )?, + u( + "kilowatt", + Category::Power, + "Kilowatt", + "kW", + &["kilowatts"], + "One thousand watts.", + "1000", + "0", + )?, + u( + "megawatt", + Category::Power, + "Megawatt", + "MW", + &["megawatts"], + "One million watts.", + "1000000", + "0", + )?, + u( + "gigawatt", + Category::Power, + "Gigawatt", + "GW", + &["gigawatts"], + "One billion watts.", + "1000000000", + "0", + )?, + u( + "horsepower_mechanical", + Category::Power, + "Mechanical Horsepower", + "hp", + &["horsepower", "imperial horsepower"], + "Mechanical horsepower.", + "745.69987158227022", + "0", + )?, + u( + "horsepower_metric", + Category::Power, + "Metric Horsepower", + "PS", + &["metric hp"], + "Metric horsepower.", + "735.49875", + "0", + )?, // Angle — base radian. - u("radian", Category::Angle, "Radian", "rad", &["radians"], "SI coherent unit for plane angle.", "1", "0")?, - u("degree", Category::Angle, "Degree", "°", &["degrees", "deg"], "One 360th of a full turn.", "0.0174532925199432957692369077", "0")?, - u("gradian", Category::Angle, "Gradian", "gon", &["grad", "gradians"], "One 400th of a full turn.", "0.0157079632679489661923132169", "0")?, - u("turn", Category::Angle, "Turn", "turn", &["revolution", "cycle"], "One complete rotation.", "6.2831853071795864769252867666", "0")?, - u("arcminute", Category::Angle, "Arcminute", "′", &["minute of arc", "arcmin"], "One sixtieth of a degree.", "0.0002908882086657215961539485", "0")?, - u("arcsecond", Category::Angle, "Arcsecond", "″", &["second of arc", "arcsec"], "One sixtieth of an arcminute.", "0.0000048481368110953599358991", "0")?, - + u( + "radian", + Category::Angle, + "Radian", + "rad", + &["radians"], + "SI coherent unit for plane angle.", + "1", + "0", + )?, + u( + "degree", + Category::Angle, + "Degree", + "°", + &["degrees", "deg"], + "One 360th of a full turn.", + "0.0174532925199432957692369077", + "0", + )?, + u( + "gradian", + Category::Angle, + "Gradian", + "gon", + &["grad", "gradians"], + "One 400th of a full turn.", + "0.0157079632679489661923132169", + "0", + )?, + u( + "turn", + Category::Angle, + "Turn", + "turn", + &["revolution", "cycle"], + "One complete rotation.", + "6.2831853071795864769252867666", + "0", + )?, + u( + "arcminute", + Category::Angle, + "Arcminute", + "′", + &["minute of arc", "arcmin"], + "One sixtieth of a degree.", + "0.0002908882086657215961539485", + "0", + )?, + u( + "arcsecond", + Category::Angle, + "Arcsecond", + "″", + &["second of arc", "arcsec"], + "One sixtieth of an arcminute.", + "0.0000048481368110953599358991", + "0", + )?, // Data size — base byte. - u("byte", Category::DataSize, "Byte", "B", &["bytes", "octet"], "Eight bits.", "1", "0")?, - u("bit", Category::DataSize, "Bit", "bit", &["bits", "b"], "One eighth of a byte.", "0.125", "0")?, - u("kilobyte", Category::DataSize, "Kilobyte", "kB", &["kb decimal"], "Decimal kilobyte, 1000 bytes.", "1000", "0")?, - u("megabyte", Category::DataSize, "Megabyte", "MB", &["mb decimal"], "Decimal megabyte, 1,000,000 bytes.", "1000000", "0")?, - u("gigabyte", Category::DataSize, "Gigabyte", "GB", &["gb decimal"], "Decimal gigabyte, 1,000,000,000 bytes.", "1000000000", "0")?, - u("terabyte", Category::DataSize, "Terabyte", "TB", &["tb decimal"], "Decimal terabyte, 1,000,000,000,000 bytes.", "1000000000000", "0")?, - u("kibibyte", Category::DataSize, "Kibibyte", "KiB", &["kib"], "Binary data unit, 1024 bytes.", "1024", "0")?, - u("mebibyte", Category::DataSize, "Mebibyte", "MiB", &["mib"], "Binary data unit, 1,048,576 bytes.", "1048576", "0")?, - u("gibibyte", Category::DataSize, "Gibibyte", "GiB", &["gib"], "Binary data unit, 1,073,741,824 bytes.", "1073741824", "0")?, - u("tebibyte", Category::DataSize, "Tebibyte", "TiB", &["tib"], "Binary data unit, 1,099,511,627,776 bytes.", "1099511627776", "0")?, - + u( + "byte", + Category::DataSize, + "Byte", + "B", + &["bytes", "octet"], + "Eight bits.", + "1", + "0", + )?, + u( + "bit", + Category::DataSize, + "Bit", + "bit", + &["bits", "b"], + "One eighth of a byte.", + "0.125", + "0", + )?, + u( + "kilobyte", + Category::DataSize, + "Kilobyte", + "kB", + &["kb decimal"], + "Decimal kilobyte, 1000 bytes.", + "1000", + "0", + )?, + u( + "megabyte", + Category::DataSize, + "Megabyte", + "MB", + &["mb decimal"], + "Decimal megabyte, 1,000,000 bytes.", + "1000000", + "0", + )?, + u( + "gigabyte", + Category::DataSize, + "Gigabyte", + "GB", + &["gb decimal"], + "Decimal gigabyte, 1,000,000,000 bytes.", + "1000000000", + "0", + )?, + u( + "terabyte", + Category::DataSize, + "Terabyte", + "TB", + &["tb decimal"], + "Decimal terabyte, 1,000,000,000,000 bytes.", + "1000000000000", + "0", + )?, + u( + "kibibyte", + Category::DataSize, + "Kibibyte", + "KiB", + &["kib"], + "Binary data unit, 1024 bytes.", + "1024", + "0", + )?, + u( + "mebibyte", + Category::DataSize, + "Mebibyte", + "MiB", + &["mib"], + "Binary data unit, 1,048,576 bytes.", + "1048576", + "0", + )?, + u( + "gibibyte", + Category::DataSize, + "Gibibyte", + "GiB", + &["gib"], + "Binary data unit, 1,073,741,824 bytes.", + "1073741824", + "0", + )?, + u( + "tebibyte", + Category::DataSize, + "Tebibyte", + "TiB", + &["tib"], + "Binary data unit, 1,099,511,627,776 bytes.", + "1099511627776", + "0", + )?, // Frequency — base hertz. - u("hertz", Category::Frequency, "Hertz", "Hz", &["hz", "cycles per second"], "SI unit of frequency.", "1", "0")?, - u("kilohertz", Category::Frequency, "Kilohertz", "kHz", &["khz"], "One thousand hertz.", "1000", "0")?, - u("megahertz", Category::Frequency, "Megahertz", "MHz", &["mhz"], "One million hertz.", "1000000", "0")?, - u("gigahertz", Category::Frequency, "Gigahertz", "GHz", &["ghz"], "One billion hertz.", "1000000000", "0")?, - u("revolution_per_minute", Category::Frequency, "Revolution per Minute", "rpm", &["revolutions per minute"], "One revolution per minute.", "0.0166666666666666666666666667", "0")?, - + u( + "hertz", + Category::Frequency, + "Hertz", + "Hz", + &["hz", "cycles per second"], + "SI unit of frequency.", + "1", + "0", + )?, + u( + "kilohertz", + Category::Frequency, + "Kilohertz", + "kHz", + &["khz"], + "One thousand hertz.", + "1000", + "0", + )?, + u( + "megahertz", + Category::Frequency, + "Megahertz", + "MHz", + &["mhz"], + "One million hertz.", + "1000000", + "0", + )?, + u( + "gigahertz", + Category::Frequency, + "Gigahertz", + "GHz", + &["ghz"], + "One billion hertz.", + "1000000000", + "0", + )?, + u( + "revolution_per_minute", + Category::Frequency, + "Revolution per Minute", + "rpm", + &["revolutions per minute"], + "One revolution per minute.", + "0.0166666666666666666666666667", + "0", + )?, // Time — base second. - u("second", Category::Time, "Second", "s", &["seconds", "sec"], "SI base unit of time.", "1", "0")?, - u("millisecond", Category::Time, "Millisecond", "ms", &["milliseconds"], "One thousandth of a second.", "0.001", "0")?, - u("microsecond", Category::Time, "Microsecond", "µs", &["us", "microseconds"], "One millionth of a second.", "0.000001", "0")?, - u("nanosecond", Category::Time, "Nanosecond", "ns", &["nanoseconds"], "One billionth of a second.", "0.000000001", "0")?, - u("minute", Category::Time, "Minute", "min", &["minutes"], "Exactly sixty seconds.", "60", "0")?, - u("hour", Category::Time, "Hour", "h", &["hours", "hr"], "Exactly 3600 seconds.", "3600", "0")?, - u("day", Category::Time, "Day", "d", &["days"], "Exactly 86,400 seconds.", "86400", "0")?, - u("week", Category::Time, "Week", "wk", &["weeks"], "Exactly seven days.", "604800", "0")?, - u("julian_year", Category::Time, "Julian Year", "a", &["year 365.25 days"], "Exactly 365.25 days.", "31557600", "0")?, - + u( + "second", + Category::Time, + "Second", + "s", + &["seconds", "sec"], + "SI base unit of time.", + "1", + "0", + )?, + u( + "millisecond", + Category::Time, + "Millisecond", + "ms", + &["milliseconds"], + "One thousandth of a second.", + "0.001", + "0", + )?, + u( + "microsecond", + Category::Time, + "Microsecond", + "µs", + &["us", "microseconds"], + "One millionth of a second.", + "0.000001", + "0", + )?, + u( + "nanosecond", + Category::Time, + "Nanosecond", + "ns", + &["nanoseconds"], + "One billionth of a second.", + "0.000000001", + "0", + )?, + u( + "minute", + Category::Time, + "Minute", + "min", + &["minutes"], + "Exactly sixty seconds.", + "60", + "0", + )?, + u( + "hour", + Category::Time, + "Hour", + "h", + &["hours", "hr"], + "Exactly 3600 seconds.", + "3600", + "0", + )?, + u( + "day", + Category::Time, + "Day", + "d", + &["days"], + "Exactly 86,400 seconds.", + "86400", + "0", + )?, + u( + "week", + Category::Time, + "Week", + "wk", + &["weeks"], + "Exactly seven days.", + "604800", + "0", + )?, + u( + "julian_year", + Category::Time, + "Julian Year", + "a", + &["year 365.25 days"], + "Exactly 365.25 days.", + "31557600", + "0", + )?, // Temperature — base kelvin. base = input * scale + offset. - u("kelvin", Category::Temperature, "Kelvin", "K", &["kelvins"], "SI base unit of thermodynamic temperature.", "1", "0")?, - u("celsius", Category::Temperature, "Celsius", "°C", &["centigrade", "degrees celsius"], "Celsius temperature scale.", "1", "273.15")?, - u("fahrenheit", Category::Temperature, "Fahrenheit", "°F", &["degrees fahrenheit"], "Fahrenheit temperature scale.", "0.5555555555555555555555555556", "255.3722222222222222222222222")?, - u("rankine", Category::Temperature, "Rankine", "°R", &["degrees rankine"], "Absolute temperature scale using Fahrenheit-sized degrees.", "0.5555555555555555555555555556", "0")?, + u( + "kelvin", + Category::Temperature, + "Kelvin", + "K", + &["kelvins"], + "SI base unit of thermodynamic temperature.", + "1", + "0", + )?, + u( + "celsius", + Category::Temperature, + "Celsius", + "°C", + &["centigrade", "degrees celsius"], + "Celsius temperature scale.", + "1", + "273.15", + )?, + u( + "fahrenheit", + Category::Temperature, + "Fahrenheit", + "°F", + &["degrees fahrenheit"], + "Fahrenheit temperature scale.", + "0.5555555555555555555555555556", + "255.3722222222222222222222222", + )?, + u( + "rankine", + Category::Temperature, + "Rankine", + "°R", + &["degrees rankine"], + "Absolute temperature scale using Fahrenheit-sized degrees.", + "0.5555555555555555555555555556", + "0", + )?, ]; Self::new(units) diff --git a/crates/unitflow_core/src/model.rs b/crates/unitflow_core/src/model.rs index 000e7021..597cface 100644 --- a/crates/unitflow_core/src/model.rs +++ b/crates/unitflow_core/src/model.rs @@ -127,10 +127,12 @@ impl UnitDefinition { max: 64, }); } - if !id - .chars() - .all(|character| character.is_ascii_lowercase() || character.is_ascii_digit() || character == '_' || character == '-') - { + if !id.chars().all(|character| { + character.is_ascii_lowercase() + || character.is_ascii_digit() + || character == '_' + || character == '-' + }) { return Err(UnitFlowError::InvalidUnitId(id)); } if name.is_empty() { diff --git a/crates/unitflow_core/src/notation.rs b/crates/unitflow_core/src/notation.rs index 1c11a2a9..02961e08 100644 --- a/crates/unitflow_core/src/notation.rs +++ b/crates/unitflow_core/src/notation.rs @@ -21,7 +21,9 @@ pub fn format_decimal( round_mode: RoundMode, ) -> Result { if decimal_places.is_some_and(|places| places > 28) { - return Err(UnitFlowError::InvalidPrecision(decimal_places.unwrap_or(29))); + return Err(UnitFlowError::InvalidPrecision( + decimal_places.unwrap_or(29), + )); } match notation { diff --git a/crates/unitflow_core/tests/catalog.rs b/crates/unitflow_core/tests/catalog.rs index 0759104a..74880ab0 100644 --- a/crates/unitflow_core/tests/catalog.rs +++ b/crates/unitflow_core/tests/catalog.rs @@ -4,7 +4,10 @@ use unitflow_core::{Category, UnitCatalog}; fn built_in_catalog_covers_every_declared_category() { let catalog = UnitCatalog::built_in().expect("built-in constants must be valid"); - assert!(catalog.len() >= 100, "catalog should remain broad enough for the MVP"); + assert!( + catalog.len() >= 100, + "catalog should remain broad enough for the MVP" + ); for category in Category::ALL { assert!( catalog.get(category.base_unit_id()).is_some(), @@ -22,7 +25,10 @@ fn search_matches_symbols_names_and_aliases() { let catalog = UnitCatalog::built_in().expect("catalog"); assert_eq!(catalog.search("psi", None, 5)[0].id, "psi"); - assert_eq!(catalog.search("metre", Some(Category::Length), 5)[0].id, "meter"); + assert_eq!( + catalog.search("metre", Some(Category::Length), 5)[0].id, + "meter" + ); assert!(catalog .search("gallon", Some(Category::Volume), 10) .iter() diff --git a/crates/unitflow_core/tests/conversion.rs b/crates/unitflow_core/tests/conversion.rs index 6bc4a06a..c6479d21 100644 --- a/crates/unitflow_core/tests/conversion.rs +++ b/crates/unitflow_core/tests/conversion.rs @@ -112,7 +112,11 @@ fn explicit_round_modes_are_respected() { #[test] fn batch_conversion_preserves_requested_target_order() { let converter = Converter::with_built_in_catalog().expect("catalog"); - let targets = vec!["centimeter".to_owned(), "kilometer".to_owned(), "inch".to_owned()]; + let targets = vec![ + "centimeter".to_owned(), + "kilometer".to_owned(), + "inch".to_owned(), + ]; let results = converter .batch_convert( diff --git a/crates/unitflow_core/tests/custom_units.rs b/crates/unitflow_core/tests/custom_units.rs index 49aa8892..1c2d11f8 100644 --- a/crates/unitflow_core/tests/custom_units.rs +++ b/crates/unitflow_core/tests/custom_units.rs @@ -1,5 +1,7 @@ use rust_decimal::Decimal; -use unitflow_core::{convert_between, Category, CustomUnitDraft, RoundMode, UnitCatalog, UnitFlowError}; +use unitflow_core::{ + convert_between, Category, CustomUnitDraft, RoundMode, UnitCatalog, UnitFlowError, +}; fn custom_scale(scale: Decimal) -> CustomUnitDraft { CustomUnitDraft { @@ -18,7 +20,9 @@ fn custom_scale(scale: Decimal) -> CustomUnitDraft { fn validates_and_converts_custom_affine_unit() { let catalog = UnitCatalog::built_in().expect("catalog"); let meter = catalog.get("meter").expect("meter"); - let custom = custom_scale(Decimal::from(2_u32)).validate().expect("valid custom unit"); + let custom = custom_scale(Decimal::from(2_u32)) + .validate() + .expect("valid custom unit"); let value = convert_between( Decimal::from(3_u32), diff --git a/crates/unitflow_core/tests/notation.rs b/crates/unitflow_core/tests/notation.rs index 02f69ca0..ac1aa903 100644 --- a/crates/unitflow_core/tests/notation.rs +++ b/crates/unitflow_core/tests/notation.rs @@ -1,5 +1,5 @@ -use std::str::FromStr; use rust_decimal::Decimal; +use std::str::FromStr; use unitflow_core::{format_decimal, Notation, RoundMode, UnitFlowError}; fn dec(value: &str) -> Decimal { @@ -8,29 +8,58 @@ fn dec(value: &str) -> Decimal { #[test] fn formats_scientific_notation() { - let formatted = format_decimal(dec("12345"), Notation::Scientific, Some(4), RoundMode::NearestEven).expect("format"); + let formatted = format_decimal( + dec("12345"), + Notation::Scientific, + Some(4), + RoundMode::NearestEven, + ) + .expect("format"); assert_eq!(formatted, "1.2345e+4"); } #[test] fn formats_engineering_notation() { - let formatted = format_decimal(dec("12345"), Notation::Engineering, Some(4), RoundMode::NearestEven).expect("format"); + let formatted = format_decimal( + dec("12345"), + Notation::Engineering, + Some(4), + RoundMode::NearestEven, + ) + .expect("format"); assert_eq!(formatted, "12.345e+3"); - let small = format_decimal(dec("0.12"), Notation::Engineering, Some(4), RoundMode::NearestEven).expect("format"); + let small = format_decimal( + dec("0.12"), + Notation::Engineering, + Some(4), + RoundMode::NearestEven, + ) + .expect("format"); assert_eq!(small, "120e-3"); } #[test] fn zero_is_canonical() { - let formatted = format_decimal(Decimal::ZERO, Notation::Scientific, Some(3), RoundMode::NearestEven).expect("format"); + let formatted = format_decimal( + Decimal::ZERO, + Notation::Scientific, + Some(3), + RoundMode::NearestEven, + ) + .expect("format"); assert_eq!(formatted, "0"); } #[test] fn rejects_precision_above_decimal_capacity() { assert_eq!( - format_decimal(Decimal::ONE, Notation::Plain, Some(29), RoundMode::NearestEven), + format_decimal( + Decimal::ONE, + Notation::Plain, + Some(29), + RoundMode::NearestEven + ), Err(UnitFlowError::InvalidPrecision(29)) ); }