Skip to content

devkabiir/decorator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

30 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Decorator

LICENSE Build status Code coverage Pub version Commitizen friendly Commitizen style Maintained

πŸš€ About

Python's decorators in dart πŸŽ†

Note: This is a work-in-progress

πŸ“„ TOC

  1. πŸš€ About
  2. πŸ“„ TOC
  3. πŸ“– Usage
    1. 1. Decorator annotation
    2. 2. Decorator generator
  4. πŸš₯ Versioning
  5. πŸ“ Milestones
  6. πŸ… Principle
  7. πŸ‘₯ Contributing
  8. :octocat: Git
  9. πŸ’„ Code style
  10. βœ… Testing
  11. ✨ Features and πŸ›bugs
  12. πŸ“° Changelog
    1. 1. Decorator annotation
      1. 0.0.1
    2. 2. Decorator generator
      1. 0.0.1
  13. πŸ“œ License

πŸ“– Usage

1. Decorator annotation

import 'package:decorator/decorator.dart';
  
/// There are 2 ways for creating custom decorators
  
/// 1. Either use the provided [DecorateWith] decorator to create wrapper
/// functions and use [DecorateWith] as a proxy decorator
  
/// Greets the host
HostElement<R> greet<R>(HostElement<R> host) {
  print('Hello $host');
  
  return host;
}
  
/// 2. Or implement the [Wrapper] and [FunctionDecorator] interfaces, and create
/// the decorator yourself
  
/// Decorater that null checks all args
class ArgumentsNotNull implements Wrapper, FunctionDecorator {
  const ArgumentsNotNull();
  @override
  bool get runInRelease => true;
  
  @override
  HostElement<R> wraps<R>(HostElement<R> host) {
    if (host.args?.isNotEmpty ?? false) {
      for (var arg in host.args.keys) {
        if (host.args[arg] == null) {
          throw ArgumentError.notNull(arg);
        }
      }
    }
  
    if (host.kwargs?.keys?.isNotEmpty ?? false) {
      for (var kwarg in host.kwargs.keys) {
        if (host.kwargs[kwarg] == null) {
          throw ArgumentError.notNull(kwarg);
        }
      }
    }
  
    return host;
  }
}
  

2. Decorator generator

/// Creating a library is required for part generation to work
library decorator_generator.example;
  
/// Import the decorator package
import 'package:decorator/decorator.dart';
import 'package:logging/logging.dart';
  
/// Import the generated part
/// this is generated by running `pub run build_runner build`
part 'example.d.dart';
  
Future<void> main() async {
  print(joinArgs(['First'], arg2: ['Second']));
  print(joinArgs3(['First'], arg2: ['Second']));
  print(await joinArgs4(['First'], arg2: ['Second']));
  
  /// This will throw automatically before [_joinArgs2]
  /// gets executed
  // print(joinArgs2(['First'], null));
}
  
const Level myLevel = Level('mylevel', 555);
  
/// Logger for `decorator_generator.example`
final Logger logger = Logger('decorator_generator.example');
  
HostElement<R> greet<R>(HostElement<R> host) {
  print('Hello $host');
  
  return host;
}
  
/// This function joins its args
@MyLogger('_joinArgs', myLevel)
String _joinArgs(List<String> arg1, {List<String> arg2}) {
  print('_joingArgs executed');
  
  return ((arg1 ?? [])..addAll(arg2 ?? [])).join();
}
  
/// This function also joins its args but doesnt check them against being null
@ArgumentsNotNull()
String _joinArgs2(List<String> arg1, [List<String> arg2]) {
  print('_joinArgs2 executed');
  
  /// Here arguments are not being null checked
  /// but the [ArgumentsNotNull] decorator will throw even before this code gets
  /// executed
  return (arg1..addAll(arg2)).join();
}
  
/// This one also joins its args but it is decorated with a [HostWrapper], this
/// is useful when the decorater doesn't require any additional args.
@DecorateWith(greet)
String _joinArgs3(List<String> arg1, {List<String> arg2}) {
  print('_joingArgs3 executed');
  
  return ((arg1 ?? [])..addAll(arg2 ?? [])).join();
}
  
/// Another one just for fun
@MyLogger.detached('loggerName')
Future<String> _joinArgs4(List<String> arg1, {List<String> arg2}) async {
  print('_joingArgs4 executed');
  
  return ((arg1 ?? [])..addAll(arg2 ?? [])).join();
}
  
/// Decorater that null checks all args
class ArgumentsNotNull implements Wrapper, FunctionDecorator {
  const ArgumentsNotNull();
  @override
  bool get runInRelease => true;
  
  @override
  HostElement<R> wraps<R>(HostElement<R> host) {
    if (host.args?.isNotEmpty ?? false) {
      for (var arg in host.args.keys) {
        if (host.args[arg] == null) {
          throw ArgumentError.notNull(arg);
        }
      }
    }
  
    if (host.kwargs?.keys?.isNotEmpty ?? false) {
      for (var kwarg in host.kwargs.keys) {
        if (host.kwargs[kwarg] == null) {
          throw ArgumentError.notNull(kwarg);
        }
      }
    }
  
    return host;
  }
}
  
/// Decorater that logs the given host with any args
class MyLogger implements Wrapper, FunctionDecorator {
  /// Name of the logger to use
  final String loggerName;
  final Level logLevel;
  
  /// Wether this is a detached logger
  final bool isDetached;
  
  /// Uses the given logger [loggerName] for logging at [logLevel]
  const MyLogger(this.loggerName, [this.logLevel = Level.FINEST])
      : isDetached = false;
  
  /// Uses a detached logger for logging
  const MyLogger.detached(this.loggerName, [this.logLevel = Level.FINEST])
      : isDetached = true;
  
  @override
  bool get runInRelease => false;
  
  @override
  HostElement<R> wraps<R>(HostElement<R> host) {
    final message = '$host was called with '
        '\nargs:${host.args} and \nkwargs:${host.kwargs}';
  
    if (isDetached) {
      Logger.detached(loggerName).log(logLevel, message);
    } else {
      Logger(loggerName).log(logLevel, message);
    }
  
    return host;
  }
}
  

πŸš₯ Versioning

This project follows Semantic Versioning 2.0.0

πŸ“ Milestones

  • Prepare v1.0.0
    • allow decorating class methods
    • allow decorating class fields
    • allow decorating top-level methods
    • allow decorating top-level fields
    • allow decorating classes
    • allow decorating libraries

πŸ… Principle

This project follows The Twelve-Factor App principle

πŸ‘₯ Contributing

  • 🍴 Fork this repo

  • ⬇️ Clone your forked version
    git clone https://github.com/<you>/decorator.git

  • βž• Add this repo as a remote
    git remote add upstream https://github.com/devkabiir/decorator.git

  • ⏬ Make sure you have recent changes
    git fetch upstream

  • ✨ Make a new branch with your proposed changes/fixes/additions
    git checkout upstream/master -b name_of_your_branch

  • πŸ“‘ Make sure you follow guidelines for Git

  • ⏫ Push your changes
    git push origin name_of_your_branch

  • πŸ”ƒ Make a pull request

:octocat: Git

  • βœ”οΈ Sign all commits. Learn about signing-commits
  • Use commitizen with cz-emoji adapter
  • Check existing commits to get an idea
  • Run the pre_commit script from project root pub run pre_commit
  • If you're adding an and in your commit message, it should probably be separate commits
  • Link relevant issues/commits with a # sign in the commit message
  • Limit message length per line to 72 characters (excluding space required for linking issues/commits)
  • Add commit description if message isn't enough for explaining changes

πŸ’„ Code style

  • Maintain consistencies using included .editorconfig
  • Everything else as per standard dart guidelines

βœ… Testing

  • Add tests for each new addition/feature
  • Do not remove/change tests when refactoring
    • unless fixing already broken test.

✨ Features and πŸ›bugs

Please file feature requests and bugs at the issue-tracker.

πŸ“° Changelog

Changes for latest release at github-releases

1. Decorator annotation

0.0.1

  • Initial version

2. Decorator generator

0.0.1

  • Initial version

πŸ“œ License

MIT License
  
Copyright (c) 2019-Present Dinesh Ahuja <dev@kabiir.me>
  
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
  
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
  
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.