Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions src/License/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,29 @@

use Symfony\Component\Filesystem\Filesystem;

/**
* Generates LICENSE files from composer.json metadata.
*
* This class orchestrates the license generation workflow:
* 1. Reads metadata from composer.json via Reader
* 2. Resolves the license identifier to a template filename
* 3. Loads the license template content
* 4. Resolves placeholders with metadata (year, author, project, organization)
* 5. Writes the resulting LICENSE file to the target path
*
* Generation is skipped if a LICENSE file already exists or if the
* license is not supported.
*/
final readonly class Generator implements GeneratorInterface
{
/**
* @param Reader $reader
* @param Resolver $resolver
* @param TemplateLoader $templateLoader
* @param PlaceholderResolver $placeholderResolver
* @param Filesystem $filesystem
* Creates a new Generator instance.
*
* @param Reader $reader The reader for extracting metadata from composer.json
* @param Resolver $resolver The resolver for mapping license identifiers to templates
* @param TemplateLoader $templateLoader The loader for reading template files
* @param PlaceholderResolver $placeholderResolver The resolver for template placeholders
* @param Filesystem $filesystem The filesystem component for file operations
*/
public function __construct(
private Reader $reader,
Expand All @@ -38,9 +53,11 @@ public function __construct(
) {}

/**
* @param string $targetPath
* Generates a LICENSE file at the specified path.
*
* @param string $targetPath The full path where the LICENSE file should be written
*
* @return string|null
* @return string|null The generated license content, or null if generation failed
*/
public function generate(string $targetPath): ?string
{
Expand Down Expand Up @@ -84,7 +101,9 @@ public function generate(string $targetPath): ?string
}

/**
* @return bool
* Checks whether a supported license is present in composer.json.
*
* @return bool True if a supported license is defined, false otherwise
*/
public function hasLicense(): bool
{
Expand Down
20 changes: 17 additions & 3 deletions src/License/GeneratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,31 @@

namespace FastForward\DevTools\License;

/**
* Generates LICENSE files from composer.json metadata.
*
* This interface defines the contract for generating license files
* by reading composer.json and producing appropriate license content.
*/
interface GeneratorInterface
{
/**
* @param string $targetPath
* Generates a LICENSE file at the specified path.
*
* @return string|null
* Reads the license from composer.json, validates it's supported,
* loads the appropriate template, resolves placeholders, and writes
* the LICENSE file to the target path.
*
* @param string $targetPath The full path where the LICENSE file should be written
*
* @return string|null The generated license content, or null if generation failed
*/
public function generate(string $targetPath): ?string;

/**
* @return bool
* Checks whether a supported license is present in composer.json.
*
* @return bool True if a supported license is defined, false otherwise
*/
public function hasLicense(): bool;
}
24 changes: 22 additions & 2 deletions src/License/PlaceholderResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,31 @@

use function Safe\preg_replace;

/**
* Resolves placeholders in license templates with metadata values.
*
* This class replaces placeholders like {{ year }}, {{ author }}, {{ project }},
* {{ organization }}, and {{ copyright_holder }} with values from metadata.
* Unresolved placeholders are removed and excess newlines are normalized.
*/
final class PlaceholderResolver implements PlaceholderResolverInterface
{
/**
* @param array{year?: int, organization?: string, author?: string, project?: string} $metadata
* @param string $template
* Resolves placeholders in a license template with the provided metadata.
*
* Supported placeholders:
* - {{ year }} - The copyright year (defaults to current year)
* - {{ organization }} - The organization or vendor name
* - {{ author }} - The primary author name or email
* - {{ project }} - The project/package name
* - {{ copyright_holder }} - Organization or author (organization takes precedence)
*
* Unmatched placeholders are removed, and consecutive blank lines are normalized.
*
* @param string $template The license template content with placeholders
* @param array{year?: int, organization?: string, author?: string, project?: string} $metadata The metadata values to use for replacement
*
* @return string The template with all resolved placeholders
*/
public function resolve(string $template, array $metadata): string
{
Expand Down
14 changes: 12 additions & 2 deletions src/License/PlaceholderResolverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@

namespace FastForward\DevTools\License;

/**
* Resolves placeholders in license templates with metadata values.
*
* This interface defines the contract for replacing template placeholders
* such as [year], [author], [project] with actual values.
*/
interface PlaceholderResolverInterface
{
/**
* @param string $template
* @param array{year?: int, organization?: string, author?: string, project?: string} $metadata
* Resolves placeholders in a license template with the provided metadata.
*
* @param string $template The license template content with placeholders
* @param array{year?: int, organization?: string, author?: string, project?: string} $metadata The metadata values to use for replacement
*
* @return string The template with all resolved placeholders
*/
public function resolve(string $template, array $metadata): string;
}
56 changes: 47 additions & 9 deletions src/License/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,35 @@

use function Safe\json_decode;

/**
* Reads composer.json and exposes metadata for license generation.
*
* This class parses a composer.json file via SplFileObject and provides
* methods to extract license information, package name, authors, vendor,
* and the current year for copyright notices.
*/
final readonly class Reader implements ReaderInterface
{
private array $data;

/**
* Creates a new Reader instance.
*
* @param SplFileObject $source The source file to read from, typically composer.json
*
* @throws JsonException if the JSON content is invalid
*/
public function __construct(SplFileObject $source)
{
$this->data = $this->readData($source);
}

/**
* @param SplFileObject $source The source file to read from, typically composer.json
* Reads and parses the JSON content from the source file.
*
* @param SplFileObject $source The source file to read from
*
* @return array
* @return array The parsed JSON data as an associative array
*
* @throws JsonException if the JSON is invalid
*/
Expand All @@ -50,7 +63,13 @@ private function readData(SplFileObject $source): array
}

/**
* @return string|null
* Retrieves the license identifier from composer.json.
*
* If the license is a single string, returns it directly.
* If it's an array with one element, extracts that element.
* Returns null if no license is set or if multiple licenses are specified.
*
* @return string|null the license string, or null if not set or unsupported
*/
public function getLicense(): ?string
{
Expand All @@ -64,15 +83,22 @@ public function getLicense(): ?string
}

/**
* @return string
* Retrieves the package name from composer.json.
*
* @return string the full package name (vendor/package), or empty string if not set
*/
public function getPackageName(): string
{
return $this->data['name'] ?? '';
}

/**
* @return array
* Retrieves the list of authors from composer.json.
*
* Each author is normalized to include name, email, homepage, and role fields.
* Returns an empty array if no authors are defined.
*
* @return array<int, array{name: string, email: string, homepage: string, role: string}>
*/
public function getAuthors(): array
{
Expand All @@ -94,7 +120,12 @@ public function getAuthors(): array
}

/**
* @return string|null
* Extracts the vendor name from the package name.
*
* The package name is expected in vendor/package format.
* Returns null if no package name is set or if the package has no vendor prefix.
*
* @return string|null the vendor name, or null if package has no vendor prefix
*/
public function getVendor(): ?string
{
Expand All @@ -114,17 +145,24 @@ public function getVendor(): ?string
}

/**
* @return int
* Returns the current year for copyright notices.
*
* @return int the current year as an integer
*/
public function getYear(): int
{
return (int) date('Y');
}

/**
* @param array $license
* Extracts a single license from an array of licenses.
*
* Returns the first license if exactly one element exists.
* Returns null if the array is empty or contains multiple licenses.
*
* @param array<string> $license The license array to extract from
*
* @return string|null
* @return string|null a single license string, or null if extraction is not possible
*/
private function extractLicense(array $license): ?string
{
Expand Down
24 changes: 20 additions & 4 deletions src/License/ReaderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,46 @@

namespace FastForward\DevTools\License;

/**
* Reads and exposes metadata from composer.json for license generation.
*
* This interface provides access to license information, package name,
* authors, vendor, and year data extracted from a project's composer.json.
*/
interface ReaderInterface
{
/**
* @return string|null
* Retrieves the license identifier from composer.json.
*
* @return string|null the license string, or null if not set or unsupported
*/
public function getLicense(): ?string;

/**
* @return string
* Retrieves the package name from composer.json.
*
* @return string the full package name (vendor/package)
*/
public function getPackageName(): string;

/**
* Retrieves the list of authors from composer.json.
*
* @return array<int, array{name: string, email: string, homepage: string, role: string}>
*/
public function getAuthors(): array;

/**
* @return string|null
* Extracts the vendor name from the package name.
*
* @return string|null the vendor name, or null if package has no vendor prefix
*/
public function getVendor(): ?string;

/**
* @return int
* Returns the current year for copyright notices.
*
* @return int the current year as an integer
*/
public function getYear(): int;
}
26 changes: 20 additions & 6 deletions src/License/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

namespace FastForward\DevTools\License;

/**
* Resolves license identifiers to their corresponding template filenames.
*
* This class maintains a mapping of supported open-source licenses to their
* template files and provides methods to check support and resolve licenses.
*/
final class Resolver implements ResolverInterface
{
private const array SUPPORTED_LICENSES = [
Expand All @@ -38,19 +44,25 @@ final class Resolver implements ResolverInterface
];

/**
* @param string $license
* Checks whether the given license identifier is supported.
*
* The check is case-insensitive and handles common license variants.
*
* @return bool
* @param string $license The license identifier to check
*
* @return bool True if the license is supported, false otherwise
*/
public function isSupported(string $license): bool
{
return isset(self::SUPPORTED_LICENSES[$this->normalize($license)]);
}

/**
* @param string $license
* Resolves a license identifier to its template filename.
*
* @param string $license The license identifier to resolve
*
* @return string|null
* @return string|null The template filename if supported, or null if not
*/
public function resolve(string $license): ?string
{
Expand All @@ -64,9 +76,11 @@ public function resolve(string $license): ?string
}

/**
* @param string $license
* Normalizes the license identifier for comparison.
*
* @param string $license The license identifier to normalize
*
* @return string
* @return string The normalized license string
*/
private function normalize(string $license): string
{
Expand Down
Loading
Loading