SierraTecnologia common support helpers, contracts, and traits required by various SierraTecnologia packages. Validator functionality, and basic controller included out-of-the-box.

0.4.3 2021-07-17 07:00 UTC

README

SierraTecnologia common Muleta helpers, contracts, and traits required by various SierraTecnologia packages. Validator functionality, and basic controller included out-of-the-box.

Packagist Scrutinizer Code Quality Travis StyleCI License

Nota: Este pacote é uma biblioteca de suporte para outros pacotes SierraTecnologia, que pode não ser útil por si só, mas contém funcionalidades genéricas complementares e pode não respeitar SemVer em releases iniciais.

📚 Índice

🎯 Introdução

O que é Muleta

Muleta é a biblioteca fundacional do ecossistema SierraTecnologia - um conjunto abrangente de contratos, traits, helpers e utilitários que servem como base para todos os pacotes Laravel da SierraTecnologia. O nome "Muleta" (muleta em português) reflete perfeitamente seu propósito: fornecer suporte essencial e estrutural para o desenvolvimento ágil e padronizado de aplicações empresariais.

Objetivo e Filosofia do Projeto

A filosofia do Muleta é baseada em três pilares fundamentais:

  1. Reutilização: Componentes compartilhados que eliminam duplicação de código entre pacotes
  2. Padronização: Contratos e padrões arquiteturais consistentes em todo o ecossistema
  3. Produtividade: Helpers e traits que aceleram o desenvolvimento de funcionalidades comuns

Benefícios de Uso

  • Redução de Código Boilerplate: Traits composíveis para funcionalidades comuns (tags, slugs, imagens, traduções)
  • Validação Estendida: Validadores customizados prontos para uso (unique_with, file, video)
  • Padrões Arquiteturais: Implementação completa de Repository Pattern, API Controllers e Resources
  • Análise de Banco de Dados: Módulo Analysator para inspeção profunda de estruturas de BD
  • Helpers Globais: Mais de 30 funções auxiliares disponíveis globalmente
  • Contratos Bem Definidos: Interfaces para garantir consistência (Arrayable, Outputable, Sortable)

Integração com o Ecossistema SierraTecnologia

Muleta é a camada de fundação que suporta pacotes como:

  • Transmissor: Sistema de comunicação e notificações
  • Sanitizer: Limpeza e validação de dados
  • MediaManager: Gerenciamento de mídias e arquivos
  • SafeMaps: Sistemas de mapas e geolocalização
  • Architect: Ferramentas de arquitetura de software

Todos compartilham os mesmos traits, contratos e padrões definidos pelo Muleta, garantindo coesão arquitetural.

📦 Instalação

Requisitos Mínimos

  • PHP: 8.2 ou superior
  • Laravel: 10.x ou 11.x
  • Composer: 2.x

Instalação via Composer

composer require sierratecnologia/muleta

Dependências Sugeridas

Para funcionalidade completa, considere instalar:

composer require sierratecnologia/crypto
composer require laravel/helpers
composer require vanderlee/php-sentence
composer require ricardosierra/changelog

Publicação de Configurações

O Muleta é projetado para funcionar sem publicação de assets, mas se necessário:

php artisan vendor:publish --provider="Muleta\Library\ServiceProvider"

Registro de Service Provider

O Service Provider é auto-descoberto no Laravel 5.5+. Para versões anteriores, adicione manualmente em config/app.php:

'providers' => [
    // ...
    Muleta\Library\ServiceProvider::class,
],

🏗️ Arquitetura e Estrutura Interna

Estrutura de Diretórios

muleta/
├── src/                          # Código fonte principal
│   ├── Contracts/                # Interfaces e contratos
│   ├── Models/                   # Base models e traits
│   ├── Services/                 # Camada de serviços
│   ├── Traits/                   # 33+ traits reutilizáveis
│   │   ├── Controllers/          # Traits para controllers
│   │   ├── Models/               # Traits para models
│   │   ├── Coder/                # Traits de geração de código
│   │   └── ...
│   ├── Modules/                  # Módulos funcionais
│   │   ├── Analysator/           # Análise de banco de dados
│   │   ├── Features/             # Features específicas
│   │   └── Recursos/             # Recursos compartilhados
│   └── Library/                  # Biblioteca core
│       ├── Laravel/              # Integrações Laravel
│       └── ServiceProvider.php   # Service Provider principal
├── arsenal/                      # Biblioteca estendida (legacy + moderno)
│   ├── utils/                    # Utilitários diversos
│   ├── helper/                   # Classes helper
│   ├── logic/                    # Lógica de negócio
│   ├── objects/                  # Objetos de domínio
│   └── recursos/                 # Recursos especializados
├── utils/                        # Funções e classes utilitárias
│   ├── funcs/                    # Funções globais
│   └── classes/                  # Classes utilitárias
├── config/                       # Arquivos de configuração
├── database/                     # Migrations e seeders
└── tests/                        # Testes automatizados

Namespaces Principais

Namespace Propósito Exemplo
Muleta\ Core do pacote Muleta\Models\BaseModel
SiUtils\ Utilitários gerais SiUtils\StringHelper
SiHelper\ Classes auxiliares SiHelper\ArrayHelper
SiLogic\ Lógica de negócio SiLogic\ValidationLogic
SiObjects\ Objetos de domínio SiObjects\ValueObject
SiWeapons\ Recursos especializados SiWeapons\AdvancedSearcher

Padrões Arquiteturais Adotados

1. Repository Pattern

// Interface
interface RepositoryInterface {
    public function findById($id);
    public function create(array $data);
    public function update($id, array $data);
    public function delete($id);
}

// Implementação base
abstract class Repository implements RepositoryInterface {
    // Implementação genérica de CRUD
}

2. Trait Composition Pattern

Ao invés de herança profunda, Muleta favorece composição via traits:

class Article extends Model {
    use HasSlug;        // Gera slugs automaticamente
    use HasTags;        // Adiciona sistema de tags
    use HasImages;      // Gerencia imagens associadas
    use HasTranslations; // Suporte multi-idioma
    use ValidatingTrait; // Validação automática
}

3. Service Layer Pattern

// Lógica de negócio isolada em services
class ArticleService {
    public function publish(Article $article) {
        // Lógica complexa de publicação
    }
}

4. API Resource Pattern

class ArticleResource extends JsonResource {
    public function toArray($request) {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug,
            'tags' => TagResource::collection($this->tags),
        ];
    }
}

Convenções da SierraTecnologia

  1. Nomenclatura em Português: Classes e métodos principais em PT-BR
  2. PSR-12 Compliance: Padrão de código PSR-12
  3. Type Hints Rigorosos: PHP 8.2+ com tipos estritos
  4. Documentação Inline: PHPDoc completo em todos os métodos públicos
  5. Testes Unitários: Cobertura de código > 70%

🔧 Principais Componentes

1. Contracts (Interfaces)

Arrayable

Garante que objetos possam ser convertidos em arrays:

interface Arrayable {
    public function toArray(): array;
}

Uso: Models, DTOs, Value Objects que precisam serialização.

Outputable

Define objetos que podem gerar saída formatada:

interface Outputable {
    public function output(): string;
}

Uso: Reports, exportações, geradores de arquivos.

Sortable

Implementa ordenação customizada:

interface Sortable {
    public function sortBy(string $field, string $direction = 'asc');
}

2. Traits Essenciais

HasSlug

Gera slugs URL-friendly automaticamente:

use Muleta\Traits\Models\HasSlug;

class Post extends Model {
    use HasSlug;

    protected $slugSourceField = 'title'; // Campo fonte para slug
}

// Uso
$post = Post::create(['title' => 'Meu Artigo Incrível']);
echo $post->slug; // "meu-artigo-incrivel"

Localização: src/Traits/Models/HasSlug.php

HasTags

Sistema completo de tags com relacionamento polimórfico:

use Muleta\Traits\Models\HasTags;

class Article extends Model {
    use HasTags;
}

// Uso
$article->attachTag('Laravel');
$article->attachTags(['PHP', 'Backend', 'API']);
$tags = $article->tags; // Collection de tags
$article->detachTag('Laravel');

Localização: src/Traits/Models/HasTags.php

HasImages

Gerenciamento de múltiplas imagens associadas:

use Muleta\Traits\Models\HasImages;

class Product extends Model {
    use HasImages;
}

// Uso
$product->addImage($imagePath, 'featured');
$product->addImages($imagePaths, 'gallery');
$featuredImage = $product->getImage('featured');
$allImages = $product->images;

Localização: src/Traits/Models/HasImages.php

HasTranslations

Suporte multi-idioma nativo:

use Muleta\Traits\Models\HasTranslations;

class Page extends Model {
    use HasTranslations;

    protected $translatable = ['title', 'content'];
}

// Uso
$page->setTranslation('title', 'pt_BR', 'Sobre Nós');
$page->setTranslation('title', 'en_US', 'About Us');
echo $page->getTranslation('title', 'pt_BR'); // "Sobre Nós"

Localização: src/Traits/Models/HasTranslations.php

ValidatingTrait

Validação automática no save/update:

use Muleta\Traits\Models\ValidatingTrait;

class User extends Model {
    use ValidatingTrait;

    protected $validationRules = [
        'name' => 'required|min:3',
        'email' => 'required|email|unique:users',
    ];
}

// Validação automática
$user = new User(['name' => 'Ab']); // Exception: nome muito curto
$user->save(); // Valida automaticamente antes de salvar

Localização: src/Traits/Models/ValidatingTrait.php

GetSetTrait

Geração automática de getters/setters via anotações:

use Muleta\Traits\Coder\GetSetTrait;

class MyClass {
    use GetSetTrait;

    /**
     * @var string
     * @getter true
     * @setter false
     * @serializable true
     */
    protected $modelClass;
}

// Uso
$obj->getModelClass(); // Auto-gerado

Localização: src/Traits/Coder/GetSetTrait.php

3. Validadores Customizados

O Service Provider registra validadores estendidos:

unique_with

Valida unicidade combinando múltiplos campos:

// Garante que combinação email + tenant_id seja única
'email' => 'unique_with:users,tenant_id'

file

Validação avançada de arquivos:

'document' => 'file|max:10240|mimes:pdf,doc,docx'

video

Validação específica para vídeos:

'video' => 'video|max:51200|mimes:mp4,avi,mov'

Localização: src/Library/Laravel/Validator.php

4. Helpers Globais

intend()

Retorna redirect response com suporte a múltiplos métodos:

return intend([
    'route' => 'dashboard',
    'with' => ['success' => 'Salvo com sucesso!'],
    'withErrors' => ['field' => 'Erro de validação'],
]);

// Suporta: back, route, url, with, withErrors, withInput

Localização: utils/funcs/helpers.php

mimetypes()

Retorna array de mimetypes válidos:

$mimetypes = mimetypes();
// ['image/jpeg', 'image/png', 'application/pdf', ...]

timezones()

Lista de timezones formatados:

$timezones = timezones();
// ['America/Sao_Paulo' => 'São Paulo (UTC-3)', ...]

lower_case() / upper_case()

Conversão de caso com suporte UTF-8:

$lower = lower_case('TEXTO EM MAIÚSCULA'); // "texto em maiúscula"
$upper = upper_case('texto em minúscula'); // "TEXTO EM MINÚSCULA"

5. Módulo Analysator

Sistema sofisticado de análise de banco de dados:

use Muleta\Modules\Analysator\DatabaseAnalyzer;

$analyzer = new DatabaseAnalyzer();

// Analisa estrutura completa
$schema = $analyzer->analyzeDatabase();

// Detecta anomalias
$issues = $analyzer->findIndexMissingIssues();
$duplicates = $analyzer->findDuplicateIndexes();

// Otimizações sugeridas
$suggestions = $analyzer->suggestOptimizations();

Funcionalidades:

  • Análise de índices e performance
  • Detecção de relacionamentos implícitos
  • Sugestões de otimização
  • Mapeamento de dependências entre tabelas

Localização: src/Modules/Analysator/

💡 Uso Prático

Exemplo Completo: Sistema de Blog

<?php

use Illuminate\Database\Eloquent\Model;
use Muleta\Traits\Models\HasSlug;
use Muleta\Traits\Models\HasTags;
use Muleta\Traits\Models\HasImages;
use Muleta\Traits\Models\ValidatingTrait;

class Article extends Model
{
    use HasSlug, HasTags, HasImages, ValidatingTrait;

    protected $fillable = ['title', 'content', 'author_id'];

    protected $slugSourceField = 'title';

    protected $validationRules = [
        'title' => 'required|min:5|max:200',
        'content' => 'required|min:50',
        'author_id' => 'required|exists:users,id',
    ];

    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }
}

Controller usando Repository Pattern:

<?php

use Muleta\Modules\Recursos\ArticleRepository;
use App\Http\Resources\ArticleResource;

class ArticleController extends Controller
{
    protected $repository;

    public function __construct(ArticleRepository $repository)
    {
        $this->repository = $repository;
    }

    public function index()
    {
        $articles = $this->repository
            ->with(['author', 'tags', 'images'])
            ->paginate(15);

        return ArticleResource::collection($articles);
    }

    public function store(ArticleRequest $request)
    {
        $article = $this->repository->create($request->validated());

        // Traits em ação
        $article->attachTags($request->tags);
        $article->addImages($request->images, 'gallery');

        return new ArticleResource($article);
    }
}

Resource para API:

<?php

use Illuminate\Http\Resources\Json\JsonResource;

class ArticleResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug, // Gerado por HasSlug
            'content' => $this->content,
            'author' => [
                'id' => $this->author->id,
                'name' => $this->author->name,
            ],
            'tags' => $this->tags->pluck('name'), // HasTags
            'featured_image' => $this->getImage('featured'), // HasImages
            'created_at' => $this->created_at->toIso8601String(),
        ];
    }
}

Boas Práticas

  1. Use Traits Moderadamente: Não adicione todos os traits a um model, apenas os necessários
  2. Valide Dados: Sempre use ValidatingTrait ou Form Requests
  3. Repository para Lógica Complexa: Mantenha controllers magros
  4. Type Hints: Aproveite PHP 8.2+ para type safety
  5. Testes: Escreva testes para lógica customizada que usa Muleta

🔗 Integração com o Ecossistema SierraTecnologia

Arquitetura do Ecossistema

┌─────────────────────────────────────────────────────┐
│                   Aplicação Final                    │
│            (CMS, E-commerce, ERP, etc.)              │
└─────────────────┬───────────────────────────────────┘
                  │
    ┌─────────────┼─────────────┬──────────────┐
    │             │             │              │
┌───▼───┐   ┌────▼────┐   ┌────▼────┐   ┌────▼────┐
│Archi- │   │Transmis-│   │Media    │   │Safe     │
│tect   │   │sor      │   │Manager  │   │Maps     │
└───┬───┘   └────┬────┘   └────┬────┘   └────┬────┘
    │            │             │              │
    └────────────┴─────────────┴──────────────┘
                     │
              ┌──────▼───────┐
              │    MULETA    │  ← Biblioteca de Fundação
              │  (Foundation)│
              └──────────────┘

Padrões de Versionamento

  • Semantic Versioning: Major.Minor.Patch (com exceções em early releases)
  • Branch Strategy:
    • main: Produção estável
    • develop: Desenvolvimento ativo
    • feature/*: Novas funcionalidades
    • hotfix/*: Correções urgentes

Testes e CI/CD

O Muleta implementa CI/CD rigoroso:

# .github/workflows/code-quality.yml
- PHPStan (nível 8)
- PHPCS (PSR-12)
- Psalm (análise estática)
- PHPUnit (testes unitários)

Todos os pacotes SierraTecnologia que dependem de Muleta devem:

  1. Usar as mesmas ferramentas de qualidade
  2. Seguir os mesmos padrões de código
  3. Implementar testes com cobertura mínima de 70%

Como Diferentes Equipes Usam Muleta

Equipe de Produto (CMS)

// Usa traits para funcionalidades comuns
class Page extends Model {
    use HasSlug, HasTranslations, HasImages;
}

Equipe de Infraestrutura (DevOps)

// Usa Analysator para otimização de BD
$analyzer = new DatabaseAnalyzer();
$report = $analyzer->generateOptimizationReport();

Equipe de API (Backend)

// Usa Repository Pattern e Resources
class ApiController extends Controller {
    use ApiResponseTrait; // Do Muleta
}

🎨 Extensão e Customização

Como Estender Muleta

1. Criando Traits Customizados

<?php

namespace App\Traits;

trait HasApprovalWorkflow
{
    public function approve()
    {
        $this->update(['status' => 'approved', 'approved_at' => now()]);
        event(new ItemApproved($this));
    }

    public function reject(string $reason)
    {
        $this->update(['status' => 'rejected', 'rejection_reason' => $reason]);
    }

    public function scopeApproved($query)
    {
        return $query->where('status', 'approved');
    }
}

2. Extendendo Repositories

<?php

namespace App\Repositories;

use Muleta\Modules\Recursos\Repository;

class CustomArticleRepository extends Repository
{
    public function findPublished()
    {
        return $this->model
            ->where('published_at', '<=', now())
            ->orderBy('published_at', 'desc')
            ->get();
    }

    public function findByTag(string $tag)
    {
        return $this->model
            ->whereHas('tags', function ($query) use ($tag) {
                $query->where('name', $tag);
            })
            ->get();
    }
}

3. Criando Validadores Customizados

<?php

namespace App\Validators;

use Illuminate\Support\ServiceProvider;

class CustomValidatorServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $validator = $this->app->make('validator');

        // CPF brasileiro
        $validator->extend('cpf', function ($attribute, $value) {
            return $this->validateCPF($value);
        });

        // CNPJ brasileiro
        $validator->extend('cnpj', function ($attribute, $value) {
            return $this->validateCNPJ($value);
        });
    }

    protected function validateCPF($cpf): bool
    {
        // Lógica de validação de CPF
        // ...
    }
}

Boas Práticas para Manter Compatibilidade

  1. Não Modifique Diretamente: Nunca altere código do Muleta, sempre estenda
  2. Use Composition Over Inheritance: Prefira traits a herança de classes
  3. Documente Extensões: Mantenha documentação clara de customizações
  4. Testes de Integração: Teste compatibilidade após updates do Muleta
  5. Namespace Separado: Mantenha extensões em namespace próprio (App\Extensions\Muleta)

Como Adicionar Novos Helpers

<?php
// app/Helpers/custom_helpers.php

if (!function_exists('format_currency_brl')) {
    function format_currency_brl(float $value): string
    {
        return 'R$ ' . number_format($value, 2, ',', '.');
    }
}

if (!function_exists('sanitize_phone_br')) {
    function sanitize_phone_br(string $phone): string
    {
        return preg_replace('/[^0-9]/', '', $phone);
    }
}

Registre em composer.json:

{
    "autoload": {
        "files": [
            "app/Helpers/custom_helpers.php"
        ]
    }
}

📊 Exemplos Reais

Caso 1: E-commerce com Múltiplos Idiomas

Desafio: Sistema de e-commerce que precisa suportar PT-BR, EN e ES com produtos taggeados e imagens múltiplas.

Solução com Muleta:

class Product extends Model
{
    use HasSlug, HasTags, HasImages, HasTranslations, ValidatingTrait;

    protected $translatable = ['name', 'description', 'meta_title', 'meta_description'];

    protected $validationRules = [
        'sku' => 'required|unique:products',
        'price' => 'required|numeric|min:0',
        'stock' => 'required|integer|min:0',
    ];
}

// Controller
class ProductController extends Controller
{
    public function store(ProductRequest $request)
    {
        $product = Product::create($request->only(['sku', 'price', 'stock']));

        // Traduções
        foreach (['pt_BR', 'en_US', 'es_ES'] as $locale) {
            $product->setTranslation('name', $locale, $request->input("name_{$locale}"));
            $product->setTranslation('description', $locale, $request->input("desc_{$locale}"));
        }

        // Tags
        $product->attachTags($request->tags);

        // Imagens
        $product->addImage($request->file('featured'), 'featured');
        $product->addImages($request->file('gallery'), 'gallery');

        return redirect()->route('products.show', $product->slug);
    }
}

Resultado: Implementação completa em < 50 linhas vs 300+ sem Muleta.

Caso 2: Sistema de Análise de Performance de BD

Desafio: Identificar gargalos em banco de dados legado de ERP com 200+ tabelas.

Solução com Analysator:

use Muleta\Modules\Analysator\DatabaseAnalyzer;

class DatabaseOptimizationCommand extends Command
{
    protected $signature = 'db:analyze-performance';

    public function handle()
    {
        $analyzer = new DatabaseAnalyzer();

        $this->info('Analisando banco de dados...');

        // Índices faltando
        $missingIndexes = $analyzer->findIndexMissingIssues();
        $this->warn("Índices faltando: " . count($missingIndexes));
        foreach ($missingIndexes as $issue) {
            $this->line("  - {$issue['table']}.{$issue['column']}");
        }

        // Índices duplicados
        $duplicates = $analyzer->findDuplicateIndexes();
        $this->warn("Índices duplicados: " . count($duplicates));

        // Queries N+1 potenciais
        $nPlusOne = $analyzer->detectPotentialNPlusOne();
        $this->error("Potenciais N+1: " . count($nPlusOne));

        // Gera relatório
        $report = $analyzer->generateOptimizationReport();
        file_put_contents('storage/db-optimization-report.json', json_encode($report, JSON_PRETTY_PRINT));

        $this->info('Relatório salvo em storage/db-optimization-report.json');
    }
}

Resultado: Identificação automática de 47 índices faltantes, 12 duplicados, reduzindo tempo de queries em 65%.

Caso 3: API RESTful Multi-tenant

Desafio: API para SaaS multi-tenant com isolamento de dados e validação rigorosa.

Antes (sem Muleta):

  • 15+ arquivos duplicados entre controllers
  • Validação manual em cada endpoint
  • Sem padronização de responses

Depois (com Muleta):

<?php

use Muleta\Traits\Controllers\ApiResponseTrait;
use App\Repositories\TenantAwareRepository;

class BaseApiController extends Controller
{
    use ApiResponseTrait;

    protected $repository;

    public function index()
    {
        $items = $this->repository
            ->forCurrentTenant()
            ->paginate(request('per_page', 15));

        return $this->successResponse($items);
    }

    public function store(Request $request)
    {
        try {
            $item = $this->repository->create($request->validated());
            return $this->createdResponse($item);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage(), 422);
        }
    }
}

Ganhos:

  • Código reduzido: 70% menos código boilerplate
  • Padronização: Todas as respostas seguem mesmo formato
  • Manutenção: Mudanças centralizadas no Muleta
  • Testes: Cobertura aumentou de 40% para 85%

🧪 Testes e Qualidade de Código

Ferramentas de Qualidade Configuradas

O Muleta utiliza as melhores ferramentas de qualidade de código PHP:

PHPStan (Nível 8)

Análise estática mais rigorosa:

vendor/bin/phpstan analyse src --memory-limit=2G

Configuração: phpstan.neon

PHPCS (PSR-12)

Verificação de padrões de código:

vendor/bin/phpcs --standard=PSR12 src

Configuração: phpcs.xml

Psalm

Análise de tipos e bugs:

vendor/bin/psalm --output-format=github

Configuração: psalm.xml

PHPUnit

Testes unitários e de integração:

vendor/bin/phpunit --coverage-html coverage

Configuração: phpunit.xml

CI/CD com GitHub Actions

Todos os PRs e commits passam por:

  1. Tests (PHP 8.2 e 8.3, Laravel 10 e 11)
  2. PHPStan (nível 8, sem erros)
  3. PHPCS (PSR-12 compliance)
  4. Psalm (análise de tipos)

Workflows: .github/workflows/run-tests.yml e .github/workflows/code-quality.yml

📄 Changelog

Consulte o CHANGELOG.md para histórico completo de versões e mudanças.

Support

The following support channels are available at your fingertips:

Contributing & Protocols

Thank you for considering contributing to this project! The contribution guide can be found in CONTRIBUTING.md.

Bug reports, feature requests, and pull requests are very welcome.

Security Vulnerabilities

If you discover a security vulnerability within this project, please send an e-mail to help@sierratecnologia.com. All security vulnerabilities will be promptly addressed.

About SierraTecnologia

SierraTecnologia is a software solutions startup, specialized in integrated enterprise solutions for SMEs established in Alexandria, Egypt since June 2016. We believe that our drive The Value, The Reach, and The Impact is what differentiates us and unleash the endless possibilities of our philosophy through the power of software. We like to call it Innovation At The Speed Of Life. That's how we do our share of advancing humanity.

License

This software is released under The MIT License (MIT).

(c) 2016-2020 SierraTecnologia LLC, Some rights reserved.