Skip to content

Prepare for v2#46

Merged
spolischook merged 1 commit intomasterfrom
unknown repository
Jul 29, 2018
Merged

Prepare for v2#46
spolischook merged 1 commit intomasterfrom
unknown repository

Conversation

@HeahDude
Copy link
Copy Markdown
Collaborator

@HeahDude HeahDude commented Dec 12, 2017

  • Require PHP 5.5 or 7.0 and Symfony 3.4 minimum
  • Refactored extension and enabled autowiring
  • [BC Break] Removed classes parameters
  • [BC Break] Removed the form data transformer
  • added a new text form type extension with a purifier listener to purify submitted data in all text based fields, using opt-in and custom profile thanks to dedicated options
  • added a new "exercise.html_purifier" tag to make custom purifier implementations available as profile through form options and Twig filter
  • added a purifiers registry to lazy load purifiers everywhere
  • added a Twig HTMLPurifierRuntime for better performances
  • upgraded the LICENSE and README files

TODO


For other PRs:

@HeahDude HeahDude closed this Dec 12, 2017
@HeahDude
Copy link
Copy Markdown
Collaborator Author

HeahDude commented Dec 12, 2017

Sorry for the noise I wanted to open a PR on my own fork to test the support of some Symfony 4 projects needing this dependency.

Thank you.

@HeahDude
Copy link
Copy Markdown
Collaborator Author

And btw if you're still interested in maintaining this bundle and any feature in there I would be glad to open a proper PR and add a BC layer if needed.

@spolischook
Copy link
Copy Markdown
Contributor

@HeahDude we need to made some branch for that. Please make PR to 4.0 branch

@spolischook spolischook reopened this Dec 12, 2017
@HeahDude
Copy link
Copy Markdown
Collaborator Author

:) So what do you need? I just remove the file headers and submit it?

@spolischook
Copy link
Copy Markdown
Contributor

Yep

@spolischook
Copy link
Copy Markdown
Contributor

Please check the tests locally after update - it must be green

@HeahDude HeahDude changed the base branch from master to 4.0 December 12, 2017 22:44
@HeahDude
Copy link
Copy Markdown
Collaborator Author

Should be good now.

@spolischook
Copy link
Copy Markdown
Contributor

@HeahDude many thanks for your contribution, I'll review you PR and let you know about results

@HeahDude
Copy link
Copy Markdown
Collaborator Author

HeahDude commented Dec 12, 2017

With pleasure :)

I'm not sure about the Symfony 2 compatibility, I have no project to test that right now.

@spolischook
Copy link
Copy Markdown
Contributor

It should be tested with symfony from composer.json

@spolischook spolischook changed the title Upgraded to v1 Upgraded to v4 Dec 12, 2017
@HeahDude
Copy link
Copy Markdown
Collaborator Author

HeahDude commented Dec 12, 2017

I confirm tests pass when forcing Symfony 2 requirements.

@HeahDude
Copy link
Copy Markdown
Collaborator Author

I fixed a few things about class name convention, some typos here and there, and enforced lazy loading while keeping BC with Symfony 2, that I had left around.

A review would be very welcome :). Thanks!

@HeahDude HeahDude changed the title Upgraded to v4 [WIP] Upgraded to v4 Dec 14, 2017
@spolischook
Copy link
Copy Markdown
Contributor

Please add phpunit as dev dependency to composer.json, and check that tests is passed

@HeahDude
Copy link
Copy Markdown
Collaborator Author

This is the case.

@HeahDude
Copy link
Copy Markdown
Collaborator Author

I've removed a form option about dynamic configs that do not play well with cache.

I've opened https://github.com/heahprod/HTMLPurifierBundle/pull/2 separately to ease the review but it should be merge before release as it breaks BC. It fixes #22 with custom profiles and #26.

Support for Symfony 2 has been drop to ease the code and rely on powerful features. So we should merge #44 to keep BC and full support of 0.2.x versions with Symfony 3.4 and 4.

I've updated the description.

Copy link
Copy Markdown

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

I would recommend to not bump PHP to 7.1 because it would prevent Symfony 3.4+PHP 5 users from using the bundle.

$locator = $container->getDefinition('exercise_html_purifier.purifiers_locator');

foreach ($container->findTaggedServiceIds('exercise.html_purifier') as $id => $tags) {
foreach ($tags as $tag) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

replace foreach by $tags[0]

private $cachedListeners;
private $purifiersRegistry;

public function __construct(HTMLPurifiersRegistry $registry)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

the type hint should be against an interface


$purifiers = [];
$purifiersLocator = $container
->register('exercise_html_purifier.purifiers_locator', ServiceLocator::class)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

you should use ServiceLocatorTagPass::register() instead (with 3rd arg)

Comment thread Form/Listener/HTMLPurifierListener.php Outdated
{
private $purifier;

public function __construct(\HTMLPurifier $purifier)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this should be lazy: inject two args: a registry + $profile

@spolischook
Copy link
Copy Markdown
Contributor

Thanks @nicolas-grekas for review!

@yakobe
Copy link
Copy Markdown

yakobe commented Feb 2, 2018

It this PR ready to be merged? It's one of the last things preventing a symfony 4 upgrade for us. Is there any way I can help out?

@spolischook
Copy link
Copy Markdown
Contributor

@yakobe can you try it manually and prove that it's works?

@alister
Copy link
Copy Markdown
Contributor

alister commented Feb 19, 2018

I've tested #44 with a Composer repository stanza (it works as expected, and the public service works), but I think that this PR does too much. If there was one PR to 'make this work with Symfony 4' (ie: only to make the service public), I'd take another look at it, and then I'd also look at other PRs for the additional functionality.

@stof
Copy link
Copy Markdown
Contributor

stof commented Feb 20, 2018

@alister see #47 for the Symfony 4 support.

$container->setAlias('exercise_html_purifier.'.$tags[0]['profile'], $id);
}

$locator->setArguments($purifiers);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

altering a ServiceLocator service is a bad idea, as the ServiceLocatorPass performs de-duplication of the service locators based on their config. You should rather ask the ServiceLocatorPass to register the service locator for the new set of arguments (or even better, configure the service locator only here in the compiler pass instead of doing it first in the DI extension and then here to find additional profiles)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed, thanks!

private function getHTMLPurifierListener($profile)
{
if (isset($this->cachedListeners[$profile])) {
return $this->cachedListeners[$profile];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this caching is useless IMO. The instantiation of the listener is cheap, and such memoization can lead to memory leaks in long-running processes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread Form/Listener/HTMLPurifierListener.php Outdated
*/
private function getPurifier()
{
if (null !== $this->cachedPurifier) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this memoization is not worth it IMO

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread README.md Outdated
#...

exercise_html_purifier.custom:
alias: \HTMLPurifier
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

that broken. You need to configure a HTMLPurifier alias targetting exercise_html_purifier.custom, not the opposite

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed, plus added alternative syntax.

}

$container->setParameter('exercise_html_purifier.cache_warmer.serializer.paths', array_unique($paths));
$container->register(HTMLPurifiersRegistry::class)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the registry should not be registered using its class name, but using an id instead. We don't want the HTMLPurifiersRegistry to be available for autowiring, only the interface.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread Resources/config/html_purifier.xml Outdated
<argument>%exercise_html_purifier.cache_warmer.serializer.paths%</argument>
<argument type="service" id="exercise_html_purifier.default" />
<tag name="kernel.cache_warmer" />
<defaults public="false" autowire="true" autoconfigure="true" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For shared bundles, using explicit configuration (rather than autowiring and autoconfiguration) is better, as it ensures that the bundle maintainer knows how the bundle gets configured.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

@stof
Copy link
Copy Markdown
Contributor

stof commented Mar 9, 2018

[BC Break] Removed the form data transformer

Why removing the data transformer ? This is a good way to perform HTML filtering.

@XWB
Copy link
Copy Markdown

XWB commented Jun 13, 2018

@HeahDude Any updates on this PR?

@HeahDude
Copy link
Copy Markdown
Collaborator Author

@XWB updated :).

@stof Thank you very much for the review.

Why removing the data transformer ? This is a good way to perform HTML filtering.

No... definitely not, the transformer is a wrong usage. Implementing the interface means changing the representation, one way to another. As long as an implementation cannot verify:

// Given $a
$b = $transformer->transform($a);

$a === $transformer->reverseTransform($b); // must be true

then this is either wrong or a hack (even if we hack Symfony, though I'm very confident I'll get rid of that for 5.0).

A view transformer is called three times in a submission process by the way.

Here we just want to normalize the submitted data, we don't want to alter model or norm data in any way. Another reason is that now the purify option superseed the default trim processing.
Bref, it's much much cleaner like this IMO.

Anyone to test/review it? Thanks!

@HeahDude
Copy link
Copy Markdown
Collaborator Author

https://github.com/heahprod/HTMLPurifierBundle/pull/2 has been rebased and fixed too.

@XWB
Copy link
Copy Markdown

XWB commented Jul 22, 2018

@stof Can you have a look as well?

->setPublic(false)
;
$container->setAlias(HTMLPurifiersRegistryInterface::class, 'exercise_html_purifier.purifiers_registry');
$container->setAlias(\HTMLPurifier::class, 'exercise_html_purifier.default');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

autoloading aliases should always be private

Comment thread HTMLPurifiersRegistry.php Outdated
return $this->purifiersLocator->get($profile);
}

throw new \InvalidArgumentException(sprintf('The profile "%s" is not registered.', $profile));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if you let $this->purifiersLocator->get trigger an exception instead, it might suggest alternative names.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ha, didn't know that thanks. Fixed.

Comment thread README.md Outdated
[configuration documentation]: http://htmlpurifier.org/live/configdoc/plain.html

## Cache Warming ##
## Configuration in Symfony 4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't this exactly the same than for Symfony 3, except for the path to the YAML file, which is not actually related to 3 vs 4 but to Flex vs non-Flex (you can use Flex with 3.4)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Right, fixed.

Comment thread README.md Outdated

## Purifiers Registry

A `Heah\HtmlPurifierBundle\HtmlPurifiersRegistry` class is registered by default
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wrong namespace

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch! Fixed.

Comment thread Twig/HTMLPurifierRuntime.php Outdated

class HTMLPurifierRuntime implements RuntimeExtensionInterface
{
private $purifiersRegistry = [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

initializing it to an empty array is wrong. This variable is not meant to contain an array

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Artefact of previous refactoring, good catch again, thanks! Fixed.

Comment thread composer.json Outdated
"description": "HTMLPurifier integration for your Symfony2 project",
"keywords": ["htmlpurifier"],
"description": "HTMLPurifier integration for your Symfony project",
"keywords": ["htmlpurifier", "html", "purifier", "symfony3", "symfony4"],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

keyword should be symfony, not symfony3 or symfony4

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread composer.json Outdated
"twig/twig": "~1.3|~2.0"
"phpunit/phpunit": "^6.5.4",
"symfony/form": "~3.4.1 || ^4.0.1",
"twig/twig": "~1.6.5 || ~2.0"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

runtimes are much newer than 1.6.5 IIRC. So this might be the wrong lowest bound.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Runtime have been introduced in 1.26 twigphp/Twig@9cb6860#diff-16e3dbccf13c8c447eff98d226c5d89bR813.
But this is more about namespaces, so I changed to 1.34|2.4. Thanks for the hint.

Comment thread composer.json Outdated
"php": ">=5.3.2",
"symfony/framework-bundle": "~2.0|~3.0",
"php": "^5.5.9|>=7.0.8",
"symfony/framework-bundle": "~3.4.1 || ^4.0.1",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does it actually depend on FrameworkBundle ? Or only on some other components ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, good question. I guess the http kernel could be added with the others require-dev deps for the cache warmer, keeping only the DI in the required ones. WDYT?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've done that.

Comment thread composer.json Outdated
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "4.0.x-dev"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this change looks wrong to me. Why making it a 4.0 release ? This would be a 2.0 one at most

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

A request from the maintainer in one of the first comment of this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I prefer to be consistent with symfony version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

but the bundle supports both 3.x and 4.x. So synchronized version number does not make sense. Thus, it means you restrict yourselves to do major versions every 2 years when Symfony does them, instead of doing them when you need them.

even official Symfony bundles developed in separate repos (MonologBundle, SwiftmailerBundle, MakerBundle) have separate version numbers (3.3.0, 3.2.2, 1.5.0 respectively).

@HeahDude
Copy link
Copy Markdown
Collaborator Author

Thanks again @stof. Comments addressed.

@HeahDude HeahDude changed the title [WIP] Upgraded to v4 Prepare for v4 Jul 23, 2018

$purifiers = [];

foreach ($container->findTaggedServiceIds('exercise.html_purifier') as $id => $tags) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Here (and in the error message) you could use the PURIFIER_TAG constant instead of the exercise.html_purifier string, right?

@@ -0,0 +1,68 @@
<?php
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Blank line here after <?php

@HeahDude
Copy link
Copy Markdown
Collaborator Author

@javiereguiluz All fixed, nice catch! Thank you for your review :)

try {
$registry = $container->findDefinition(HTMLPurifiersRegistryInterface::class);
} catch (ServiceNotFoundException $e) {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Instead of the try/catch block, why not something like this:

if (!$container->hasDefinition(HTMLPurifiersRegistryInterface::class)) {
    return;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There won't be any definition and the check will fail, we need to check for an alias here.

$purifier = $container->getDefinition($id);

if (empty($purifier->getArguments())) {
$configId = "exercise_html_purifier.config.$profile";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm not sure if this bundle follows Symfony style. If so, it should be 'exercise_html_purifier.config'.$profile;.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This syntax may be used in the core, it mainly depends on the contextual readability it provides versus perf. This is more performant like this :)

->addMethodCall('loadArray', array($config));
}
foreach ($configs as $name => $config) {
$configId = "exercise_html_purifier.config.$name";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm not sure if this bundle follows Symfony style. If so, it should be 'exercise_html_purifier.config'.$name;.

'exercise_html_purifier.' . $name,
new Definition('%exercise_html_purifier.class%', array(new Reference($configId)))
);
$container->register("exercise_html_purifier.$name", \HTMLPurifier::class)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same question as above :)


class ExerciseHTMLPurifierBundle extends Bundle
{
public function build(ContainerBuilder $container)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@inheritdoc is missing.

$this->profile = $profile;
}

public function purifySubmittedData(FormEvent $event)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method doc is missing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Following Symfony style here, we don't need to add : void.

{
private $purifiersRegistry;

public function __construct(HTMLPurifiersRegistryInterface $registry)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing docs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

What would be the benefit?

$this->purifiersRegistry = $registry;
}

public function getExtendedType()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The entire class is missing @inheritdoc comments.

Comment thread HTMLPurifiersRegistry.php
{
private $purifiersLocator;

public function __construct(ContainerInterface $purifiersLocator)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing docs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

which doc would be needed here ? Only duplicating the typehint would be useless

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not sure it would be valuable here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would swear Symfony did something similar. Apparently not.

@HeahDude
Copy link
Copy Markdown
Collaborator Author

Last comments addressed, I'll squash tomorrow. Thank you all for your reviews :).

@HeahDude
Copy link
Copy Markdown
Collaborator Author

So should I make it a 2.0 before squash? ping @spolischook

Comment thread Form/Listener/HTMLPurifierListener.php Outdated
}

/**
* @inheritdoc}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

CS

@XWB
Copy link
Copy Markdown

XWB commented Jul 27, 2018

IMO it should be 2.0, this PR supports both Symfony 3 and 4 anyway.

@spolischook
Copy link
Copy Markdown
Contributor

@HeahDude as mentioned stof and XWB lets go with 2.0 version.
Thanks for your work!

@HeahDude
Copy link
Copy Markdown
Collaborator Author

@spolischook Would you create a 1.x branch from master so I can make master the target here?

@HeahDude HeahDude changed the title Prepare for v4 Prepare for v2 Jul 28, 2018
@HeahDude HeahDude changed the base branch from 4.0 to master July 28, 2018 10:52
@HeahDude
Copy link
Copy Markdown
Collaborator Author

This is now ready to merge from my side. Once done I'll reopen https://github.com/heahprod/HTMLPurifierBundle/pull/2 on master in this repo.

- Require PHP 5.5 or 7.0 and Symfony 3.4 minimum
- Refactored extension and enabled autowiring
- [BC Break] Removed classes parameters
- [BC Break] Removed the form data transformer
- added a new text form type extension with a purifier listener to purify submitted data in all text based fields, using opt-in and custom profile thanks to dedicated options
- added a new "exercise.html_purifier" tag to make custom purifier implementations available as profile through form options and Twig filter
- added a purifiers registry to lazy load purifiers everywhere
- added a Twig HTMLPurifierRuntime for better performances
- upgraded the LICENSE and README files
@HeahDude
Copy link
Copy Markdown
Collaborator Author

HeahDude commented Jul 28, 2018

@spolischook rebased. Tests are green. Let's merge?

@HeahDude
Copy link
Copy Markdown
Collaborator Author

@spolischook heahprod#2 is rebased with green tests, and ready to be open on master after merge before we can review, merge and release v2 :).

@spolischook spolischook merged commit 728672b into Exercise:master Jul 29, 2018
@spolischook
Copy link
Copy Markdown
Contributor

Thanks @HeahDude for your contribution

@HeahDude
Copy link
Copy Markdown
Collaborator Author

Thank you for merging! Here's the following #52 :).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants