Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Documentation #64

Closed
azjezz opened this issue Sep 29, 2020 · 5 comments · Fixed by #153
Closed

Documentation #64

azjezz opened this issue Sep 29, 2020 · 5 comments · Fixed by #153
Labels
hacktoberfest Priority: High After critical issues are fixed, these should be dealt with before any further issues. Status: Available No one has claimed responsibility for resolving this issue. Type: Documentation Updating documentation
Milestone

Comments

@azjezz
Copy link
Owner

azjezz commented Sep 29, 2020

We should probably have documentation that people can use to lookup functions and their descriptions + a couple of examples.

@azjezz azjezz added Priority: High After critical issues are fixed, these should be dealt with before any further issues. Status: Available No one has claimed responsibility for resolving this issue. Type: Documentation Updating documentation labels Sep 29, 2020
@azjezz azjezz added this to the v1.0.0 milestone Sep 29, 2020
@lotfio
Copy link

lotfio commented Oct 1, 2020

do we have an example on how documentation files should look like ?
does this example help Arr

@azjezz
Copy link
Owner Author

azjezz commented Oct 1, 2020

I don't have an example in mind, but one thing to note is that we want to document generic functions, so instead of:

function at(array $array, $key)

it would be:

function at<Tk as array-key, Tv>(array<Tk, Tv> $array, Tk $key): Tv

as that's how the function would look like if PHP had generics, but currently we use Psalm for that.

Note, when using generics in code, i would recommand using hack as a language instead of php, so we get a better highlight for generics.

example:

the following results in broken highlighting:

```php
function at<Tk of array-key, Tv>(array<Tk, Tv> $array, Tk $key): Tv
```

this works as Hack syntax is similar to PHP and it supports generics.

```hack
function at<Tk of array-key, Tv>(array<Tk, Tv> $array, Tk $key): Tv
```

Another thing is that there's no need to have a folder for each version, as once we are done with 1.0, we would make another branch for that.


the ideal structure for the files would be something like this:

docs/
  README.md // <-- general docs about what PSL is .. etc
  installation.md // document how to install PSL and use it, also how to preload PSL.
  str/
    README.md // <-- general documentation of the Str component
    constants.md // <-- document all constants in that comonent ( usually they are in a constants.php file )
    function/    
      replace.md // <-- document the `Str\replace` function
      replace_ci.md // <-- document the `Str\replace_ci`
      ... 
    class/
      someclass.php
    interface/
      someinterface.php

so that every component can have its own folder, with a readme.md, in that readme we would also list all functions/classes of that component, with links to their documentation ( docs/str/README.md ):

example:

# Str

<-- documentation -->

## constants

- [`Psl\Str\FOO`](constants.md)
- [`Psl\Str\BAR`](constants.md)

## functions

- [`Psl\Str\replace`](function/replace.function.md)
- [`Psl\Str\replace_ci`](function/replace_ci.md)
...

## interfaces

- [`Psl\Str\SomeInterface`](interface/someinterface.md)

## classes

- [`Psl\Str\SomeClass`](class/someclass.md)

## traits

- [`Psl\Str\SomeTrait`](trait/sometrait.md)

@azjezz
Copy link
Owner Author

azjezz commented Oct 1, 2020

by the way, I would suggest documenting 1 component at the time and sending a PR for that instead of trying to document everything together, so it makes things easier to review, and we can focus on one thing at the time.

@veewee
Copy link
Collaborator

veewee commented Oct 1, 2020

Currently the code also contains detailed docblocks with code samples in various places.
I don't really know of any tooling, but would generating the docs from code make sense instead of manually keeping docs and code in sync?

@azjezz
Copy link
Owner Author

azjezz commented Oct 1, 2020

I have actually tried to do that in the past but gave up on it.

this is the generated I previously wrote ( a bit messy and incomplete ):

<?php

declare(strict_types=1);

namespace Local;

use Psl\{Arr, Str};
use Roave\BetterReflection\BetterReflection;
use Roave\BetterReflection\Reflection\ReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionConstant;
use Roave\BetterReflection\Reflection\ReflectionFunction;
use Roave\BetterReflection\Reflector\ConstantReflector;
use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
use RuntimeException;

require_once __DIR__ . '/../vendor/autoload.php';

$symbols = [];

$symbols['constants'] = get_defined_constants();
$symbols['functions'] = get_defined_functions()['user'];
$symbols['classes'] = get_declared_classes();
$symbols['interfaces'] = get_declared_interfaces();
$symbols['traits'] = get_declared_traits();


$extract_component = static function (string $type): ?string {
    if (!Str\starts_with($type, 'Psl\\')) {
        return null;
    }

    if (Str\contains($type, 'Internal')) {
        return null;
    }

    $name = Str\slice($type, Str\search_last($type, '\\') + 1);
    $component = Str\strip_suffix($type, Str\concat('\\', $name));
    $component = Str\strip_prefix($component, 'Psl\\');

    return $component;
};

$fix_function_casing = static function (string $function): string {
    $parts = Str\split($function, '\\');
    $parts = Arr\map($parts, fn(string $part) => Str\capitalize($part));
    $parts[Arr\last_key($parts)] = Str\lowercase(Arr\last($parts));
    return Str\join($parts, '\\');
};


/**
 * @psalm-var array<string, array{
 *      constants:  list<string>,
 *      functions:  list<string>,
 *      classes:    list<string>,
 *      interfaces: list<string>,
 *      traits:     list<string>
 * }> $components
 */
$components = [];
foreach ($symbols['constants'] as $symbol => $value) {
    $component = $extract_component($symbol);
    if (null === $component) {
        continue;
    }

    $components[$component]['constants'][] = $symbol;
}

foreach (['functions', 'classes', 'interfaces', 'traits'] as $type) {
    foreach ($symbols[$type] as $symbol) {
        if ('functions' === $type) {
            $symbol = $fix_function_casing($symbol);
        }

        $component = $extract_component($symbol);
        if (null === $component) {
            continue;
        }

        $components[$component][$type][] = $symbol;
    }
}

/**
 * @param ReflectionConstant|ReflectionClass|ReflectionFunction $reflection
 */
$extract_docblock = static function($reflection): ?string {
    $doc = $reflection->getDocComment();
    $lines = Str\split(Str\trim($doc), "\n");
    if (0 === Arr\count($lines) || (1 === Arr\count($lines) && Str\is_empty(Arr\first($lines)))) {
        return null;
    }

    $lines = Arr\slice($lines, 1, Arr\count($lines) - 2);
    $lines = Arr\map($lines, fn(string $line) => Str\strip_prefix($line, ' * '));
    $lines = Arr\map($lines, fn(string $line) => Str\strip_prefix($line, ' *'));
    $lines = Arr\filter($lines);
    $lines = Arr\filter($lines, fn(string $line) => !Str\starts_with($line, '@'));
    $lines = Arr\map($lines, static function(string $line): string {
        if (Str\starts_with($line, '    ')) {
            return Str\format('  %s', $line);
        }

        if (Str\ends_with($line, ':')) {
            return Str\format('    %s%s', $line, "\n");
        }

        return Str\format('     %s', $line);
    });

    return Str\join($lines, "\n");
};

$generate_constants_documentation = static function(string $component, array $constants) use($extract_docblock): string {

    $docs = <<<MARKDOWN
## Predefined Constants

The constants below are defined by the ${component} component of PSL.

MARKDOWN;

    $br = (new BetterReflection());
    $ref = new ConstantReflector(new SingleFileSourceLocator(
        Str\format('%s/%s/constants.php', __DIR__.'/../src/Psl', $component),
        $br->astLocator()
    ), $br->classReflector());

    foreach ($constants as $constant) {
        $reflection = $ref->reflect($constant);
        $docblock = $extract_docblock($reflection);

        $type = gettype(constant($constant));
        $type = 'double' === $type ? 'float' : $type;
        $nl = "\n";
        if ($docblock) {
            $nl .= <<<DOCS
    ${nl}${docblock}{$nl}{$nl}
DOCS;

        }
        $docs .= <<<DOCS
  - *${constant}* ( ${type} )${nl}
DOCS;
    }

    return $docs;
};

$generate_documentation = static function(string $type, string $symbol): string {
    die();
};

$mkdir = static function (string $directory): void {
    if (!is_dir($directory) && !mkdir($directory) && !is_dir($directory)) {
        throw new RuntimeException(sprintf('Directory "%s" was not created', $directory));
    }
};

foreach($components as $component => $symbols) {
    $directory = Str\format('%s/../docs/%s', __DIR__, Str\lowercase(Str\replace($component, '\\', '/')));
    $mkdir($directory);

    foreach ($symbols as $type => $list_of_symbols) {
        if ('constants' === $type) {
            $file = Str\format('%s/%s.md', $directory, $type);
            $documentation = $generate_constants_documentation($component, $list_of_symbols);
            file_put_contents($file, $documentation);
        } else {
            $sub_directory = Str\format('%s/%s', $directory, $type);
            $mkdir($sub_directory);

            continue;
            foreach ($list_of_symbols as $symbol) {
                $file = Str\format('%s/%s.md', $sub_directory, $symbol);
                $documentation = $generate_documentation($type, $symbol);

                file_put_contents($file, $documentation);
            }
        }
    }
}

cc @lotfio if you want to do that, you can use the generator as a starting point.

@azjezz azjezz pinned this issue Oct 15, 2020
@azjezz azjezz modified the milestones: 0.1.0, 1.0.0 Oct 30, 2020
@azjezz azjezz unpinned this issue Jan 18, 2021
@azjezz azjezz linked a pull request Mar 8, 2021 that will close this issue
@azjezz azjezz modified the milestones: 2.0.0, 1.5.0 Oct 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest Priority: High After critical issues are fixed, these should be dealt with before any further issues. Status: Available No one has claimed responsibility for resolving this issue. Type: Documentation Updating documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants