<?php

use PHPUnit\Framework\TestCase;

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

/**
 * Tests for the Singly Linked Lists class
 */
class SinglyLinkedListTest extends TestCase
{
    /**
     * Create a SinglyLinkedList node
     */
    private function createNode(string $string): SinglyLinkedList
    {
        $node = new SinglyLinkedList($string[0]);

        for ($i = 1; $i < strlen($string); $i++) {
            $node->append($string[$i]);
        }

        return $node;
    }

    /**
     * Provider of SinglyLinkedList nodes
     */
    public function provideNodes()
    {
        return [
            'IsPalindrome' => [
                'node' => $this->createNode('hannah'),
                'expected' => true
            ],
            'IsPalindrome2' => [
                'node' => $this->createNode('hanah'),
                'expected' => true
            ],
            'IsNotPalindrome' => [
                'node' => $this->createNode('hanbah'),
                'expected' => false
            ],
            'IsNotPalindrome2' => [
                'node' => $this->createNode('han1h'),
                'expected' => false
            ],
        ];
    }

    /**
     * Test the if a SinglyLinkedList is a palindrome
     *
     * @dataProvider provideNodes
     */
    public function testIsPalindrome($node, $expected): void
    {
        $this->assertEquals($expected, $this->isPalindrome($node));
    }

    /**
     * Supporting method for testing if a linked list is palindrome
     */
    private function isPalindrome(SinglyLinkedList $node): bool
    {
        $length = $this->length($node);
        $pairs = 0;
        $curr = $node;

        while ($curr instanceof SinglyLinkedList) {
            if ($this->hasPair($curr->next, $curr->data)) {
                $pairs++;
            }

            $curr = $curr->next;
        }

        return $pairs == floor($length / 2);
    }

    /**
     * Supporting method for testing if a linked list is palindrome
     */
    private function hasPair($node, string $data): bool
    {
        if (! $node instanceof SinglyLinkedList) {
            return false;
        }

        $curr = $node;

        while ($curr instanceof SinglyLinkedList) {
            if ($curr->data == $data) {
                return true;
            }

            $curr = $curr->next;
        }

        return false;
    }

    /**
     * Supporting method for testing if a linked list is palindrome
     */
    private function length(SinglyLinkedList $node): int
    {
        $curr = $node;
        $counter = 0;

        while ($curr instanceof SinglyLinkedList) {
            $counter++;
            $curr = $curr->next;
        }

        return $counter;
    }

    /**
     * Test SinglyLinkedList's delete functionality
     */
    public function testDelete(): void
    {
        $this->assertEquals(
            $this->createNode('hanah'), // expected node
            $this->createNode('hannah')->delete('n'), // actual node
        );

        $this->assertNotEquals(
            $this->createNode('hanah'), // expected node
            $this->createNode('hannah')->delete('h'), // actual node
        );
    }
}


<?php

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

use PHPUnit\Framework\TestCase;

class BreadthFirstSearchTest extends TestCase
{
    public function testBreadthFirstSearch()
    {
        $graph = array(
            "A" => ["B", "C", "D"],
            "B" => ["A", "D", "E"],
            "C" => ["A", "F"],
            "D" => ["B", "D"],
            "E" => ["B", "F"],
            "F" => ["C", "E", "G"],
            "G" => ["F"],
        );
        $ans = bfs($graph, "A", "F");
        $this->assertTrue($ans);
    }
    public function testBreadthFirstSearch2()
    {
        $graph = array(
            [1, 2],
            [2],
            [0, 3],
            [3],
        );

        $ans2 = bfs($graph, 3, 0);
        $this->assertFalse($ans2);
    }
    public function testBreadthFirstSearch3()
    {
        $graph = array(
            [2, 3, 1],
            [0],
            [0, 4],
            [0],
            [2],
        );
        $ans = bfs($graph, 0, 3);
        $this->assertTrue($ans);
    }
}


<?php

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

use PHPUnit\Framework\TestCase;

class DepthFirstSearchTest extends TestCase
{
    public function testDepthFirstSearch()
    {
        $graph = array(
            "A" => ["B", "C", "D"],
            "B" => ["A", "D", "E"],
            "C" => ["A", "F"],
            "D" => ["B", "D"],
            "E" => ["B", "F"],
            "F" => ["C", "E", "G"],
            "G" => ["F"],
        );
        $visited = dfs($graph, "A");
        $this->assertEquals(["A", "B", "D", "E", "F", "C", "G"], $visited);
    }
    public function testDepthFirstSearch2()
    {
        $graph = array(
            [1, 2],
            [2],
            [0, 3],
            [3],
        );

        $visited = dfs($graph, 2);
        $this->assertEquals([2, 0, 1, 3], $visited);
    }
    public function testDepthFirstSearch3()
    {
        $graph = array(
            [2, 3, 1],
            [0],
            [0, 4],
            [0],
            [2],
        );
        $visited = dfs($graph, 0);
        $this->assertEquals([0, 2, 4, 3, 1], $visited);
    }
}


<?php

use function PHPUnit\Framework\assertEquals;
use function PHPUnit\Framework\assertFalse;
use function PHPUnit\Framework\assertTrue;
use PHPUnit\Framework\TestCase;

require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../Maths/AbsoluteMax.php';
require_once __DIR__ . '/../../Maths/ArmstrongNumber.php';
require_once __DIR__ . '/../../Maths/AbsoluteMin.php';
require_once __DIR__ . '/../../Maths/CheckPalindrome.php';
require_once __DIR__ . '/../../Maths/CheckPrime.php';
require_once __DIR__ . '/../../Maths/Factorial.php';
require_once __DIR__ . '/../../Maths/FastExponentiation.php';
require_once __DIR__ . '/../../Maths/Fibonacci.php';
require_once __DIR__ . '/../../Maths/Fibonacci2.php';
require_once __DIR__ . '/../../Maths/NeonNumber.php';
require_once __DIR__ . '/../../Maths/PerfectSquare.php';
require_once __DIR__ . '/../../Maths/Mean.php';
require_once __DIR__ . '/../../Maths/Median.php';
require_once __DIR__ . '/../../Maths/Mode.php';

class MathsTest extends TestCase
{
    public function testFactorial()
    {
        assertEquals(1, factorial(1));
        assertEquals(120, factorial(5));
        assertEquals(1, factorial(0));
        $this->expectException(\Exception::class);
        factorial(-25);
    }

    public function testIsNumberArmstrong()
    {
        assertTrue(isNumberArmstrong(153));
        assertFalse(isNumberArmstrong(123));
        assertTrue(isNumberArmstrong(370));
        assertFalse(isNumberArmstrong(2468));
    }

    public function testIsNumberPalindromic()
    {
        assertTrue(isNumberPalindromic(121));
        assertFalse(isNumberPalindromic(123));
        assertTrue(isNumberPalindromic(123321));
        assertFalse(isNumberPalindromic(2468));
    }

    public function testIsPrime()
    {
        assertTrue(isPrime(73));
        assertFalse(isPrime(21));
        assertFalse(isPrime(1));
        assertTrue(isPrime(997));
    }

    public function testAbsoluteMax()
    {
        assertEquals(50, absolute_max(12, 20, 35, 50, 50, 23));
        assertEquals(13, absolute_max(13));
        assertEquals(54, absolute_max(12, 13, 54, 22));
        $this->expectException(\Exception::class);
        absolute_max();
    }

    public function testAbsoluteMin()
    {
        assertEquals(12, absolute_min(12, 20, 35, 50, 50, 23));
        assertEquals(13, absolute_min(13));
        assertEquals(12, absolute_min(12, 13, 54, 22));
        $this->expectException(\Exception::class);
        absolute_min();
    }

    public function testPerfectSquare()
    {
        assertTrue(isPerfectSquare(25));
        assertFalse(isPerfectSquare(43));
        assertFalse(isPerfectSquare(2));
        assertTrue(isPerfectSquare(225));
    }

    public function testFastExponentiation()
    {
        assertEquals(fastExponentiation(10, 0), 1);
        assertEquals(fastExponentiation(10, 1), 10);
        assertEquals(fastExponentiation(10, 2), 100);
        assertEquals(fastExponentiation(10, 3), 1000);
        assertEquals(fastExponentiation(20, 5), 3200000);
    }

    public function testFibonacciSeries()
    {
        assertEquals([0, 1, 1, 2, 3], fibonacciRecursive(5));
        assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], fibonacciRecursive(10));

        assertEquals([0, 1, 1, 2, 3], fibonacciWithBinetFormula(5));
        assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], fibonacciWithBinetFormula(10));
    }

    public function testNeonNumber()
    {
        assertTrue(isNumberNeon(1));
        assertFalse(isNumberNeon(43));
        assertFalse(isNumberNeon(123));
        assertTrue(isNumberNeon(9));
    }

    public function testFibonacciGenerator()
    {
        assertEquals([0, 1, 1, 2, 3], iterator_to_array(loop(5, fib())));
        assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], iterator_to_array(loop(10, fib())));

        assertEquals([0, 1, 1, 2, 3], iterator_to_array(loop(5, fib())));
        assertEquals([0, 1, 1, 2, 3, 5, 8, 13, 21, 34], iterator_to_array(loop(10, fib())));
    }

    public function testMean()
    {
        assertEquals(
            (2 + 4 + 6 + 8 + 20 + 50 + 70) / 7,
            mean(2, 4, 6, 8, 20, 50, 70)
        );

        assertEquals(
            (-5 - 7 + 10) / 3,
            mean(-5, -7, 10)
        );

        assertEquals(-1, mean(-1));
    }

    public function testMedian()
    {
        assertEquals(3, median(1, 2, 3, 4, 5));
        assertEquals(4.5, median(1, 2, 3, 4, 5, 6, 7, 8));
        assertEquals(3, median(5, 3, 1, 2, 4));
    }

    public function testMode()
    {
        mode(4,2,7,9,2,9,0,4,2);
        mode(21,72,40,21,0,99,21,99,0,40);
        mode(3,3,3,3);
    }
}


<?php

use PHPUnit\Framework\TestCase;

require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem1.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem2.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem3.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem4.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem5.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem6.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem7.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem8.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem9.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem10.php';
require_once __DIR__ . '/../../Maths/ProjectEuler/Problem11.php';

class ProjectEulerTest extends TestCase
{
    public function testProblem1(): void
    {
        $this->assertSame(233168, problem1a());
        $this->assertSame(233168, problem1b());
    }

    public function testProblem2(): void
    {
        $this->assertSame(4613732, problem2());
    }

    public function testProblem3(): void
    {
        $this->assertSame(6857, problem3());
    }

    public function testProblem4(): void
    {
        $this->assertSame(906609, problem4());
    }

    public function testProblem5(): void
    {
        $this->assertSame(232792560, problem5());
    }

    public function testProblem6(): void
    {
        $this->assertSame(25164150, problem6());
    }

    public function testProblem7(): void
    {
        $this->assertSame(104743, problem7());
    }

    public function testProblem8(): void
    {
        $this->assertSame(23514624000, problem8());
    }

    public function testProblem9(): void
    {
        $this->assertSame(31875000, problem9());
    }

    public function testProblem10(): void
    {
        $this->assertSame(142913828922, problem10());
    }

    public function testProblem11(): void
    {
        $this->assertSame(70600674, problem11());
    }
}


<?php

use function PHPUnit\Framework\assertEquals;
use function PHPUnit\Framework\assertFalse;
use function PHPUnit\Framework\assertTrue;
use PHPUnit\Framework\TestCase;

require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../Searches/BinarySearch.php';
require_once __DIR__ . '/../../Searches/FibonacciSearch.php';
require_once __DIR__ . '/../../Searches/LinearSearch.php';
require_once __DIR__ . '/../../Searches/LowerBound.php';
require_once __DIR__ . '/../../Searches/UpperBound.php';
require_once __DIR__ . '/../../Searches/JumpSearch.php';
require_once __DIR__ . '/../../Searches/ExponentialSearch.php';
require_once __DIR__ . '/../../Searches/TernarySearch.php';
require_once __DIR__ . '/../../Searches/InterpolationSearch.php';

class SearchesTest extends TestCase
{
    public function testBinarySearchIterative()
    {
        $list = [0, 5, 7, 10, 15];
        $target = 0;
        $result = binarySearchIterative($list, $target);
        assertEquals(0, $result);
        $target = 15;
        $result = binarySearchIterative($list, $target);
        assertEquals(4, $result);
        $target = 5;
        $result = binarySearchIterative($list, $target);
        assertEquals(1, $result);
        $target = 6;
        $result = binarySearchIterative($list, $target);
        assertEquals(null, $result);
    }

    public function testBinarySearchByRecursion()
    {
        $list = [0, 5, 7, 10, 15];
        $target = 0;
        $result = binarySearchByRecursion($list, $target, 0, 4);
        assertEquals(0, $result);
        $target = 15;
        $result = binarySearchByRecursion($list, $target, 0, 4);
        assertEquals(4, $result);
        $target = 5;
        $result = binarySearchByRecursion($list, $target, 0, 4);
        assertEquals(1, $result);
        $target = 6;
        $result = binarySearchByRecursion($list, $target, 0, 4);
        assertEquals(null, $result);
    }

    public function testBinarySearchByRecursionWithEmptyList()
    {
        $list = [];
        $target = 0;
        $result = binarySearchByRecursion($list, $target, 0, 0);
        assertEquals(null, $result);
    }

    public function testBinarySearchByRecursionWithOneElementList()
    {
        $list = [0];
        $target = 0;
        $result = binarySearchByRecursion($list, $target, 0, 0);
        assertEquals(0, $result);
        $target = 1;
        $result = binarySearchByRecursion($list, $target, 0, 0);
        assertEquals(null, $result);
    }

    public function testBinarySearchByRecursionWithTwoElementList()
    {
        $list = [0, 1];
        $target = 0;
        $result = binarySearchByRecursion($list, $target, 0, 1);
        assertEquals(0, $result);
        $target = 1;
        $result = binarySearchByRecursion($list, $target, 0, 1);
        assertEquals(1, $result);
        $target = 2;
        $result = binarySearchByRecursion($list, $target, 0, 1);
        assertEquals(null, $result);
    }

    public function testBinarySearchByRecursionWithThreeElementList()
    {
        $list = [0, 1, 2];
        $target = 0;
        $result = binarySearchByRecursion($list, $target, 0, 2);
        assertEquals(0, $result);
        $target = 1;
        $result = binarySearchByRecursion($list, $target, 0, 2);
        assertEquals(1, $result);
        $target = 2;
        $result = binarySearchByRecursion($list, $target, 0, 2);
        assertEquals(2, $result);
        $target = 3;
        $result = binarySearchByRecursion($list, $target, 0, 2);
        assertEquals(null, $result);
    }

    public function testFibonacciSearch()
    {
        $test1 = fibonacciPosition(6);
        assertEquals(8, $test1);

        $test2 = fibonacciPosition(9);
        assertEquals(34, $test2);

        $test3 = fibonacciPosition(60);
        assertEquals(1548008755920, $test3);
    }

    public function testLinearSearch()
    {
        $list = [5, 7, 8, 11, 12, 15, 17, 18, 20];
        $target = 15;
        $result = linearSearch($list, $target);
        assertEquals(6, $result);
    }

    public function testLowerBound()
    {
        $list = [1, 2, 3, 3, 3, 4, 5, 9];
        $target = 3;
        $result = lowerBound($list, $target);
        assertEquals(2, $result);
    }

    public function testUpperBound()
    {
        $list = [1, 2, 3, 3, 3, 4, 5, 9];
        $target = 3;
        $result = upperBound($list, $target);
        assertEquals(5, $result);
    }

    public function testJumpSearch()
    {
        $list = array( 3,5,6,7,9,10,12,20,22,24);
        $target = 12;
        $result = jumpSearch($list, $target);
        assertEquals(6, $result);
    }

    public function testExponentialSearch()
    {
        $list = array(2,3,4,7,28,35,63,98);
        $target = 35;
        $result = exponentialSearch($list, $target);
        assertEquals(5, $result);
    }

    public function testTernarySearchIterative()
    {
        $list = [0, 5, 7, 10, 15];
        $target = 0;
        $result = ternarySearchIterative($list, $target);
        assertEquals(0, $result);
        $target = 15;
        $result = ternarySearchIterative($list, $target);
        assertEquals(4, $result);
        $target = 5;
        $result = ternarySearchIterative($list, $target);
        assertEquals(1, $result);
        $target = 6;
        $result = ternarySearchIterative($list, $target);
        assertEquals(null, $result);
    }

    public function testTernarySearchByRecursion()
    {
        $list = [0, 5, 7, 10, 15];
        $target = 0;
        $result = ternarySearchByRecursion($list, $target, 0, 4);
        assertEquals(0, $result);
        $target = 15;
        $result = ternarySearchByRecursion($list, $target, 0, 4);
        assertEquals(4, $result);
        $target = 5;
        $result = ternarySearchByRecursion($list, $target, 0, 4);
        assertEquals(1, $result);
        $target = 6;
        $result = ternarySearchByRecursion($list, $target, 0, 4);
        assertEquals(null, $result);
    }

    public function testInterpolationSearch()
    {
        $list = [2, 6, 8, 10, 12, 14, 16, 18, 20, 22, 26, 34, 39];
        $target = 20;
        $result = interpolationSearch($list, $target);
        assertEquals(8, $result);
        $target = 12;
        $result = interpolationSearch($list, $target);
        assertEquals(4, $result);
        $target = 1000;
        $result = interpolationSearch($list, $target);
        assertEquals(null, $result);
        $target = 39;
        $result = interpolationSearch($list, $target);
        assertEquals(12, $result);
    }
}


<?php

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

use PHPUnit\Framework\TestCase;

class ArrayKeysSortTest extends TestCase
{
    public function testArrayKeysSort() {
        //test array of arrays
        $array1 = [
            ['fruit' => 'banana', 'color' => 'yellow', 'cant' => 5],
            ['fruit' => 'apple', 'color' => 'red', 'cant' => 2],
            ['fruit' => 'apple', 'color' => 'green', 'cant' => 7],
            ['fruit' => 'grapes', 'color' => 'purple', 'cant' => 4],
        ];

        $result1 = [
            ['fruit' => 'apple', 'color' => 'red', 'cant' => 2],
            ['fruit' => 'grapes', 'color' => 'purple', 'cant' => 4],
            ['fruit' => 'banana', 'color' => 'yellow', 'cant' => 5],
            ['fruit' => 'apple', 'color' => 'green', 'cant' => 7],
        ];
        $result2 = [
            ['fruit' => 'apple', 'color' => 'green', 'cant' => 7],
            ['fruit' => 'apple', 'color' => 'red', 'cant' => 2],
            ['fruit' => 'banana', 'color' => 'yellow', 'cant' => 5],
            ['fruit' => 'grapes', 'color' => 'purple', 'cant' => 4],
        ];

        $test1 = arrayKeysSort::sort($array1, ['cant']);
        $test2 = arrayKeysSort::sort($array1, ['fruit', 'color']);

        $this->assertEquals($result1, $test1);
        $this->assertEquals($result2, $test2);

        //test array of objects
        $object1 = new \stdClass;
        $object1->fruit = 'banana';
        $object1->color = 'yellow';
        $object1->cant = 5;

        $object2 = new \stdClass;
        $object2->fruit = 'apple';
        $object2->color = 'red';
        $object2->cant = 2;

        $object3 = new \stdClass;
        $object3->fruit = 'apple';
        $object3->color = 'green';
        $object3->cant = 7;

        $object4 = new \stdClass;
        $object4->fruit = 'grapes';
        $object4->color = 'purple';
        $object4->cant = 4;

        $array2 = [$object1, $object2, $object3, $object4];

        $result3 = [$object2, $object4, $object1, $object3];
        $result4 = [$object3, $object2, $object1, $object4];

        $test3 = arrayKeysSort::sort($array2, ['cant']);
        $test4 = arrayKeysSort::sort($array2, ['fruit', 'color']);

        $this->assertEquals($result3, $test3);
        $this->assertEquals($result4, $test4);
    }
}


<?php

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

use PHPUnit\Framework\TestCase;

class GnomeSortTest extends TestCase
{
    public function testGnomeSort()
    {
        $array = array(3, 0, 2, 5, -1, 4, 1);
        $sorted = gnomeSort($array);
        $this->assertEquals([-1, 0, 1, 2, 3, 4, 5], $sorted);
    }

    public function testGnomeSort2()
    {
        $array = array(8, -50, 1, 90, 35, 47, 69, -5);
        $sorted = gnomeSort($array);
        $this->assertEquals([-50, -5, 1, 8, 35, 47, 69, 90], $sorted);
    }
}


<?php

use function PHPUnit\Framework\assertEquals;
use function PHPUnit\Framework\assertFalse;
use function PHPUnit\Framework\assertNotEquals;
use function PHPUnit\Framework\assertTrue;
use PHPUnit\Framework\TestCase;

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

require_once __DIR__ . '/../../Strings/CheckAnagram.php';
require_once __DIR__ . '/../../Strings/CheckPalindrome.php';
require_once __DIR__ . '/../../Strings/CheckPalindrome2.php';
require_once __DIR__ . '/../../Strings/CountConsonants.php';
require_once __DIR__ . '/../../Strings/CountSentences.php';
require_once __DIR__ . '/../../Strings/CountVowels.php';
require_once __DIR__ . '/../../Strings/Distance.php';
require_once __DIR__ . '/../../Strings/MaxCharacter.php';
require_once __DIR__ . '/../../Strings/ReverseString.php';
require_once __DIR__ . '/../../Strings/ReverseWords.php';

class StringsTest extends TestCase
{
    public function testIsPalindrome()
    {
        assertTrue(isPalindrome('MaDam')); // true
        assertFalse(isPalindrome('ThisIsTest')); // false
        assertFalse(isPalindrome('')); // false
        assertTrue(isPalindrome('Sator Arepo Tenet Opera Rotas')); // true
        assertFalse(isPalindrome('Sator Arepo Tenet Opera Rotas', false)); // false since we are doing case-sensitive check
    }

    public function testCountSentences()
    {
        assertEquals(countSentences('Hi! Hello world.'), 2);
        assertEquals(countSentences('i am testing. test....'), 2);
        assertEquals(countSentences('How are you?'), 1);
    }

    public function testReverseString()
    {
        assertEquals('txet emos si sihT', reverseString('This is some text'));
        assertEquals('mADaM', reverseString('MaDAm'));
        assertNotEquals('MaDAm', reverseString('MaDAm'));
    }

    public function testReverseWords()
    {
        assertEquals('Fun is Coding PHP', reverseWords('PHP Coding is Fun'));
        assertEquals('OneWord', reverseWords('OneWord'));
        assertEquals('Text different some is This', reverseWords('This is some different Text'));
    }

    public function testIsAnagram()
    {
        assertTrue(isAnagram("php", "PHP")); // By default it's case-insensitive
        assertFalse(isAnagram("php", "PHP", false)); // Make case-sensitive check
        assertTrue(isAnagram("php is fun", "pin hpf us"));
        assertFalse(isAnagram("Hello", " Hello")); //Extra space character
        assertTrue(isAnagram("ScRamble", "SRlmcaeb", false)); // Check with a mixture of upper and lower case
    }

    public function testMaxCharacter()
    {
        assertEquals(maxCharacter("this is test for max character repetition"), 't');
        assertEquals(maxCharacter("This is Test for max characTer repetition"), 't');
        assertEquals(maxCharacter("           "), ' ');
    }

    public function testCountVowels()
    {
        assertEquals(countVowelsSimple("This is a string with 7 vowels"), 7);
        assertEquals(countVowelsSimple("hello world"), 3);
        assertEquals(countVowelsRegex("Just A list of somE aaaaaaaaaa"), 16);

        assertEquals(countVowelsRegex("This is a string with 7 vowels"), 7);
        assertEquals(countVowelsRegex("hello world"), 3);
        assertEquals(countVowelsRegex("Just A list of somE aaaaaaaaaa"), 16);
    }

    public function testCountConsonants()
    {
        assertEquals(countConsonants("This is a string with 19 consonants"), 19);
        assertEquals(countConsonants("hello world"), 7);
        assertEquals(countConsonants("Just A list of somE aaaaaaaaaa"), 9);
    }

    public function testFindDistance()
    {
        assertEquals(findDistance("hello", "hallo"), 1);
        assertEquals(findDistance("hallo", "hello"), 1);
        assertEquals(findDistance("sunday", "sunday"), 0);
        assertEquals(findDistance("saturday", "sunday"), 3);
    }
}


<?php

/**
 * @param an array of integers
 * @return is array sorted in ascending
 */
function isSortedAscendingInts(array $arr): void{
    $len = count($arr);

    if($len == 0){
        return;
    }

    if(!is_int($arr[0])){
        throw UnexpectedValueException;
    }

    for($i = 1; $i < $len; $i++){
        // a sorted array is expected
        if(!is_int($arr[$i]) && $arr[$i] < $arr[$i-1]){
           throw UnexpectedValueException;
        }
    }
}


<?php

/*
 * Run script and test execution time with following script
        $executionTime = New ExecutionTime();
        print_r(fibonacciRecursive(10));
 */
class ExecutionTime
{
    private $start_time = 0;
    private $end_time = 0;
    private $execution_time = 0;

    public function __construct()
    {
        $this->start_time = microtime(true);
    }

    public function __destruct()
    {
        $this->end_time = microtime(true);
        $this->execution_time = $this->end_time - $this->start_time;
        echo "Executed in $this->execution_time seconds\n";
    }
}


<?php
namespace Aws\Sns;

use Psr\Http\Message\RequestInterface;

/**
 * Represents an SNS message received over http(s).
 */
class Message implements \ArrayAccess, \IteratorAggregate
{
    private static $requiredKeys = [
        'Message',
        'MessageId',
        'Timestamp',
        'TopicArn',
        'Type',
        'Signature',
        ['SigningCertURL', 'SigningCertUrl'],
        'SignatureVersion',
    ];

    private static $subscribeKeys = [
        ['SubscribeURL', 'SubscribeUrl'],
        'Token'
    ];

    /** @var array The message data */
    private $data;

    /**
     * Creates a Message object from the raw POST data
     *
     * @return Message
     * @throws \RuntimeException If the POST data is absent, or not a valid JSON document
     */
    public static function fromRawPostData()
    {
        // Make sure the SNS-provided header exists.
        if (!isset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'])) {
            throw new \RuntimeException('SNS message type header not provided.');
        }

        // Read the raw POST data and JSON-decode it into a message.
        return self::fromJsonString(file_get_contents('php://input'));
    }

    /**
     * Creates a Message object from a PSR-7 Request or ServerRequest object.
     *
     * @param RequestInterface $request
     * @return Message
     */
    public static function fromPsrRequest(RequestInterface $request)
    {
        return self::fromJsonString($request->getBody());
    }

    /**
     * Creates a Message object from a JSON-decodable string.
     *
     * @param string $requestBody
     * @return Message
     */
    public static function fromJsonString($requestBody)
    {
        $data = json_decode($requestBody, true);
        if (JSON_ERROR_NONE !== json_last_error() || !is_array($data)) {
            throw new \RuntimeException('Invalid POST data.');
        }

        return new Message($data);
    }

    /**
     * Creates a Message object from an array of raw message data.
     *
     * @param array $data The message data.
     *
     * @throws \InvalidArgumentException If a valid type is not provided or
     *                                   there are other required keys missing.
     */
    public function __construct(array $data)
    {
        // Ensure that all the required keys for the message's type are present.
        $this->validateRequiredKeys($data, self::$requiredKeys);
        if ($data['Type'] === 'SubscriptionConfirmation'
            || $data['Type'] === 'UnsubscribeConfirmation'
        ) {
            $this->validateRequiredKeys($data, self::$subscribeKeys);
        }

        $this->data = $data;
    }

    #[\ReturnTypeWillChange]
    public function getIterator()
    {
        return new \ArrayIterator($this->data);
    }

    #[\ReturnTypeWillChange]
    public function offsetExists($key)
    {
        return isset($this->data[$key]);
    }

    #[\ReturnTypeWillChange]
    public function offsetGet($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }

    #[\ReturnTypeWillChange]
    public function offsetSet($key, $value)
    {
        $this->data[$key] = $value;
    }

    #[\ReturnTypeWillChange]
    public function offsetUnset($key)
    {
        unset($this->data[$key]);
    }

    /**
     * Get all the message data as a plain array.
     *
     * @return array
     */
    public function toArray()
    {
        return $this->data;
    }

    private function validateRequiredKeys(array $data, array $keys)
    {
        foreach ($keys as $key) {
            $keyIsArray = is_array($key);
            if (!$keyIsArray) {
                $found = isset($data[$key]);
            } else {
                $found = false;
                foreach ($key as $keyOption) {
                    if (isset($data[$keyOption])) {
                        $found = true;
                        break;
                    }
                }
            }

            if (!$found) {
                if ($keyIsArray) {
                    $key = $key[0];
                }
                throw new \InvalidArgumentException(
                    "\"{$key}\" is required to verify the SNS Message."
                );
            }
        }
    }
}


<?php
namespace Aws\Sns;

use Aws\Sns\Exception\InvalidSnsMessageException;

/**
 * Uses openssl to verify SNS messages to ensure that they were sent by AWS.
 */
class MessageValidator
{
    const SIGNATURE_VERSION_1 = '1';
    const SIGNATURE_VERSION_2 = '2';

    /**
     * @var callable Callable used to download the certificate content.
     */
    private $certClient;

    /** @var string */
    private $hostPattern;

    /**
     * @var string  A pattern that will match all regional SNS endpoints, e.g.:
     *                  - sns.<region>.amazonaws.com        (AWS)
     *                  - sns.us-gov-west-1.amazonaws.com   (AWS GovCloud)
     *                  - sns.cn-north-1.amazonaws.com.cn   (AWS China)
     */
    private static $defaultHostPattern
        = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';

    private static function isLambdaStyle(Message $message)
    {
        return isset($message['SigningCertUrl']);
    }

    private static function convertLambdaMessage(Message $lambdaMessage)
    {
        $keyReplacements = [
            'SigningCertUrl' => 'SigningCertURL',
            'SubscribeUrl' => 'SubscribeURL',
            'UnsubscribeUrl' => 'UnsubscribeURL',
        ];

        $message = clone $lambdaMessage;
        foreach ($keyReplacements as $lambdaKey => $canonicalKey) {
            if (isset($message[$lambdaKey])) {
                $message[$canonicalKey] = $message[$lambdaKey];
                unset($message[$lambdaKey]);
            }
        }

        return $message;
    }

    /**
     * Constructs the Message Validator object and ensures that openssl is
     * installed.
     *
     * @param callable $certClient Callable used to download the certificate.
     *                             Should have the following function signature:
     *                             `function (string $certUrl) : string|false $certContent`
     * @param string $hostNamePattern
     */
    public function __construct(
        callable $certClient = null,
        $hostNamePattern = ''
    ) {
        $this->certClient = $certClient ?: function($certUrl) {
            return @ file_get_contents($certUrl);
        };
        $this->hostPattern = $hostNamePattern ?: self::$defaultHostPattern;
    }

    /**
     * Validates a message from SNS to ensure that it was delivered by AWS.
     *
     * @param Message $message Message to validate.
     *
     * @throws InvalidSnsMessageException If the cert cannot be retrieved or its
     *                                    source verified, or the message
     *                                    signature is invalid.
     */
    public function validate(Message $message)
    {
        if (self::isLambdaStyle($message)) {
            $message = self::convertLambdaMessage($message);
        }

        // Get the certificate.
        $this->validateUrl($message['SigningCertURL']);
        $certificate = call_user_func($this->certClient, $message['SigningCertURL']);
        if ($certificate === false) {
            throw new InvalidSnsMessageException(
                "Cannot get the certificate from \"{$message['SigningCertURL']}\"."
            );
        }

        // Extract the public key.
        $key = openssl_get_publickey($certificate);
        if (!$key) {
            throw new InvalidSnsMessageException(
                'Cannot get the public key from the certificate.'
            );
        }

        // Verify the signature of the message.
        $content = $this->getStringToSign($message);
        $signature = base64_decode($message['Signature']);
        $algo = ($message['SignatureVersion'] === self::SIGNATURE_VERSION_1 ? OPENSSL_ALGO_SHA1 : OPENSSL_ALGO_SHA256);
        if (openssl_verify($content, $signature, $key, $algo) !== 1) {
            throw new InvalidSnsMessageException(
                'The message signature is invalid.'
            );
        }
    }

    /**
     * Determines if a message is valid and that is was delivered by AWS. This
     * method does not throw exceptions and returns a simple boolean value.
     *
     * @param Message $message The message to validate
     *
     * @return bool
     */
    public function isValid(Message $message)
    {
        try {
            $this->validate($message);
            return true;
        } catch (InvalidSnsMessageException $e) {
            return false;
        }
    }

    /**
     * Builds string-to-sign according to the SNS message spec.
     *
     * @param Message $message Message for which to build the string-to-sign.
     *
     * @return string
     * @link http://docs.aws.amazon.com/sns/latest/gsg/SendMessageToHttp.verify.signature.html
     */
    public function getStringToSign(Message $message)
    {
        static $signableKeys = [
            'Message',
            'MessageId',
            'Subject',
            'SubscribeURL',
            'Timestamp',
            'Token',
            'TopicArn',
            'Type',
        ];

        if ($message['SignatureVersion'] !== self::SIGNATURE_VERSION_1
            && $message['SignatureVersion'] !== self::SIGNATURE_VERSION_2) {
            throw new InvalidSnsMessageException(
                "The SignatureVersion \"{$message['SignatureVersion']}\" is not supported."
            );
        }

        $stringToSign = '';
        foreach ($signableKeys as $key) {
            if (isset($message[$key])) {
                $stringToSign .= "{$key}\n{$message[$key]}\n";
            }
        }

        return $stringToSign;
    }

    /**
     * Ensures that the URL of the certificate is one belonging to AWS, and not
     * just something from the amazonaws domain, which could include S3 buckets.
     *
     * @param string $url Certificate URL
     *
     * @throws InvalidSnsMessageException if the cert url is invalid.
     */
    private function validateUrl($url)
    {
        $parsed = parse_url($url);
        if (empty($parsed['scheme'])
            || empty($parsed['host'])
            || $parsed['scheme'] !== 'https'
            || substr($url, -4) !== '.pem'
            || !preg_match($this->hostPattern, $parsed['host'])
        ) {
            throw new InvalidSnsMessageException(
                'The certificate is located on an invalid domain.'
            );
        }
    }
}


<?php
namespace Aws\Sns\Exception;

/**
 * Runtime exception thrown by the SNS Message Validator.
 */
class InvalidSnsMessageException extends \RuntimeException
{
}


<?php
namespace Aws\Sns;

use GuzzleHttp\Psr7\Request;

/**
 * @covers \Aws\Sns\Message
 */
class MessageTest extends \PHPUnit_Framework_TestCase
{
    public $messageData = array(
        'Message' => 'a',
        'MessageId' => 'b',
        'Timestamp' => 'c',
        'TopicArn' => 'd',
        'Type' => 'e',
        'Subject' => 'f',
        'Signature' => 'g',
        'SignatureVersion' => '1',
        'SigningCertURL' => 'h',
        'SubscribeURL' => 'i',
        'Token' => 'j',
    );

    public function testGetters()
    {
        $message = new Message($this->messageData);
        $this->assertInternalType('array', $message->toArray());

        foreach ($this->messageData as $key => $expectedValue) {
            $this->assertTrue(isset($message[$key]));
            $this->assertEquals($expectedValue, $message[$key]);
        }
    }

    public function testIterable()
    {
        $message = new Message($this->messageData);

        $this->assertInstanceOf('Traversable', $message);
        foreach ($message as $key => $value) {
            $this->assertTrue(isset($this->messageData[$key]));
            $this->assertEquals($value, $this->messageData[$key]);
        }
    }

    /**
     * @dataProvider messageTypeProvider
     *
     * @param string $messageType
     */
    public function testConstructorSucceedsWithGoodData($messageType)
    {
        $this->assertInstanceOf('Aws\Sns\Message', new Message(
            ['Type' => $messageType] + $this->messageData
        ));
    }

    public function messageTypeProvider()
    {
        return [
            ['Notification'],
            ['SubscriptionConfirmation'],
            ['UnsubscribeConfirmation'],
        ];
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testConstructorFailsWithNoType()
    {
        $data = $this->messageData;
        unset($data['Type']);
        new Message($data);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testConstructorFailsWithMissingData()
    {
        new Message(['Type' => 'Notification']);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testRequiresTokenAndSubscribeUrlForSubscribeMessage()
    {
        new Message(
            ['Type' => 'SubscriptionConfirmation'] + array_diff_key(
                $this->messageData,
                array_flip(['Token', 'SubscribeURL'])
            )
        );
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testRequiresTokenAndSubscribeUrlForUnsubscribeMessage()
    {
        new Message(
            ['Type' => 'UnsubscribeConfirmation'] + array_diff_key(
                $this->messageData,
                array_flip(['Token', 'SubscribeURL'])
            )
        );
    }

    public function testCanCreateFromRawPost()
    {
        $_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'] = 'Notification';

        // Prep php://input with mocked data
        MockPhpStream::setStartingData(json_encode($this->messageData));
        stream_wrapper_unregister('php');
        stream_wrapper_register('php', __NAMESPACE__ . '\MockPhpStream');

        $message = Message::fromRawPostData();
        $this->assertInstanceOf('Aws\Sns\Message', $message);

        stream_wrapper_restore("php");
        unset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE']);
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testCreateFromRawPostFailsWithMissingHeader()
    {
        Message::fromRawPostData();
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testCreateFromRawPostFailsWithMissingData()
    {
        $_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'] = 'Notification';
        Message::fromRawPostData();
        unset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE']);
    }

    public function testCanCreateFromPsr7Request()
    {
        $request = new Request(
            'POST',
            '/',
            [],
            json_encode($this->messageData)
        );
        $message = Message::fromPsrRequest($request);
        $this->assertInstanceOf('Aws\Sns\Message', $message);
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testCreateFromPsr7RequestFailsWithMissingData()
    {
        $request = new Request(
            'POST',
            '/',
            [],
            'Not valid JSON'
        );
        Message::fromPsrRequest($request);
    }

    public function testArrayAccess()
    {
        $message = new Message($this->messageData);

        $this->assertInstanceOf('ArrayAccess', $message);
        $message['foo'] = 'bar';
        $this->assertTrue(isset($message['foo']));
        $this->assertTrue($message['foo'] === 'bar');
        unset($message['foo']);
        $this->assertFalse(isset($message['foo']));
    }
}


<?php
namespace Aws\Sns;

/**
 * @covers Aws\Sns\MessageValidator
 */
class MessageValidatorTest extends \PHPUnit_Framework_TestCase
{
    const VALID_CERT_URL = 'https://sns.foo.amazonaws.com/bar.pem';

    private static $pKey;
    private static $certificate;

    public static function setUpBeforeClass()
    {
        self::$pKey = openssl_pkey_new();
        $csr = openssl_csr_new([], self::$pKey);
        $x509 = openssl_csr_sign($csr, null, self::$pKey, 1);
        openssl_x509_export($x509, self::$certificate);
        openssl_x509_free($x509);
    }

    public static function tearDownAfterClass()
    {
        openssl_pkey_free(self::$pKey);
    }

    public function testIsValidReturnsFalseOnFailedValidation()
    {
        $validator = new MessageValidator($this->getMockHttpClient());
        $message = $this->getTestMessage([
            'SignatureVersion' => '2',
        ]);
        $this->assertFalse($validator->isValid($message));
    }

    /**
     * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
     * @expectedExceptionMessage The SignatureVersion "3" is not supported.
     */
    public function testValidateFailsWhenSignatureVersionIsInvalid()
    {
        $validator = new MessageValidator($this->getMockCertServerClient());
        $message = $this->getTestMessage([
            'SignatureVersion' => '3',
        ]);
        $validator->validate($message);
    }

    /**
     * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
     * @expectedExceptionMessage The certificate is located on an invalid domain.
     */
    public function testValidateFailsWhenCertUrlInvalid()
    {
        $validator = new MessageValidator();
        $message = $this->getTestMessage([
            'SigningCertURL' => 'https://foo.amazonaws.com/bar.pem',
        ]);
        $validator->validate($message);
    }

    /**
     * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
     * @expectedExceptionMessage The certificate is located on an invalid domain.
     */
    public function testValidateFailsWhenCertUrlNotAPemFile()
    {
        $validator = new MessageValidator();
        $message = $this->getTestMessage([
            'SigningCertURL' => 'https://foo.amazonaws.com/bar',
        ]);
        $validator->validate($message);
    }

    public function testValidatesAgainstCustomDomains()
    {
        $validator = new MessageValidator(
            function () {
                return self::$certificate;
            },
            '/^(foo|bar).example.com$/'
        );
        $message = $this->getTestMessage([
            'SigningCertURL' => 'https://foo.example.com/baz.pem',
        ]);
        $message['Signature'] = $this->getSignature($validator->getStringToSign($message));
        $this->assertTrue($validator->isValid($message));
    }

    /**
     * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
     * @expectedExceptionMessageRegExp /Cannot get the certificate from ".+"./
     */
    public function testValidateFailsWhenCannotGetCertificate()
    {
        $validator = new MessageValidator($this->getMockHttpClient(false));
        $message = $this->getTestMessage();
        $validator->validate($message);
    }

    /**
     * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
     * @expectedExceptionMessage Cannot get the public key from the certificate.
     */
    public function testValidateFailsWhenCannotDeterminePublicKey()
    {
        $validator = new MessageValidator($this->getMockHttpClient());
        $message = $this->getTestMessage();
        $validator->validate($message);
    }

    /**
     * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
     * @expectedExceptionMessage The message signature is invalid.
     */
    public function testValidateFailsWhenMessageIsInvalid()
    {
        $validator = new MessageValidator($this->getMockCertServerClient());
        $message = $this->getTestMessage([
            'Signature' => $this->getSignature('foo'),
        ]);
        $validator->validate($message);
    }

        /**
         * @expectedException \Aws\Sns\Exception\InvalidSnsMessageException
         * @expectedExceptionMessage The message signature is invalid.
         */
        public function testValidateFailsWhenSha256MessageIsInvalid()
        {
            $validator = new MessageValidator($this->getMockCertServerClient());
            $message = $this->getTestMessage([
                'Signature' => $this->getSignature('foo'),
                 'SignatureVersion' => '2'

            ]);
            $validator->validate($message);
        }

    public function testValidateSucceedsWhenMessageIsValid()
    {
        $validator = new MessageValidator($this->getMockCertServerClient());
        $message = $this->getTestMessage();

        // Get the signature for a real message
        $message['Signature'] = $this->getSignature($validator->getStringToSign($message));

        // The message should validate
        $this->assertTrue($validator->isValid($message));
    }

    public function testValidateSucceedsWhenSha256MessageIsValid()
    {
        $validator = new MessageValidator($this->getMockCertServerClient());
        $message = $this->getTestMessage([
            'SignatureVersion' => '2'
        ]);

        // Get the signature for a real message
        $message['Signature'] = $this->getSignature($validator->getStringToSign($message), '2');

        // The message should validate
        $this->assertTrue($validator->isValid($message));
    }

    public function testBuildsStringToSignCorrectly()
    {
        $validator = new MessageValidator();
        $stringToSign = <<< STRINGTOSIGN
Message
foo
MessageId
bar
Timestamp
1435697129
TopicArn
baz
Type
Notification

STRINGTOSIGN;

        $this->assertEquals(
            $stringToSign,
            $validator->getStringToSign($this->getTestMessage())
        );
    }

    /**
     * @param array $customData
     *
     * @return Message
     */
    private function getTestMessage(array $customData = [])
    {
        return new Message($customData + [
            'Message'          => 'foo',
            'MessageId'        => 'bar',
            'Timestamp'        => time(),
            'TopicArn'         => 'baz',
            'Type'             => 'Notification',
            'SigningCertURL'   => self::VALID_CERT_URL,
            'Signature'        => true,
            'SignatureVersion' => '1',
        ]);
    }

    private function getMockHttpClient($responseBody = '')
    {
        return function () use ($responseBody) {
            return $responseBody;
        };
    }

    private function getMockCertServerClient()
    {
        return function ($url) {
            if ($url !== self::VALID_CERT_URL) {
                return '';
            }

            return self::$certificate;
        };
    }

    private function getSignature($stringToSign, $algo = '1')
    {
        if ($algo === '2') {
            openssl_sign($stringToSign, $signature, self::$pKey, 'SHA256');
        } else {
            openssl_sign($stringToSign, $signature, self::$pKey);
        }

        return base64_encode($signature);
    }
}

function time()
{
    return 1435697129;
}


<?php
namespace Aws\Sns;

class MockPhpStream
{
    private static $startingData = '';
    private $index;
    private $length;
    private $data;

    public static function setStartingData($data)
    {
        self::$startingData = $data;
    }

    public function __construct()
    {
        $this->data = self::$startingData;
        $this->index = 0;
        $this->length = strlen(self::$startingData);
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        return true;
    }

    public function stream_close()
    {
    }

    public function stream_stat()
    {
        return array();
    }

    public function stream_flush()
    {
        return true;
    }

    public function stream_read($count)
    {
        $length = min($count, $this->length - $this->index);
        $data = substr($this->data, $this->index);
        $this->index = $this->index + $length;

        return $data;
    }

    public function stream_eof()
    {
        return ($this->index >= $this->length);
    }

    public function stream_write($data)
    {
        return 0;
    }
}


<?php
// Run this script from the command line to see if your system is able to run
// the AWS SDK for PHP

class CompatibilityTest
{
    protected $isCli;
    protected $lines = array();

    public function __construct()
    {
        $this->isCli = php_sapi_name() == 'cli';
        $title = 'AWS SDK for PHP Compatibility Test';
        if ($this->isCli) {
            $rep = str_repeat('=', strlen($title));
            $this->lines[] = "{$rep}\n{$title}\n{$rep}";
        } else {
            $this->lines[] = sprintf(
                '',
                'html {font-family:verdana;} .OK {color: #166116;}',
                '.FAIL {margin-top: 1em; color: #A52C27;} .WARNING {margin-top: 1em; color:#6B036B;}'
            );
            $this->lines[] = "
{$title}
";
        }
    }

    public function endTest()
    {
        $text = implode("\n", $this->lines);
        echo $this->isCli ? $text : "{$text}";
    }

    public function title($text)
    {
        $this->lines[] = $this->isCli
            ?  "\n" . $text . "\n" . str_repeat('-', strlen($text)) . "\n"
            : "
{$text}
";
    }

    public function write($text)
    {
        $this->lines[] = $text;
    }

    public function quote($text)
    {
        return !$this->isCli
            ? "
{$text}
"
            : implode("\n", array_map(function ($t) { return '    ' . $t; }, explode("\n", $text)));
    }

    public function check($info, $func, $text, $required)
    {
        $level = $func() ? 'OK' : ($required ? 'FAIL' : 'WARNING');
        if ($this->isCli) {
            $text = $level == 'OK' ? "- {$info}: [OK]" : "- {$info}: [{$level}]\n  - {$text}";
        } else {
            $text = $level == 'OK'
                ? "{$info}
"
                : "
{$info}: [{$level}]

{$text}

";
        }
        $this->write($text);
    }

    public function addRecommend($info, $func, $text)
    {
        $this->check($info, $func, $text, false);
    }

    public function addRequire($info, $func, $text)
    {
        $this->check($info, $func, $text, true);
    }

    public function iniCheck($info, $setting, $expected, $required = true, $help = null)
    {
        $current = ini_get($setting);
        $cb = function () use ($current, $expected) {
            return is_callable($expected)
                ? call_user_func($expected, $current)
                : $current == $expected;
        };

        $message = sprintf(
            '%s in %s is currently set to %s but %s be set to %s.',
            $setting,
            php_ini_loaded_file(),
            var_export($current, true),
            $required ? 'must' : 'should',
            var_export($expected, true)
        ) . ' ' . $help;

        $this->check($info, $cb, trim($message), $required);
    }

    public function extCheck($ext, $required = true, $help = '')
    {
        $info = sprintf('Checking if the %s extension is installed', $ext);
        $cb = function () use ($ext) { return extension_loaded($ext); };
        $message = $help ?: sprintf('The %s extension %s be installed', $ext, $required ? 'must' : 'should');
        $this->check($info, $cb, $message, $required);
    }
}

$c = new CompatibilityTest();
$c->title('System requirements');
$c->addRequire(
    'Ensuring that the version of PHP is >= 5.5.0',
    function () { return version_compare(phpversion(), '5.5.0', '>='); },
    'You must update your version of PHP to 5.5.0 to run the AWS SDK for PHP'
);

$c->iniCheck('Ensuring that detect_unicode is disabled', 'detect_unicode', false, true, 'Enabling detect_unicode may cause errors when using phar files. See https://bugs.php.net/bug.php?id=42396');
$c->iniCheck('Ensuring that session.auto_start is disabled', 'session.auto_start', false);

if (extension_loaded('suhosin')) {
    $c->addRequire(
        'Ensuring that phar files can be run with the suhosin patch',
        function () {
            return false !== stripos(ini_get('suhosin.executor.include.whitelist'), 'phar');
        },
        sprintf('suhosin.executor.include.whitelist must be configured to include "phar" in %s so that the phar file works correctly', php_ini_loaded_file())
    );
}

foreach (array('pcre', 'spl', 'json', 'simplexml') as $ext) {
    $c->extCheck($ext, true);
}

if (function_exists('curl_version')) {
    $c->addRequire('Ensuring that cURL can send https requests', function () {
        $version = curl_version();
        return in_array('https', $version['protocols'], true);
    }, 'cURL must be able to send https requests');
}

$c->addRequire('Ensuring that file_get_contents works', function () {
    return function_exists('file_get_contents');
}, 'file_get_contents has been disabled');

$c->title('System recommendations');

$c->addRecommend('Checking if you are running on a 64-bit platform', function () {
    return PHP_INT_MAX === 9223372036854775807;
}, 'You are not running on a 64-bit installation of PHP. You may run into issues uploading or downloading files larger than 2GB.');

$c->iniCheck('Ensuring that zend.enable_gc is enabled', 'zend.enable_gc', true, false);

$c->check('Ensuring that date.timezone is set', function () {
    return (bool) ini_get('date.timezone');
}, 'The date.timezone PHP ini setting has not been set in ' . php_ini_loaded_file(), false);

if (extension_loaded('xdebug')) {
    $c->addRecommend('Checking if Xdebug is installed', function () { return false; }, 'Xdebug is installed. Consider uninstalling Xdebug to make the SDK run much faster.');
    $c->iniCheck('Ensuring that Xdebug\'s infinite recursion detection does not erroneously cause a fatal error', 'xdebug.max_nesting_level', 0, false);
}

$c->extCheck('dom',false);
$c->extCheck('curl',false);
$c->extCheck('openssl', false);
$c->extCheck('zlib', false);
$c->iniCheck('Checking if OPCache is enabled', 'opcache.enable', 1, false);

$c->title('PHP information');
ob_start();
phpinfo(INFO_GENERAL);
$info = ob_get_clean();
$c->write($c->quote($info));

$c->endTest();


<?php
/**
 * This file updates the '@method' annotations on the Aws\Sdk class.
 */

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

$namespaces = array_map(function (array $manifest) {
    return $manifest['namespace'];
}, array_values(Aws\manifest()));

sort($namespaces);
$annotations = [];
foreach ($namespaces as $namespace) {
    $mrClient = "\\Aws\\{$namespace}\\{$namespace}MultiRegionClient";
    $mrClient = class_exists($mrClient) ? $mrClient : "\\Aws\\MultiRegionClient";

    $annotations []= " * @method \\Aws\\{$namespace}\\{$namespace}Client"
        . " create{$namespace}(array \$args = [])";
    $annotations []= " * @method $mrClient"
        . " createMultiRegion{$namespace}(array \$args = [])";
}

$previousAnnotationPattern = '/^\* @method'
    . ' \\\\Aws\\\\(?:[a-zA-Z0-9\\\\]+)Client'
    . ' create(?:[a-zA-Z0-9]+)\\(array \$args = \\[\\]\\)/';

$updater = new ClassAnnotationUpdater(
    new ReflectionClass(\Aws\Sdk::class),
    $annotations,
    '',
    $previousAnnotationPattern
);
$updater->update();


<?php
/**
 * This file is responsible for adding @method annotations to each client class.
 */
require __DIR__ . '/../vendor/autoload.php';

// parse out --class, --tag, and --all options
// You can have multiple --class and --tag flags
//  e.g. php annotate-clients.php --class=Aws\S3\S3Client --class=Aws\Swf\SwfClient
// Use --class to update the annotation on a class
// Use --tag to update annotations on clients updated since a given git tag
// Use --tag=latest to do so comparing against the last release
// Use --all to update annotations on all clients
// Combine them at will; each class will only be updated once
$options = getopt('', ['class:', 'tag:', 'all']) + ['class' => [], 'tag' => []];

// make sure all options are arrays
array_walk($options, function (&$value) {
    if (!is_array($value)) {
        $value = [$value];
    }
});

function get_client_classes($namespace) {
    $clients = ["Aws\\{$namespace}\\{$namespace}Client"];
    if (class_exists("Aws\\{$namespace}\\{$namespace}MultiRegionClient")) {
        $clients []= "Aws\\{$namespace}\\{$namespace}MultiRegionClient";
    }

    return $clients;
};

if (isset($options['all'])) {
    // Get all client classes and mark them for update
    $options['class'] = Aws\flatmap(Aws\manifest(), function (array $manifest) {
        return get_client_classes($manifest['namespace']);
    });
}

foreach ($options['tag'] as $tag) {
    // Change 'latest' to the last release
    if ('latest' === $tag) {
        $tag = trim(`git tag | tail -n 1`);
    }

    // Find all api-2.json files changed since a given tag. These are service
    // API definitions; changes here can mean changes to client annotations.
    exec("git diff-index --name-only --cached $tag", $files);
    $alteredApiFiles = array_filter($files, function ($file) {
        return preg_match('/api-2.json$/', $file);
    });

    // Find the client classes for each changed API service definition and mark
    // them for update.
    $clientsWithChangedApis = Aws\flatmap($alteredApiFiles, function ($file) {
        $file = str_replace('src/data/', '', $file);
        $endpoint = substr($file, 0, strpos($file, '/'));
        return get_client_classes(Aws\manifest($endpoint)['namespace']);
    });
    $options['class'] = \Aws\flatmap(
        [$options['class'], $clientsWithChangedApis],
        function ($class) { return $class; }
    );
}

foreach ($options['class'] as $classToUpdate) {
    // Update the @method annotations on a client.
    $annotator = new ClientAnnotator($classToUpdate);

    if (!$annotator->updateApiMethodAnnotations()) {
        trigger_error(
            "Unable to update annotations on $classToUpdate",
            E_USER_WARNING
        );
    }
}


<?php
require __DIR__  . '/../vendor/autoload.php';

\JmesPath\Env::cleanCompileDir();


<?php
/*
 * This file is responsible for building up the API manifest file, including
 * determining the latest API version and hooking up endpoint prefix names
 * with client namespace names.
 */

$compatibleApiVersions = [
    'apigateway' => [
        'latest' => [
            '2015-06-01',
        ],
    ],
    'cloudfront' => [
        'latest' => [
            '2016-01-13',
            '2015-09-17',
        ],
        '2015-07-27' => [
            '2015-04-17',
            '2014-11-06'
        ],
    ],
    'ec2' => [
        'latest' => [
            '2015-04-15',
        ],
    ],
    'events' => [
        'latest' => [
            '2014-02-03',
        ],
    ],
    'inspector' => [
        'latest' => [
            '2015-08-18',
        ]
    ],
];

// Create a list of possible namespaces so that we can include it in the
// manifest.
$possibleNamespaces = [];
$skip = ['.', '..', 'Api', 'data', 'Multipart', 'Signature'];
foreach (scandir(__DIR__ . '/../src') as $dir) {
    if (!in_array($dir, $skip) && is_dir(__DIR__ . '/../src/' . $dir)) {
        $possibleNamespaces[strtolower($dir)] = $dir;
    }
}

$manifest = [];
foreach (glob(__DIR__ . '/../src/data/**/**/api-2.json') as $file) {
    $model = json_decode(file_get_contents($file), true);
    preg_match('@src/data/([^/]+)/[0-9]{4}-[0-9]{2}-[0-9]{2}/api-2.json$@', $file, $matches);
    $identifier = $matches[1];
    $metadata = $model['metadata'] + ['compatibleApiVersions' => []];
    if (empty($manifest[$identifier])) {
        // Calculate a namespace for the service.
        $ns = isset($metadata['serviceAbbreviation'])
            ? $metadata['serviceAbbreviation']
            : $metadata['serviceFullName'];
        $ns = str_replace(['Amazon', 'AWS', 'Beta', '(', ')', ' ', '/', '-'], '', $ns);

        //check if it's a grandfathered namespace
        $grandfatheredServices = json_decode(
            file_get_contents(__DIR__ . '/../src/data/grandfathered-services.json'),
            true
        )['grandfathered-services'];
        if (!in_array($ns, $grandfatheredServices)) {
            $ns = str_replace(' ', '', ucwords($metadata['serviceId']));
        }

        if (!isset($possibleNamespaces[strtolower($ns)])) {
            throw new \Exception('NS not found: ' . $ns);
        }

        $ns = $possibleNamespaces[strtolower($ns)];

        $manifest[$identifier] = [
            'namespace' => $ns,
            'versions' => [],
        ];
    }

    $manifest[$identifier]['versions'][$metadata['apiVersion']]
        = $metadata['apiVersion'];
}

foreach ($manifest as $identifier => &$metadata) {
    $metadata['versions']['latest'] = max(array_keys($metadata['versions']));
    foreach ($metadata['versions'] as $name => $version) {
        if (isset($compatibleApiVersions[$identifier][$name])) {
            foreach ($compatibleApiVersions[$identifier][$name] as $compatVersion) {
                $metadata['versions'][$compatVersion]
                    = $metadata['versions'][$name];
            }
        }
    }

    krsort($metadata['versions']);
}

$data = json_encode($manifest, JSON_PRETTY_PRINT);
$file = __DIR__ . '/../src/data/manifest.json';
file_put_contents($file, "$data\n");

echo "Wrote the following data to {$file}:\n>>>>>\n{$data}<<<<<\n";


<?php
// Setup autoloading for SDK and build classes.
require __DIR__ . '/../vendor/autoload.php';

use Aws\Build\Docs\RedirectMapBuilder;

$path = __DIR__ . '/artifacts/docs/package.redirects.conf';
$apiProvider = \Aws\Api\ApiProvider::defaultProvider();

$builder = new RedirectMapBuilder($apiProvider, $path);
$builder->build();


<?php

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/ServiceBuilder.php';

$options = getopt('', [
    'namespace:',
    'model:',
    'clientPath:',
    'exceptionPath:'
]);

if (empty($options['namespace']) || empty($options['model'])) {
    throw new LogicException(
        "You must specify a namespace (--namespace=) and path to an api model "
        . "(--model=) to build a service"
    );
}

$options['model'] = \Aws\load_compiled_json($options['model']);
$options['namespace'] = ucfirst($options['namespace']);

$options += [
    'clientPath' => dirname(__DIR__)
        . "/src/{$options['namespace']}/{$options['namespace']}Client.php",
    'exceptionPath' => dirname(__DIR__)
        . "/src/{$options['namespace']}/Exception/{$options['namespace']}Exception.php",
];

(new ServiceBuilder(
    $options['namespace'],
    $options['model'],
    $options['clientPath'],
    $options['exceptionPath']
))
    ->buildClient()
    ->buildException();


<?php

/**
 * Packages the zip and phar file using a staging directory.
 *
 * @license MIT, Michael Dowling https://github.com/mtdowling
 * @license https://github.com/mtdowling/Burgomaster/LICENSE
 */
class Burgomaster
{
    /** @var string Base staging directory of the project */
    public $stageDir;

    /** @var string Root directory of the project */
    public $projectRoot;

    /** @var array stack of sections */
    private $sections = array();

    /**
     * @param string $stageDir    Staging base directory where your packaging
     *                            takes place. This folder will be created for
     *                            you if it does not exist. If it exists, it
     *                            will be deleted and recreated to start fresh.
     * @param string $projectRoot Root directory of the project.
     *
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     */
    public function __construct($stageDir, $projectRoot = null)
    {
        $this->startSection('setting_up');
        $this->stageDir = $stageDir;
        $this->projectRoot = $projectRoot;

        if (!$this->stageDir || $this->stageDir == '/') {
            throw new \InvalidArgumentException('Invalid base directory');
        }

        if (is_dir($this->stageDir)) {
            $this->debug("Removing existing directory: $this->stageDir");
            echo $this->exec("rm -rf $this->stageDir");
        }

        $this->debug("Creating staging directory: $this->stageDir");

        if (!mkdir($this->stageDir, 0777, true)) {
            throw new \RuntimeException("Could not create {$this->stageDir}");
        }

        $this->stageDir = realpath($this->stageDir);
        $this->debug("Creating staging directory at: {$this->stageDir}");

        if (!is_dir($this->projectRoot)) {
            throw new \InvalidArgumentException(
                "Project root not found: $this->projectRoot"
            );
        }

        $this->endSection();
        $this->startSection('staging');

        chdir($this->projectRoot);
    }

    /**
     * Cleanup if the last section was not already closed.
     */
    public function __destruct()
    {
        if ($this->sections) {
            $this->endSection();
        }
    }

    /**
     * Call this method when starting a specific section of the packager.
     *
     * This makes the debug messages used in your script more meaningful and
     * adds context when things go wrong. Be sure to call endSection() when
     * you have finished a section of your packaging script.
     *
     * @param string $section Part of the packager that is running
     */
    public function startSection($section)
    {
        $this->sections[] = $section;
        $this->debug('Starting');
    }

    /**
     * Call this method when leaving the last pushed section of the packager.
     */
    public function endSection()
    {
        if ($this->sections) {
            $this->debug('Completed');
            array_pop($this->sections);
        }
    }

    /**
     * Prints a debug message to STDERR bound to the current section.
     *
     * @param string $message Message to echo to STDERR
     */
    public function debug($message)
    {
        $prefix = date('c') . ': ';

        if ($this->sections) {
            $prefix .= '[' . end($this->sections) . '] ';
        }

        fwrite(STDERR, $prefix . $message . "\n");
    }

    /**
     * Copies a file and creates the destination directory if needed.
     *
     * @param string $from File to copy
     * @param string $to   Destination to copy the file to, relative to the
     *                     base staging directory.
     * @throws \InvalidArgumentException if the file cannot be found
     * @throws \RuntimeException if the directory cannot be created.
     * @throws \RuntimeException if the file cannot be copied.
     */
    public function deepCopy($from, $to)
    {
        if (!is_file($from)) {
            throw new \InvalidArgumentException("File not found: {$from}");
        }

        $to = str_replace('//', '/', $this->stageDir . '/' . $to);
        $dir = dirname($to);

        if (!is_dir($dir)) {
            if (!mkdir($dir, 0777, true)) {
                throw new \RuntimeException("Unable to create directory: $dir");
            }
        }

        if (!copy($from, $to)) {
            throw new \RuntimeException("Unable to copy $from to $to");
        }
    }

    /**
     * Recursively copy one folder to another.
     *
     * Any LICENSE file is automatically copied.
     *
     * @param string    $sourceDir  Source directory to copy from
     * @param string    $destDir    Directory to copy the files to that is relative
     *                              to the the stage base directory.
     * @param array     $extensions File extensions to copy from the $sourceDir.
     *                              Defaults to "php" files only (e.g., ['php']).
     * @param Iterator|null  $files Files to copy from the source directory, each
     *                              yielded out as an SplFileInfo object.
     *                              Defaults to a recursive iterator of $sourceDir
     * @throws \InvalidArgumentException if the source directory is invalid.
     */
    function recursiveCopy(
        $sourceDir,
        $destDir,
        $extensions = array('php', 'php.gz'),
        Iterator $files = null
    ) {
        if (!realpath($sourceDir)) {
            throw new \InvalidArgumentException("$sourceDir not found");
        }

        if (!$extensions) {
            throw new \InvalidArgumentException('$extensions is empty!');
        }

        $sourceDir = realpath($sourceDir);
        $exts = array_fill_keys($extensions, true);
        if (empty($files)) {
            $files = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($sourceDir)
            );
        }
        $total = 0;

        $this->startSection('copy');
        $this->debug("Starting to copy files from $sourceDir");
        foreach ($files as $file) {
            $shouldCopyFile = false;
            if ($file->getBaseName() == 'LICENSE') {
                $shouldCopyFile = true;
            } else {
                foreach ($extensions as $extension) {
                    if (substr($file->getFilename(), -strlen($extension)) == $extension) {
                        $shouldCopyFile = true;
                    }
                }
            }
            if ($shouldCopyFile) {
                // Remove the source directory from the destination path
                $toPath = str_replace($sourceDir, '', (string) $file);
                $toPath = $destDir . '/' . $toPath;
                $toPath = str_replace('//', '/', $toPath);
                $this->deepCopy((string) $file, $toPath);
                $total++;
            }
        }

        $this->debug("Copied $total files from $sourceDir");
        $this->endSection();
    }

    /**
     * Execute a command and throw an exception if the return code is not 0.
     *
     * @param string $command Command to execute
     *
     * @return string Returns the output of the command as a string
     * @throws \RuntimeException on error.
     */
    public function exec($command)
    {
        $this->debug("Executing: $command");
        $output = $returnValue = null;
        exec($command, $output, $returnValue);

        if ($returnValue != 0) {
            throw new \RuntimeException('Error executing command: '
                . $command . ' : ' . implode("\n", $output));
        }

        return implode("\n", $output);
    }

    /**
     * Creates a class-map autoloader to the staging directory in a file
     * named autoloader.php
     *
     * @param array $files Files to explicitly require in the autoloader. This
     *                     is similar to Composer's "files" "autoload" section.
     * @param string $filename Name of the autoloader file.
     * @throws \RuntimeException if the file cannot be written
     */
    function createAutoloader($files = array(), $filename = 'autoloader.php') {
        $sourceDir = realpath($this->stageDir);
        $iter = new \RecursiveDirectoryIterator($sourceDir);
        $iter = new \RecursiveIteratorIterator($iter);

        $this->startSection('autoloader');
        $this->debug('Creating classmap autoloader');
        $this->debug("Collecting valid PHP files from {$this->stageDir}");

        $classMap = array();
        foreach ($iter as $file) {
            if ($file->getExtension() == 'php') {
                $location = str_replace($this->stageDir . '/', '', (string) $file);
                $className = str_replace('/', '\\', $location);
                $className = substr($className, 0, -4);

                // Remove "src\" or "lib\"
                if (strpos($className, 'src\\') === 0
                    || strpos($className, 'lib\\') === 0
                ) {
                    $className = substr($className, 4);
                }

                $classMap[$className] = "__DIR__ . '/$location'";
                $this->debug("Found $className");
            }
        }

        $destFile = $this->stageDir . '/' . $filename;
        $this->debug("Writing autoloader to {$destFile}");

        if (!($h = fopen($destFile, 'w'))) {
            throw new \RuntimeException('Unable to open file for writing');
        }

        $this->debug('Writing classmap files');
        fwrite($h, "<?php\n\n");
        fwrite($h, "\$mapping = array(\n");
        foreach ($classMap as $c => $f) {
            fwrite($h, "    '$c' => $f,\n");
        }
        fwrite($h, ");\n\n");
        fwrite($h, <<<EOT
spl_autoload_register(function (\$class) use (\$mapping) {
    if (isset(\$mapping[\$class])) {
        require \$mapping[\$class];
    }
}, true);

EOT
        );

        fwrite($h, "\n");

        $this->debug('Writing automatically included files');
        foreach ($files as $file) {
            fwrite($h, "require __DIR__ . '/$file';\n");
        }

        fclose($h);

        $this->endSection();
    }

    /**
     * Creates a default stub for the phar that includes the generated
     * autoloader.
     *
     * This phar also registers a constant that can be used to check if you
     * are running the phar. The constant is the basename of the $dest variable
     * without the extension, with "_PHAR" appended, then converted to all
     * caps (e.g., "/foo/guzzle.phar" gets a contant defined as GUZZLE_PHAR.
     *
     * @param $dest
     * @param string $autoloaderFilename Name of the autoloader file.
     * @param string $alias The phar alias to use
     *
     * @return string
     */
    private function createStub($dest, $autoloaderFilename = 'autoloader.php', $alias = null)
    {
        $this->startSection('stub');
        $this->debug("Creating phar stub at $dest");

        $alias = $alias ?: basename($dest);
        $constName = str_replace('.phar', '', strtoupper($alias)) . '_PHAR';
        $stub  = "<?php\n";
        $stub .= "define('$constName', true);\n";
        $stub .= "Phar::mapPhar('$alias');\n";
        $stub .= "require 'phar://$alias/{$autoloaderFilename}';\n";
        $stub .= "__HALT_COMPILER();\n";
        $this->endSection();

        return $stub;
    }

    /**
     * Creates a phar that automatically registers an autoloader.
     *
     * Call this only after your staging directory is built.
     *
     * @param string $dest Where to save the file. The basename of the file
     *     is also used as the alias name in the phar
     *     (e.g., /path/to/guzzle.phar => guzzle.phar).
     * @param string|bool|null $stub The path to the phar stub file. Pass or
     *      leave null to automatically have one created for you. Pass false
     *      to no use a stub in the generated phar.
     * @param string $autoloaderFilename Name of the autoloader filename.
     */
    public function createPhar(
        $dest,
        $stub = null,
        $autoloaderFilename = 'autoloader.php',
        $alias = null
    ) {
        $this->startSection('phar');
        $this->debug("Creating phar file at $dest");
        $this->createDirIfNeeded(dirname($dest));
        $phar = new \Phar($dest, 0, $alias ?: basename($dest));
        $phar->buildFromDirectory($this->stageDir);

        if ($stub !== false) {
            if (!$stub) {
                $stub = $this->createStub($dest, $autoloaderFilename, $alias);
            }
            $phar->setStub($stub);
        }

        $this->debug("Created phar at $dest");
        $this->endSection();
    }

    /**
     * Creates a zip file containing the staged files of your project.
     *
     * Call this only after your staging directory is built.
     *
     * @param string $dest Where to save the zip file
     */
    public function createZip($dest)
    {
        $this->startSection('zip');
        $this->debug("Creating a zip file at $dest");
        $this->createDirIfNeeded(dirname($dest));
        chdir($this->stageDir);
        $this->exec("zip -r $dest ./");
        $this->debug("  > Created at $dest");
        chdir(__DIR__);
        $this->endSection();
    }

    private function createDirIfNeeded($dir)
    {
        if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
            throw new \RuntimeException("Could not create dir: $dir");
        }
    }
}


<?php

/**
 * Adds and removes annotations to a class.
 *
 * @internal
 */
class ClassAnnotationUpdater
{
    use PhpFileLinterTrait;

    /** @var ReflectionClass */
    private $reflection;
    /** @var string[] */
    private $linesToAppend;
    /** @var string */
    private $defaultDocBlock;
    /** @var string */
    private $removeMatching;

    public function __construct(
        ReflectionClass $reflection,
        array $linesToAppend,
        $defaultDocBlock,
        $removeMatching = ''
    ) {
        $this->reflection = $reflection;
        $this->linesToAppend = $linesToAppend;
        $this->defaultDocBlock = $defaultDocBlock;
        $this->removeMatching = $removeMatching;
    }

    /**
     * Performs update on class file and lints the output. If the output fails
     * linting, the change is reverted.
     *
     * @return bool TRUE on success, FALSE on failure
     */
    public function update()
    {
        // copy the code into memory
        $backup = file($this->reflection->getFileName());

        list($preamble, $class) = $this->splitClassFile($backup);
        $preamble = $this->stripOutExistingDocBlock($preamble);
        $preamble .= $this->buildUpdatedDocBlock();

        if ($this->writeClassFile(implode(PHP_EOL, [$preamble, $class]))
            && $this->commandLineLint($this->reflection->getFileName())
        ) {
            return true;
        }

        $this->writeClassFile(implode('', $backup));
        return false;
    }

    private function splitClassFile(array $lines)
    {
        $classLineOffset = $this->reflection->getStartLine() - 1;
        return [
            implode('', array_slice($lines, 0, $classLineOffset)),
            implode('', array_slice($lines, $classLineOffset)),
        ];
    }

    private function stripOutExistingDocBlock($preamble)
    {
        if ($this->reflection->getDocComment()) {
            return str_replace(
                $this->reflection->getDocComment() . PHP_EOL,
                '',
                $preamble
            );
        }

        return $preamble;
    }

    private function buildUpdatedDocBlock()
    {
        $docBlockLines = explode(
            PHP_EOL,
            $this->reflection->getDocComment() ?: $this->defaultDocBlock
        );

        // remove lines matching exclusion patterns
        if ($this->removeMatching) {
            $docBlockLines = array_filter($docBlockLines, function ($line) {
                return !preg_match($this->removeMatching, trim($line));
            });
        }

        // hold on to the closing line
        $lastLine = array_pop($docBlockLines);

        // add a padding line if needed
        if (' *' !== end($docBlockLines)) {
            $docLines []= ' *';
        }

        // append API @method annotations
        $docBlockLines = array_merge($docBlockLines, $this->linesToAppend);

        // add back the closing line
        $docBlockLines []= $lastLine;

        // send everything back as a string
        return implode(PHP_EOL, $docBlockLines);
    }

    private function writeClassFile($contents)
    {
        return (bool) file_put_contents(
            $this->reflection->getFileName(),
            $contents,
            LOCK_EX
        );
    }
}


<?php

use Aws\Api\ApiProvider;

class ClientAnnotator
{
    /** @var ReflectionClass */
    private $reflection;
    /** @var string */
    private $endpoint;
    /** @var string[] */
    private $versions;
    /** @var array */
    private $methods;
    /** @var array */
    private $aliases;

    public function __construct($clientClassName)
    {
        $this->reflection = new ReflectionClass($clientClassName);
        $this->aliases = \Aws\load_compiled_json(__DIR__ . '/../src/data/aliases.json');
    }

    /**
     * Adds @method annotations to a client class.
     *
     * @return bool TRUE on success, FALSE on failure
     */
    public function updateApiMethodAnnotations()
    {
        $updater = new ClassAnnotationUpdater(
            $this->reflection,
            $this->getMethodAnnotations(),
            $this->getDefaultDocComment(),
            '/^\* @method (\\\\Aws\\\\Result|\\\\GuzzleHttp\\\\Promise\\\\Promise) /'
        );

        return $updater->update();
    }

    private function getMethodAnnotations()
    {
        $annotations = [];

        foreach ($this->getMethods() as $command => $apiVersions) {
            $commandMethods = [
                $command => '\\Aws\\Result',
                "{$command}Async" => '\\GuzzleHttp\\Promise\\Promise',
            ];
            foreach ($commandMethods as $method => $returnType) {
                $annotations []= $this->getAnnotationLine(
                    $method,
                    $returnType,
                    $apiVersions
                );
            }
        }

        return $annotations;
    }

    private function getAnnotationLine($method, $return, array $versionsWithSupport)
    {
        $signature = lcfirst($method) . '(array $args = [])';
        $annotation = " * @method $return $signature";

        if ($versionsWithSupport !== $this->getVersions()) {
            $supportedIn = implode(', ', $versionsWithSupport);
            $annotation .= " (supported in versions $supportedIn)";
        }

        return $annotation;
    }

    private function getMethods()
    {
        if (empty($this->methods)) {
            $this->methods = [];

            foreach ($this->getVersions() as $version) {
                $methodsInVersion = array_keys(
                    $this->getApiDefinition($version)['operations']
                );

                $api = $this->getApiDefinition($version);
                $serviceId = !empty($api['metadata']['serviceId'])
                    ? $api['metadata']['serviceId']
                    : null;

                foreach ($methodsInVersion as $method) {

                    if (!empty($serviceId)
                        && !empty($this->aliases['operations'][$serviceId][$version][$method])
                    ) {
                        $method = $this->aliases['operations'][$serviceId][$version][$method];
                    }

                    if (empty($this->methods[$method])) {
                        $this->methods[$method] = [];
                    }

                    $this->methods[$method] []= $version;
                }
            }
        }

        return $this->methods;
    }

    private function getVersions()
    {
        if (empty($this->versions)) {
            $this->versions = ApiProvider::defaultProvider()
                ->getVersions($this->getEndpoint());

            // ensure that versions are always iterated from oldest to newest
            sort($this->versions);
        }

        return $this->versions;
    }

    private function getApiDefinition($version = 'latest')
    {
        $provider = ApiProvider::defaultProvider();
        return $provider('api', $this->getEndpoint(), $version);
    }

    private function getEndpoint()
    {
        if (empty($this->endpoint)) {
            $service = strtolower(
                preg_replace('/(MultiRegion)?Client$/', '', $this->reflection->getShortName())
            );

            $this->endpoint = Aws\manifest($service)['endpoint'];
        }

        return $this->endpoint;
    }

    private function getDefaultDocComment()
    {
        $serviceName = $this->getApiDefinition()['metadata']['serviceFullName'];
        switch ($this->reflection->getParentClass()->getShortName()) {
            case 'MultiRegionClient':
                return <<<EODC
/**
 * **{$serviceName}** multi-region client.
 *
 */
EODC;
            default:
                return <<<EODC
/**
 * **{$serviceName}** client.
 *
 */
EODC;
        }
    }
}


<?php
require __DIR__ . '/../vendor/autoload.php';

/**
 * Finds all JSON files in src/data, converts them to PHP files that return
 * arrays, and writes those files to $originalFileName.json.php.  Files for
 * endpoints v2 starting with endpoints- will get zipped.
 */
$dataFilesIterator = \Aws\recursive_dir_iterator(realpath(__DIR__ . '/../src/data'));
foreach (new RegexIterator($dataFilesIterator, '/\.json$/') as $dataFile) {
    (new JsonCompiler($dataFile))
        ->compile("$dataFile.php");
}


<?php
use Aws\Build\Docs\DocsBuilder;

// Setup autoloading for SDK and build classes.
$loader = require __DIR__ . '/../vendor/autoload.php';

// Setup directories.
$config = \Nette\Neon\Neon::decode(file_get_contents(__DIR__ . '/docs/apigen.neon'));
$outputDir = __DIR__ . '/../' . $config['destination'];
$apiProvider = \Aws\Api\ApiProvider::defaultProvider();

// Extract the built homepage into a template file.
$xml = new DOMDocument();
@$xml->loadHTMLFile(__DIR__ . '/artifacts/docs/index.html');
$ele = $xml->getElementById('content');
$ele->nodeValue = '{{ contents }}';
$template = str_replace('class="homepage"', 'class="generated-page"', $xml->saveHTML());

$quickLinkServices = ['s3', 'dynamodb', 'glacier', 'ec2'];

$sourceDirs = array_map(function ($dirRelativeToProjectRoot) {
    return __DIR__ . '/../' . $dirRelativeToProjectRoot;
}, is_array($config['source']) ? $config['source'] : [$config['source']]);
$sourceFiles = [];
foreach ($sourceDirs as $dir) {
    $sourceFiles = array_merge(
        $sourceFiles,
        array_filter(
            array_map('realpath', iterator_to_array(\Aws\recursive_dir_iterator($dir))),
            function ($path) {
                return preg_match('/(?<!\.json)\.php/', $path);
            }
        )
    );
}

$issueLoggingEnabled = isset(getopt(
    '',
    array('issue-logging-enabled::')
)['issue-logging-enabled']);

// Generate API docs
$builder = new DocsBuilder(
    $apiProvider,
    $outputDir,
    $template,
    $config['baseUrl'],
    $quickLinkServices,
    $sourceFiles,
    $issueLoggingEnabled
);
$builder->build();


<?php
/*
 * Creates a Github API release using the changelog contents. Attaches aws.zip
 * and aws.phar to the release.
 *
 * The OAUTH_TOKEN environment variable is required.
 *
 *     Usage: php gh-release.php X.Y.Z
 */

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

use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7;

const MAX_ATTEMPTS = 3;

$owner = 'aws';
$repo = 'aws-sdk-php';
$token = getenv('OAUTH_TOKEN') or die('An OAUTH_TOKEN environment variable is required');
isset($argv[1]) or die('Usage php gh-release.php X.Y.Z');
$tag = $argv[1];

// Grab and validate the tag annotation
chdir(dirname(__DIR__));
$message = `chag contents -t "$tag"` or die('Chag could not find or parse the tag');

// Add retry middleware
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push(\GuzzleHttp\Middleware::retry(
    function ($retries, $request, $response) {
        $statusCode = $response->getStatusCode();
        if ($retries < MAX_ATTEMPTS && !in_array($statusCode, [200, 201, 202])) {
            echo "Attempt failed with status code {$statusCode}: "
                . $response->getBody();
            return true;
        }
        return false;
    },
    function ($retries) {
        return 1000 * (1 + $retries);
    }
));

// Create a GitHub client.
$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.github.com/',
    'headers' => ['Authorization' => "token $token"],
    'handler' => $stack,
]);

// Create a Github client with no retry middleware, to allow for custom retry handling
$uploadClient = new GuzzleHttp\Client([
    'base_uri' => 'https://api.github.com/',
    'headers' => ['Authorization' => "token $token"]
]);

// Publish the release
$response = $client->post("repos/${owner}/${repo}/releases", [
    'json' => [
        'tag_name'   => $tag,
        'name'       => "Version {$tag}",
        'body'       => $message,
    ]
]);
$releaseBody = json_decode($response->getBody(), true);

// Grab the location of the new release
$url = $response->getHeaderLine('Location');
echo "Release successfully published to: $url\n";

// Uploads go to uploads.github.com
$uploadUrl = new Uri($url);
$uploadUrl = $uploadUrl->withHost('uploads.github.com');

// Upload aws.zip
$zipAttempts = retryUpload($client, $uploadClient, $owner, $repo, $releaseBody, $uploadUrl, 'aws.zip');
if ($zipAttempts === false) {
    echo "aws.zip upload failed after " . MAX_ATTEMPTS . " attempts.\n";
} else {
    echo "aws.zip upload succeeded after {$zipAttempts} attempt(s).\n";
}

// Upload aws.phar
$pharAttempts = retryUpload($client, $uploadClient, $owner, $repo, $releaseBody, $uploadUrl, 'aws.phar');
if ($pharAttempts === false) {
    echo "aws.phar upload failed after " . MAX_ATTEMPTS . " attempts.\n";
} else {
    echo "aws.phar upload succeeded after {$pharAttempts} attempt(s).\n";
}

/**
 * Attempts an artifact upload and retries up to MAX_ATTEMPTS times
 *
 * @param $client
 * @param $uploadClient
 * @param $owner
 * @param $repo
 * @param $releaseBody
 * @param $uploadUrl
 * @param $filename
 * @return bool|int
 */
function retryUpload($client, $uploadClient, $owner, $repo, $releaseBody, $uploadUrl, $filename)
{
    $isSuccessful = false;
    $attempts = 0;
    $filetype = substr($filename, strpos($filename, '.') + 1);

    while (!$isSuccessful && $attempts < MAX_ATTEMPTS) {
        try {
            $attempts++;
            $response = $uploadClient->post("{$uploadUrl}/assets?name={$filename}", [
                'headers' => ['Content-Type' => "application/{$filetype}"],
                'body'    => Psr7\Utils::tryFopen(__DIR__ . "/artifacts/{$filename}", 'r')
            ]);
            echo "{$filename} uploaded to: " . json_decode($response->getBody(), true)['browser_download_url'] . "\n";
            $isSuccessful = true;
        } catch (\GuzzleHttp\Exception\ServerException $e) {
            echo "{$filename} failed to upload:\n";
            var_dump($e->getMessage());

            // Fetch and inspect assets for failed downloads
            $response = $client->get("/repos/{$owner}/{$repo}/releases/{$releaseBody['id']}/assets", []);
            $assets = json_decode($response->getBody(), true);

            foreach ($assets as $asset) {

                // Only successful uploads have a state of 'uploaded'
                if ($asset['state'] !== 'uploaded') {
                    try {

                        // Failed uploads leave behind a corrupted artifact that must be deleted
                        $response = $uploadClient->delete("/repos/{$owner}/{$repo}/releases/assets/{$asset['id']}", []);

                        // Currently the successful 204 will trigger an exception due to response formatting,
                        // but keeping this in case Github changes their API response
                        if ($response->getStatusCode() == 204) {
                            echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} has successfully been deleted.\n";
                        } else {
                            echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} was unable to be deleted.\n";
                        }
                    } catch (\GuzzleHttp\Exception\ClientException $e) {

                        // Currently expected to generate an exception every time, even for success
                        $response = $e->getResponse();
                        if ($response->getStatusCode() == 204) {
                            echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} has successfully been deleted.\n";
                        } else {
                            echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} was unable to be deleted.\n";
                            var_dump($e);
                        }
                    }
                }
            }
        }
    }

    if ($isSuccessful) {
        return $attempts;
    }

    return false;
}


<?php

class JsonCompiler
{
    use PhpFileLinterTrait;

    private static $tokensToReplace = [
        '(' => '__OPEN_PARENTHESIS__',
        ')' => '__CLOSE_PARENTHESIS__',
    ];

    /** @var string */
    private $path;

    public function __construct($path)
    {
        if (!file_exists($path)) {
            throw new InvalidArgumentException("No JSON file found at $path");
        }

        $this->path = realpath($path);
    }

    public function compile($outputPath)
    {
        $backup = $this->readPhpFile($outputPath);

        $this->writeFile($outputPath, $this->getTranspiledPhp());
        if (!$this->lintFile($outputPath)) {
            $this->writeFile($outputPath, $backup);
            trigger_error(
                "Unable to compile {$this->path} to valid PHP",
                E_USER_WARNING
            );
        }
    }

    private function getTranspiledPhp()
    {
        // Use var_export as a starting point.
        $code = var_export($this->getDecodedData(), true);

        // Convert "array()" to "[]".
        $code = str_replace('array (', '[', $code);
        $code = str_replace(')', ']', $code);

        // Removing trailing whitespace.
        $code = preg_replace('/\s+$/m', '', $code);

        // Move arrays to the same line.
        $code = preg_replace('/=>\s*\n\s*\[/', '=> [', $code);

        // Get rid of numbered array indexes.
        $code = preg_replace('/(\s*)(\d+ => )/', '$1', $code);

        // Make empty arrays span only a single line.
        $code = preg_replace('/=>\s*\[\n\s*\]/', '=> []', $code);

        // Minify the PHP file
        $code = preg_replace('/\s+/', ' ', str_replace("\n", '', $code));

        $originalPath = preg_replace('/^.+?(?=\/src\/data\/)/', '', $this->path);

        return <<<EOPHP
<?php
// This file was auto-generated from sdk-root$originalPath
return $code;

EOPHP;
    }

    private function getDecodedData()
    {
        return json_decode(
            strtr(file_get_contents($this->path), self::$tokensToReplace),
            true
        );
    }

    private function readPhpFile($path)
    {
        return file_exists($path) ?
            file_get_contents($path)
            : '<?php return [];';
    }

    private function writeFile($path, $contents)
    {
        return file_put_contents(
            $path,
            strtr($contents, array_flip(self::$tokensToReplace)),
            LOCK_EX
        );
    }
}


<?php
require __DIR__  . '/../vendor/autoload.php';

function generateDocblock(array $args)
{
    foreach ($args as $name => $value) {
        if (!isset($value['doc']) || !empty($value['internal'])) {
            continue;
        }

        $docs = $value['doc'];
        $modifiers = [];

        if (isset($value['valid'])) {
            $modifiers[] = implode('|', $value['valid']);
        }

        if (!empty($value['required'])) {
            $modifiers[] = 'required';
        }

        if (isset($value['default']) && !is_callable($value['default'])) {
            $modifiers[] = 'default=' . \Aws\describe_type($value['default']);
        }

        if ($modifiers) {
            $docs = '(' . implode(', ', $modifiers) . ') ' . $docs;
        }

        $docs = '* - ' . $name . ': ' . $docs;
        echo wordwrap($docs, 70, "\n*   ") . "\n";
    }
}

$clientName = isset($argv[1]) ? $argv[1] : 'Aws\AwsClient';
$args = call_user_func([$clientName, 'getArguments']);
ksort($args);

$type = isset($argv[2]) ? $argv[2] : 'docblock';

switch ($type) {
    case 'docblock':
        generateDocblock($args);
        break;
    default:
        die('Unknown type: ' . $type);
}