text:
steal('can/util', function (can) {
	// ##string.js
	// _Miscellaneous string utility functions._

	// Several of the methods in this plugin use code adapated from Prototype
	// Prototype JavaScript framework, version 1.6.0.1.
	// © 2005-2007 Sam Stephenson
	var strUndHash = /_|-/,
		strColons = /\=\=/,
		strWords = /([A-Z]+)([A-Z][a-z])/g,
		strLowUp = /([a-z\d])([A-Z])/g,
		strDash = /([a-z\d])([A-Z])/g,
		strReplacer = /\{([^\}]+)\}/g,
		strQuote = /"/g,
		strSingleQuote = /'/g,
		strHyphenMatch = /-+(.)?/g,
		strCamelMatch = /[a-z][A-Z]/g,
	// Returns the `prop` property from `obj`.
	// If `add` is true and `prop` doesn't exist in `obj`, create it as an
	// empty object.
		getNext = function (obj, prop, add) {
			var result = obj[prop];

			if (result === undefined && add === true) { result = obj[prop] = {} }
			return result
		},

	// Returns `true` if the object can have properties (no `null`s).
		isContainer = function (current) {
			return (/^f|^o/).test(typeof current);
		},
		convertBadValues = function(content) {
			// Convert bad values into empty strings
			var isInvalid = content === null || content === undefined || (isNaN(content) && ("" + content === 'NaN'));
			return ( "" + ( isInvalid ? '' : content ) )
		};

	can.extend(can, {
		// Escapes strings for HTML.
		esc: function (content) {
			return convertBadValues(content)
				.replace(/&/g, '&')
				.replace(/</g, '<')
				.replace(/>/g, '>')
				.replace(strQuote, '"')
				.replace(strSingleQuote, "'");
		},

		getObject: function (name, roots, add) {

			// The parts of the name we are looking up
			// `['App','Models','Recipe']`
			var parts = name ? name.split('.') : [],
				length = parts.length,
				current,
				r = 0,
				i, container, rootsLength;

			// Make sure roots is an `array`.
			roots = can.isArray(roots) ? roots : [roots || window];

			rootsLength = roots.length

			if (!length) {
				return roots[0];
			}

			// For each root, mark it as current.
			for (r; r < rootsLength; r++) {
				current = roots[r];
				container = undefined;

				// Walk current to the 2nd to last object or until there
				// is not a container.
				for (i = 0; i < length && isContainer(current); i++) {
					container = current;
					current = getNext(container, parts[i]);
				}

				// If we found property break cycle
				if (container !== undefined && current !== undefined) {
					break
				}
			}

			// Remove property from found container
			if (add === false && current !== undefined) {
				delete container[parts[i - 1]]
			}

			// When adding property add it to the first root
			if (add === true && current === undefined) {
				current = roots[0]

				for (i = 0; i < length && isContainer(current); i++) {
					current = getNext(current, parts[i], true);
				}
			}

			return current;
		},
		// Capitalizes a string.
		/**
		 * @function can.capitalize
		 * @parent can.util
		 * @description Capitalize the first letter of a string.
		 * @signature `can.capitalize(str)`
		 * @param {String} str The string to capitalize.
		 * @return {String} The string with the first letter capitalized.
		 *
		 *        can.capitalize('candy is fun!'); //-> Returns: 'Candy is fun!'
		 *
		 */
		capitalize: function (s, cache) {
			// Used to make newId.
			return s.charAt(0).toUpperCase() + s.slice(1);
		},

		/**
		 * @function can.camelize
		 * @parent can.util
		 * @description Takes a hyphenated string and converts it to a camelCase string..
		 * @signature `can.camelize(str)`
		 * @param {String} str The string to camelCase.
		 * @return {String} The camelCased string.
		 *
		 *        can.camelize('array-count'); //-> 'arrayCount'
		 *
		 */
		camelize: function(str){
			return convertBadValues(str).replace(strHyphenMatch, function(match, chr){
				return chr ? chr.toUpperCase() : ''
			})
		},

		/**
		 * @function can.hyphenate
		 * @parent can.util
		 * @description Hypenates a camelCase string, and makes it lower case.
		 * @signature `can.capitalize(str)`
		 * @param {String} str The camelCase string to hyphenate.
		 * @return {String} The hyphenated string.
		 *
		 *        can.hyphenate('fooBarBaz'); //-> 'foo-bar-baz'
		 *
		 */
		hyphenate: function(str) {
			return convertBadValues(str).replace(strCamelMatch, function(str, offset) {
				return str.charAt(0) + '-' + str.charAt(1).toLowerCase();
			});
		},

		// Underscores a string.
		underscore: function (s) {
			return s
				.replace(strColons, '/')
				.replace(strWords, '$1_$2')
				.replace(strLowUp, '$1_$2')
				.replace(strDash, '_')
				.toLowerCase();
		},
		// Micro-templating.
		/**
		 */
		sub: function (str, data, remove) {
			var obs = [];

			str = str || '';

			obs.push(str.replace(strReplacer, function (whole, inside) {

				// Convert inside to type.
				var ob = can.getObject(inside, data, remove === true ? false : undefined);

				if (ob === undefined || ob === null) {
					obs = null;
					return "";
				}

				// If a container, push into objs (which will return objects found).
				if (isContainer(ob) && obs) {
					obs.push(ob);
					return "";
				}

				return "" + ob;
			}));

			return obs === null ? obs : (obs.length <= 1 ? obs[0] : obs);
		},

		// These regex's are used throughout the rest of can, so let's make
		// them available.
		replacer: strReplacer,
		undHash: strUndHash
	});
	return can;
});


text:
<?php

namespace Sonata\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * User
 *
 * @ORM\Table(name="users")
 * @ORM\Entity(repositoryClass="Sonata\UserBundle\Repository\UserRepository")
 */
class User extends BaseUser {

    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Sonata\UserBundle\Entity\Role", inversedBy="users")
     * @ORM\JoinTable(name="user_roles",
     *                  joinColumns={@ORM\JoinColumn(name="userID", referencedColumnName="id")},
     *                  inverseJoinColumns={@ORM\JoinColumn(name="roleID", referencedColumnName="id")})
     */
    private $userRoles;

    /**
     * @ORM\Column(name="firstName", type="string", length=75)
     */
    private $firstName;

    /**
     * @ORM\Column(name="lastName", type="string", length=75)
     */
    private $lastName;

    /**
     * @ORM\Column(name="primaryDoctor", type="string", length=128, nullable=true)
     */
    private $primaryDoctor;

    /**
     * @ORM\OneToOne(targetEntity="Sonata\UserBundle\Entity\Address", inversedBy="user", fetch="EAGER")
     * @ORM\JoinColumn(name="address_id", referencedColumnName="id", nullable=true, unique=true)
     */
    private $address;

    /**
     * @ORM\OneToOne(targetEntity="Sonata\UserBundle\Entity\Insurance", inversedBy="patient", fetch="EAGER")
     * @ORM\JoinColumn(name="insurance_id", referencedColumnName="id", nullable=true, unique=true)
     */
    private $insuranceInfo;

    /**
     * @ORM\OneToMany(targetEntity="Sonata\HealthBundle\Entity\Prescription", mappedBy="patient")
     */
    private $prescriptions;

    /**
     * @ORM\OneToMany(targetEntity="Sonata\HealthBundle\Entity\Allergy", mappedBy="patient")
     */
    private $allergies;

    /**
     * @ORM\OneToMany(targetEntity="Sonata\AppointmentBundle\Entity\Appointment", mappedBy="patient")
     */
    private $appointments;

    /**
     * @ORM\Column(type="datetime")
     * @var DateTime $createdOn
     */
    protected $createdOn;

    /**
     *
     * @param type $role
     * @return \Sonata\UserBundle\Entity\User
     */
    public function addRole($role) {
        //make sure user doesn't already have the role
        if (!$this->hasRole($role)) {
            $this->userRoles->add($role);
        }

        return $this;
    }

    /**
     *
     * @return type
     */
    public function getRoles() {
        return $this->userRoles->toArray();
    }

    /**
     *
     * @param type $role
     * @return boolean
     */
    public function hasRole($role) {
        foreach ($this->userRoles as $userRole) {
            if ($userRole == $role) {
                return true;
            }
        }

        return false;
    }

    public function hasRoleByName($role) {
        foreach ($this->userRoles as $userRole) {
            if (0 === strcmp($userRole->getName(), $role)) {
                return true;
            }
        }

        return false;
    }

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

    public function __construct() {
        parent::__construct();
        $this->createdOn = new \DateTime('now');
        $this->userRoles = new ArrayCollection();
        $this->appointmentInfo = new ArrayCollection();
        $this->allergies = new ArrayCollection();
        $this->prescriptions = new ArrayCollection();
    }

    public function getUserRoles() {
        return $this->userRoles;
    }

    public function setUserRoles($userRoles) {
        $this->userRoles = $userRoles;
        return $this;
    }

    public function getFirstName() {
        return $this->firstName;
    }

    public function setFirstName($firstName) {
        $this->firstName = $firstName;
        return $this;
    }

    public function getLastName() {
        return $this->lastName;
    }

    public function setLastName($lastName) {
        $this->lastName = $lastName;
        return $this;
    }
    public function getPrimaryDoctor() {
        return $this->primaryDoctor;
    }

    public function setPrimaryDoctor($primaryDoctor) {
        $this->primaryDoctor = $primaryDoctor;
        return $this;
    }

    public function getAddress() {
        return $this->address;
    }

    public function setAddress($address) {
        $this->address = $address;
        return $this;
    }

    public function getInsuranceInfo() {
        return $this->insuranceInfo;
    }

    public function setInsuranceInfo($insuranceInfo) {
        $this->insuranceInfo = $insuranceInfo;
        return $this;
    }

    public function getPrescriptions() {
        return $this->prescriptions;
    }

    public function setPrescriptions($prescriptions) {
        $this->prescriptions = $prescriptions;
        return $this;
    }

    public function getAppointments() {
        return $this->appointments;
    }

    public function setAppointments($appointments) {
        $this->appointments = $appointments;
        return $this;
    }

    public function getAllergies() {
        return $this->allergies;
    }

    public function setAllergies($allergies) {
        $this->allergies = $allergies;
        return $this;
    }

    /**
     * Override setEmail Method so that Username
     * is set to Email.
     *
     * @param type $email
     * @return \Sonata\UserBundle\Entity\User
     */
    public function setEmail($email) {
        $this->email = $email;
        $this->setUsername($email);
        return $this;
    }

    public function getCreatedOn() {
        return $this->createdOn;
    }

    public function getName() {
        return $this->getFirstName().' '.$this->getLastName();
    }

    public function __toString() {
        return $this->getFirstName() . ' ' . $this->getLastName();
    }

}

redpajama_set_name: RedPajamaGithub


text:
*By [Jack Dougherty](../../introduction/who.md), last updated March 16, 2016*

** TO DO - move this tutorial to separate section, will no longer be supported **

A thematic polygon map (also called a choropleth map) displays a colored pattern on each region to express its numerical value. In the interactive version embedded below, click on any region to view its info window with additional data.

*Median Household Income in Connecticut Towns, 2009-14, American Community Survey 5-Year Estimates, US Census/Social Explorer.*

To create a thematic polygon map, we need to merge two types of files:
- **data table:** the numerical values for each polygon
- **geographic borders:** a series of points that draws each polygon

To merge the data table and geographic borders, the two files **must share a common column** of information, usually the name or ID of each polygon. In the tutorial below, we will merge together:
- Connecticut town demographic data
- Connecticut town geographic borders

![](GFT-merge-data-and-borders.png)

##Google Fusion Tables
This free and easy-to-learn tool looks similar to Google Sheets, but is more powerful than a spreadsheet tool. Google Fusion Tables (GFT) allows you to merge or "fuse together" two or more data tables -- including geographic border tables -- which allows you to visualize spreadsheet data on a map. GFT requires a [free Google Drive account](http://drive.google.com) (use a regular Google username; avoid limited-access Google Apps for Education accounts). For general information, see Google documentation "About Fusion Tables" and also the GFT Help Page.

###Strengths of GFT
- Free, easy-to-learn, and familiar interface for Google Drive users
- Operates entirely within the browser, across all platforms
- Upload geographic border files to create nearly any polygon map
- Publish and embed the interactive map on your own website
- Flexible GFT point and polygon maps can be inserted into [Searchable Map Template, in this book](../../fusion/searchable-point-map/index.html)

###Limitations of GFT
- Google Fusion Tables has been listed as *experimental* for several years
- Legend displays only numeric values. To display text labels in the legend, see https://github.com/JackDougherty/FusionTable-Map-custom-legend **TO DO**: Create chapter in modify section
- Not ideal for very large geography files (such as census tracts for several states)

##Install Google Fusion Tables

Sign into Google Drive (http://drive.google.com) and install GFT by clicking on New > More > Connect More Apps.

![](GFT-connect-more-apps.png)

Search for "fusion" to find the free app, and click Connect to add it to your Google Drive.

![](GFT-connect-fusion.png)

In Google Drive, click New > More > Google Fusion Tables to start up.

![](GFT-new-more-gft.png)

##Download and Explore Sample Data

To follow this thematic polygon map tutorial, download two sample data files, and open them to learn about their structure:
- [Connecticut town demographic data in CSV format](CT-town-data-2014-ACS-5YearEstimates.csv). Includes columns of economic and racial data for each municipal town in Connecticut, drawn from the American Community Survey 5-year estimates for 2009-14, from the US Census/Social Explorer. Open and explore this file with any spreadsheet tool.
- [Connecticut town geographic borders in KML format](CT-town-borders-2010-MAGIC.kml). Includes many series of coordinate points to draw the polygon for each municipal town, similar to "connect the dots," from MAGIC UConn Libraries. Open and explore this file by with the http://geojson.io tool.

Since both of these sample data files share a common column -- the Connecticut town names, spelled the same way -- we can merge them in the next few steps.

##Upload Data into Google Fusion Tables

In Google Drive, click New > Google Fusion Tables. Import each sample file, one at a time, by selecting "From this computer" and navigating to the file. Click Next to confirm the data upload. Click Next to add any source information (such as "American Community Survey 2009-14" and "MAGIC UConn Libraries"), then Finish.

![](GFT-import-new-table.png)

When you upload the CT town data CSV table, Google Fusion Tables displays yellow highlights because it is attempting to recognize and geocode the town names as locations, and place them as points on a map. But geocoding is unnecessary here, because our CT town borders KML file already includes the exact geography for each town.

To turn off the yellow highlighting, in the town name column drop-down menu, select Change, and switch the column type description from "Location" to "Text" as shown below.

![](GFT-change-location.png)

##The Fusion: Merge data with borders

In your web browser, you should have two GFT tabs open: the CT town data and the CT town borders.

Start with the CT town data tab. Select File > Merge, and click on the listing for the  border file, which should be second in the list.

![](GFT-merge-table.png)

On the next screen, select the matching column types to merge. In this tutorial, the CT town borders should appear on the right-hand side, and you should select the "Name" column in the drop-down header. The goal is to **match the correct column type**, even if the list of town names does not appear in the same order.

![](GFT-merge-types.png)

Click the Next button, Merge all columns, and View the newly merged table.

![](GFT-merge-table-created.png)

##Create and Style the Thematic Map with Info Windows

Open your newly merged Google Fusion Table, which should include columns from CT town data and CT town borders. Select the Map tab, and an all-red polygon map will appear by default. Click on Change Feature Styles.

![](GFT-map-change-feature-styles.png)

To style your thematic map, select Polygons > Fill Color, and experiment with Buckets and Gradients to adjust the range and colors. **Hint:** if you map a column of percentages (rather than raw numbers), choose an appropriate range (such as 0 - 1.0). a percentage column (rather than raw numbers), adjust the range (from 0-100 to an appropriate range, such as 0 - 1.0).

![](GFT-polygons-fill-color.png)

Select Automatic Legend, and check the box for its display position, insert a title, and link to the source data.

![](GFT-automatic-legend.png)

To modify how data appears in the map info windows, go to Map > Feature Map > Change info window.

![](GFT-change-info-window.png)

In the Automatic tab, select data to be displayed. Or click on the Custom tab and learn how to edit code for info windows. **TO DO** link to learn more.

![](GFT-info-window-automatic.png)

View the map to check appearance of your info windows.

![](GFT-info-window-sample.png)

##Share, Publish, and Embed the Polygon Map

In the GFT Merged Table, click the Share button in upper-right corner to change the visibility settings of the default (Private) to Anyone with the Link, or Public:

![](GFT-share-settings.png)

Modify the map zoom level and position, then select Map tab > Publish.

![](GFT-map-publish.png)

Google Fusion Tables displays an iframe embed code. Modify the map width and height to fit your destination website. Copy the long string of code code from the "Paste HTML to embed" field.

![](GFTPoly14.png)

In the Trinity self-hosted WordPress site for my classes, I have already activated a special plugin ("iframe") to correctly process the html embedded map code for students with regular authoring privileges. (If you have administrative privileges or your own self-hosted WordPress.org site, this step may not be necessary. Currently, iframes do NOT work in most WordPress.com sites.)

Go to WordPress and create a new post. In the editor, switch from the Visual to the Text tab, which allows users to insert and modify html code. Paste the long string that you copied from the step above. Add square brackets at beginning and end, and edit a few characters to match this format (called a "shortcode"), then publish to view your post.

**TO DO LIST**

Advanced tip: See Mark Monmonier, How to Lie with Maps, Second Edition (University of Chicago Press, 1996), pp. 39-42 excerpt on how same data can be spatially represented in very different ways, by modifying thematic map categories and cut-offs, in the Buckets or Gradients section of GFT. See also ColorBrewer for advice on selecting appropriate map colors and categories. (*To do: look for ColorBrewer with HTML color output.)

Also, you can download census tract spreadsheet data to merge with it from Census.gov or SocialExplorer.com (export in CSV format, and multiply 6-digit tract number by 0.01 to add decimals to match).

These sample boundary files in KML (Keyhole Markup Language) format initially were downloaded from MAGIC in compressed KMZ format, which is not compatible with Google Fusion Tables. To learn how to modify files with free tools, see this tutorial to convert from KMZ to KML in Google Earth and filter results in Google Fusion Tables.

for the MAGIC border files above, note that I also removed the County Subdivisions that did not correspond to any spreadsheet

decide where to put commonly used geographic files: here or in Find CT chapter?

Advanced tip: To limit the data that appears in your map, go to the Merged Table and select Filter to exclude selected rows, as shown below:

When downloading census data for county subdivisions (municipal governments; aka towns in CT), long names may not match your data. for CT use my name converter table with VLookup. Beware that Windsor and Windsor Locks are flipped in alphabetically sorted long-name census lists versus short name lists.

Redo video screencast
https://www.youtube.com/watch?v=ReUAlZsJxP4

{% footer %}
{% endfooter %}


text:
"""
    webapp2_extras.appengine.sessions_ndb
    =====================================

    Extended sessions stored in datastore using the ndb library.

    :copyright: 2011 by tipfy.org.
    :license: Apache Sotware License, see LICENSE for details.
"""
from __future__ import absolute_import

import pickle

from google.appengine.api import datastore_errors
from google.appengine.api import memcache

from ndb import model

from webapp2_extras import sessions

class PickledProperty(model.BlobProperty):
    _type = None
    _indexed = False

    def __init__(self, _type, *args, **kwargs):
        super(PickledProperty, self).__init__(*args, **kwargs)
        self._type = _type

    def _validate_type(self, value):
        if not isinstance(value, self._type):
            raise datastore_errors.BadValueError(
                'Expected %r, got %r' % (self._type, value))
        return value

    def _validate(self, value):
        return self._validate_type(value)

    def _db_set_value(self, v, p, value):
        value = self._validate_type(value)
        super(PickledProperty, self)._db_set_value(v, p, pickle.dumps(value))

    def _db_get_value(self, v, p):
        if not v.has_stringvalue():
            return None

        return pickle.loads(v.stringvalue())

class Session(model.Model):
    """A model to store session data."""

    #: Save time.
    updated = model.DateTimeProperty(auto_now=True)
    #: Session data, pickled.
    data = PickledProperty(dict)

    @classmethod
    def get_by_sid(cls, sid):
        """Returns a ``Session`` instance by session id.

        :param sid:
            A session id.
        :returns:
            An existing ``Session`` entity.
        """
        data = memcache.get(sid)
        if not data:
            session = model.Key(cls, sid).get()
            if session:
                data = session.data
                memcache.set(sid, data)

        return data

    def _put(self):
        """Saves the session and updates the memcache entry."""
        memcache.set(self._key.id(), self.data)
        super(Session, self).put()

class DatastoreSessionFactory(sessions.CustomBackendSessionFactory):
    """A session factory that stores data serialized in datastore.

    To use datastore sessions, pass this class as the `factory` keyword to
    :meth:`webapp2_extras.sessions.SessionStore.get_session`::

        from webapp2_extras import sessions_ndb

        # [...]

        session = self.session_store.get_session(
            name='db_session', factory=sessions_ndb.DatastoreSessionFactory)

    See in :meth:`webapp2_extras.sessions.SessionStore` an example of how to
    make sessions available in a :class:`webapp2.RequestHandler`.
    """

    #: The session model class.
    session_model = Session

    def _get_by_sid(self, sid):
        """Returns a session given a session id."""
        if self._is_valid_sid(sid):
            data = self.session_model.get_by_sid(sid)
            if data is not None:
                self.sid = sid
                return sessions.SessionDict(self, data=data)

        self.sid = self._get_new_sid()
        return sessions.SessionDict(self, new=True)

    def save_session(self, response):
        if self.session is None or not self.session.modified:
            return

        self.session_model(id=self.sid, data=dict(self.session))._put()
        self.session_store.save_secure_cookie(
            response, self.name, {'_sid': self.sid}, **self.session_args)


text:
// Copyright (C) 2017 History in Paderborn App - Universität Paderborn
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Acr.UserDialogs;
using PaderbornUniversity.SILab.Hip.Mobile.Shared.BusinessLayer.ContentApiFetchers.Contracts;
using PaderbornUniversity.SILab.Hip.Mobile.Shared.BusinessLayer.DtoToModelConverters;
using PaderbornUniversity.SILab.Hip.Mobile.Shared.BusinessLayer.Models;
using PaderbornUniversity.SILab.Hip.Mobile.Shared.Common;
using PaderbornUniversity.SILab.Hip.Mobile.Shared.Common.Contracts;
using PaderbornUniversity.SILab.Hip.Mobile.Shared.Helpers;
using PaderbornUniversity.SILab.Hip.Mobile.UI.Resources;
using PaderbornUniversity.SILab.Hip.Mobile.UI.ViewModels.Views;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Input;
using Xamarin.Forms;

namespace PaderbornUniversity.SILab.Hip.Mobile.UI.ViewModels.Pages
{
    public class ExhibitRouteDownloadPageViewModel : NavigationViewModel, IProgressListener
    {
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly IDownloadableListItemViewModel downloadableListItemViewModel;
        private double loadingProgress;
        private double maximumProgress;
        private double currentProgress;

        public ExhibitRouteDownloadPageViewModel(IDownloadable downloadable, IDownloadableListItemViewModel downloadableListItemViewModel)
        {
            Downloadable = downloadable;
            this.downloadableListItemViewModel = downloadableListItemViewModel;
            SetImage();

            StartDownload = new Command(DownloadData);
            CancelCommand = new Command(CancelDownload);
        }

        public IDownloadable Downloadable { get; }

        public ImageSource Image { get; private set; }

        public ICommand StartDownload { get; }

        public ICommand CancelCommand { get; }

        // Remove the description label if no description exists to center the other labels precisely
        public bool DescriptionExists => !string.IsNullOrEmpty(Downloadable.Description);

        public int DownloadableIdForRestApi => Downloadable.IdForRestApi;

        public double LoadingProgress
        {
            get => loadingProgress;
            set => SetProperty(ref loadingProgress, value);
        }

        private async void SetImage()
        {
            var imageData = await Downloadable.Image.GetDataAsync();
            Image = imageData != null ? ImageSource.FromStream(() => new MemoryStream(imageData)) : ImageSource.FromStream(() => new MemoryStream(BackupData.BackupImageData));
        }

        private void CancelDownload()
        {
            cancellationTokenSource?.Cancel();
            CloseDownloadPage();
        }

        private void OpenDetailsView()
        {
            downloadableListItemViewModel.OpenDetailsView(Downloadable.Id);
        }

        private void CloseDownloadPage()
        {
            downloadableListItemViewModel.CloseDownloadPage();
        }

        private async void DownloadData()
        {
            string messageToShow = null;
            string titleToShow = null;
            var isDownloadAllowed = true;
            var networkAccessStatus = IoCManager.Resolve<INetworkAccessChecker>().GetNetworkAccessStatus();

            var fullDownloadableDataFetcher = (Downloadable.Type == DownloadableType.Exhibit)
                ? IoCManager.Resolve<IFullExhibitDataFetcher>()
                : IoCManager.Resolve<IFullRouteDataFetcher>() as IFullDownloadableDataFetcher;

            if (networkAccessStatus != NetworkAccessStatus.NoAccess)
            {
                if (networkAccessStatus == NetworkAccessStatus.MobileAccess && Settings.WifiOnly)
                {
                    isDownloadAllowed = await UserDialogs.Instance.ConfirmAsync(new ConfirmConfig()
                    {
                        Title = Strings.ExhibitRouteDownloadPageViewModel_Wifi_Only_Title,
                        Message = Strings.ExhibitRouteDownloadPageViewModel_Wifi_Only_Message,
                        OkText = Strings.Yes,
                        CancelText = Strings.No
                    });
                }

                if (isDownloadAllowed)
                {
                    try
                    {
                        await fullDownloadableDataFetcher.FetchFullDownloadableDataIntoDatabase(Downloadable.Id, DownloadableIdForRestApi, cancellationTokenSource.Token, this);
                    }
                    catch (Exception e)
                    {
                        if (!cancellationTokenSource.IsCancellationRequested)
                        {
                            titleToShow = Strings.LoadingPageViewModel_BaseData_DownloadFailed_Title;
                            messageToShow = Strings.LoadingPageViewModel_BaseData_DownloadFailed_Text;
                            Debug.WriteLine(e.Message);
                            Debug.WriteLine(e.StackTrace);
                        }
                    }
                }
                else
                {
                    CloseDownloadPage();
                }
            }
            else
            {
                titleToShow = Strings.LoadingPageViewModel_BaseData_DownloadFailed_Title;
                messageToShow = Strings.LoadingPageViewModel_BaseData_DownloadFailed_Text;
                if (networkAccessStatus == NetworkAccessStatus.MobileAccess)
                {
                    messageToShow += Environment.NewLine + Strings.LoadingPageViewModel_BaseData_DownloadViaMobile;
                }
            }

            if (messageToShow != null)
            {
                await Navigation.DisplayAlert(titleToShow, messageToShow, Strings.Ok);
                CloseDownloadPage();
                return;
            }

            if (!cancellationTokenSource.IsCancellationRequested && isDownloadAllowed)
            {
                await downloadableListItemViewModel.SetDetailsAvailable(true);

                if (downloadableListItemViewModel.GetType() != typeof(AppetizerPageViewModel))
                    OpenDetailsView();
                else
                    CloseDownloadPage();
            }
            IoCManager.Resolve<IDbChangedHandler>().NotifyAll();
        }

        public override void OnAppearing()
        {
            base.OnAppearing();
            StartDownload.Execute(null);
        }

        public override void OnDisappearing()
        {
            base.OnDisappearing();
            cancellationTokenSource?.Cancel();
        }

        public void ProgressOneStep()
        {
            currentProgress++;
            LoadingProgress = currentProgress / maximumProgress;
        }

        public void SetMaxProgress(double maxProgress)
        {
            maximumProgress = maxProgress;
        }

        public void UpdateProgress(double newProgress, double maxProgress)
        {
            LoadingProgress = newProgress / maxProgress;
        }
    }
}


text:
class RatesController < ShopifyApp::AuthenticatedController
  def index
    @rates = shop.rates.includes(:conditions, :product_specific_prices).order(:name)

    if @rates.empty?
      render('blank')
    else
      render('index')
    end
  end

  def show
    @rate = shop.rates.find(params[:id])
  end

  def new
    @rate = shop.rates.build
  end

  def update
    rate = shop.rates.find(params[:id])

    if rate.update_attributes(rate_params)
      redirect_to(rates_path)
    else
      redirect_to(rate_path(rate))
    end
  end

  def destroy
    rate = shop.rates.find(params[:id])

    if rate.destroy
      redirect_to(rates_path)
    else
      redirect_to(rate_path(rate))
    end
  end

  def create
    @rate = shop.rates.build(rate_params)

    if @rate.save
      redirect_to(rates_path)
    else
      render('new')
    end
  end

  private

  def rate_params
    params.require(:rate).permit(
      :name,
      :price,
      :price_weight_modifier,
      :price_weight_modifier_starter,
      :price_total_modifier,
      :price_total_modifier_starter,
      :description,
      :min_price,
      :max_price,
      :min_grams,
      :max_grams,
      :code,
      :notes,
      :disabled,
      conditions_attributes: condition_params,
      product_specific_prices_attributes: product_specific_price_params
    )
  end

  def condition_params
    [:id, :field, :verb, :value, :all_items_must_match, :_destroy]
  end

  def product_specific_price_params
    [:id, :field, :verb, :value, :price, :after_n_items, :_destroy]
  end
end


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>What Input?</title>

    <link
      rel="stylesheet"
      href="styles/index.css">

What Input?

        A global utility for tracking the current input method (mouse, keyboard or touch), as well as the current intent (mouse or keyboard).

        Tab, click or tap the links and form controls to see how What Input allows them to be styled differently.

Cras justo odio

Dapibus ac facilisis in

Morbi leo risus

Porta ac consectetur ac

Vestibulum at eros

          <form class="well-column">

              Example Text Input
              <input
                type="email"
                id="exampleInput"
                name="exampleInput">

              Selection
              <select id="exampleSelect" name="exampleSelect">
                <option value="" disabled selected>Select an option</option>
                <option>Option 1</option>
                <option>Option 2</option>
                <option>Option 3</option>
              </select>

              Example Textarea
              <textarea
                id="exampleTextarea"
                name="exampleTextarea"></textarea>

                <input type="checkbox"> Check me out

              <button type="submit">Submit</button>

          </form>

          Made with ♥ at Viget.

          Check out the project on Github.


text:
module.exports = function(context) {

  var fs = context.requireCordovaModule('fs'),
    path = context.requireCordovaModule('path');

  var platformRoot = path.join(context.opts.projectRoot, 'platforms/android');

  var manifestFile = path.join(platformRoot, 'AndroidManifest.xml');

  if (fs.existsSync(manifestFile)) {

    fs.readFile(manifestFile, 'utf8', function (err,data) {
      if (err) {
        throw new Error('Unable to find AndroidManifest.xml: ' + err);
      }

      var appClass = 'com.blanktrack.alipush.MyApplication';

      if (data.indexOf(appClass) == -1) {

        var result = data.replace(/<application/g, '<application android:name="' + appClass + '"');

        fs.writeFile(manifestFile, result, 'utf8', function (err) {
          if (err) throw new Error('Unable to write into AndroidManifest.xml: ' + err);
        })
      }
    });
  }

};


text:
require "log4r"

module VagrantPlugins
  module PersistentStorage
    module Action
      class AttachStorage

        def initialize(app, env)
          @app = app
          @machine = env[:machine]
          @global_env = @machine.env
          @provider = env[:provider]
          @logger = Log4r::Logger.new('vagrant::persistentstorage::action::attachstorage')
        end

        def call(env)
          # skip if machine is not running and the action is destroy, halt or suspend
          return @app.call(env) if @machine.state.id != :running && [:destroy, :halt, :suspend].include?(env[:machine_action])
          # skip if machine is saved
          return @app.call(env) if @machine.state.id == :saved

          return @app.call(env) unless env[:machine].config.persistent_storage.enabled?
          @logger.info '** Attaching Persistent Storage **'

          env[:ui].info I18n.t("vagrant_persistent_storage.action.attach_storage")
          location = env[:machine].config.persistent_storage.location
          env[:machine].provider.driver.attach_storage(location)

          @app.call(env)

        end

      end
    end
  end
end


text:
//=======================================================================
// Copyright Baptiste Wicht 2013-2018.
// Distributed under the terms of the MIT License.
// (See accompanying file LICENSE or copy at
//  http://www.opensource.org/licenses/MIT)
//=======================================================================

#ifndef TIMER_H
#define TIMER_H

#include <types.hpp>

namespace timer {

/*!
 * \brief Install the system timer
 */
void install();

/*!
 * \brief Returns a up-counter in seconds
 */
uint64_t seconds();

/*!
 * \brief Returns a up-counter in milliseconds
 */
uint64_t milliseconds();

/*!
 * \brief Let the timer know of a new tick
 */
void tick();

/*!
 * \brief Return the frequency in Hz of the current timer system.
 */
uint64_t timer_frequency();

/*!
 * \brief Sets the frequency in Hz of the current timer system.
 */
void timer_frequency(uint64_t freq);

/*!
 * \brief Returns a up-counter based on the counter frequency
 */
uint64_t counter();

/*!
 * \brief Return the frequency in Hz of the current counter system.
 */
uint64_t counter_frequency();

/*!
 * \brief Sets the frequency in Hz of the current counter system.
 */
void counter_frequency(uint64_t freq);

/*!
 * \brief Sets the function to use to get the counter value;
 */
void counter_fun(uint64_t (*fun)());

} //end of timer namespace

#endif


text:
module Fog
  module Compute
    class Ovirt
      class Real
        def list_virtual_machines(filters = {})
          client.vms(filters).map {|ovirt_obj| ovirt_attrs ovirt_obj}
        end
      end
      class Mock
        def list_virtual_machines(filters = {})
          xml = read_xml 'vms.xml'
          Nokogiri::XML(xml).xpath('/vms/vm').map do |vm|
            ovirt_attrs OVIRT::VM::new(self, vm)
          end
        end
      end
    end
  end
end


text:
'use strict';

var debug = require('debug')('pipes:fittings');
var path = require('path');
var assert = require('assert');
var util = require('util');

module.exports = function createFitting(pipes, fittingDef) {

  assert(fittingDef.name, util.format('name is required on fitting: %j', fittingDef));

  var dir = pipes.config.fittingsDir || path.resolve(__dirname, '../fittings');

  var modulePath = path.resolve(dir, fittingDef.name);
  try {
    var module = require(modulePath);
    var fitting = module(fittingDef, pipes);
    debug('loaded system fitting %s from %s', fittingDef.name, dir);
    return fitting;
  } catch (err) {
    debug('no system fitting %s in %s', fittingDef.name, dir);
    throw err;
  }
};


text:
/**
 * angular-input-masks
 * Personalized input masks for AngularJS
 * @version v1.5.2
 * @link http://github.com/assisrafael/angular-input-masks
 * @license MIT
 */
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
module.exports = angular.module('ui.utils.masks', [
	require('./global/global-masks'),
	require('./br/br-masks')
]).name;

},{"./br/br-masks":3,"./global/global-masks":10}],2:[function(require,module,exports){
var StringMask = require('string-mask');
var maskFactory = require('mask-factory');

var boletoBancarioMask = new StringMask('00000.00000 00000.000000 00000.000000 0 00000000000000');

module.exports = maskFactory({
	validationErrorKey: 'brBoletoBancario',
	clearValue: function(rawValue) {
		return rawValue.replace(/[^0-9]/g, '').slice(0, 47);
	},
	format: function(cleanValue) {
		if (cleanValue.length === 0) {
			return cleanValue;
		}

		return boletoBancarioMask.apply(cleanValue).replace(/[^0-9]$/, '');
	},
	validate: function(value) {
		return value.length === 47;
	}
});

},{"mask-factory":"mask-factory","string-mask":undefined}],3:[function(require,module,exports){
var m = angular.module('ui.utils.masks.br', [
	require('../helpers'),
	require('./cpf-cnpj/cpf-cnpj')
])
.directive('uiBrBoletoBancarioMask', require('./boleto-bancario/boleto-bancario'))
.directive('uiBrCepMask', require('./cep/cep'))
.directive('uiBrIeMask', require('./inscricao-estadual/ie'))
.directive('uiNfeAccessKeyMask', require('./nfe/nfe'))
.directive('uiBrPhoneNumber', require('./phone/br-phone'));

module.exports = m.name;

},{"../helpers":16,"./boleto-bancario/boleto-bancario":2,"./cep/cep":4,"./cpf-cnpj/cpf-cnpj":5,"./inscricao-estadual/ie":6,"./nfe/nfe":7,"./phone/br-phone":8}],4:[function(require,module,exports){
var StringMask = require('string-mask');
var maskFactory = require('mask-factory');

var cepMask = new StringMask('00000-000');

module.exports = maskFactory({
	validationErrorKey: 'cep',
	clearValue: function(rawValue) {
		return rawValue.replace(/[^0-9]/g, '').slice(0, 8);
	},
	format: function(cleanValue) {
		return (cepMask.apply(cleanValue) || '').replace(/[^0-9]$/, '');
	},
	validate: function(value) {
		return value.length === 8;
	}
});

},{"mask-factory":"mask-factory","string-mask":undefined}],5:[function(require,module,exports){
var StringMask = require('string-mask');
var BrV = require('br-validations');
var maskFactory = require('mask-factory');

var cnpjPattern = new StringMask('00.000.000\/0000-00');
var cpfPattern = new StringMask('000.000.000-00');

function validateCPF(ctrl, value) {
	var valid = ctrl.$isEmpty(value) || BrV.cpf.validate(value);
	ctrl.$setValidity('cpf', valid);
	return value;
}

function validateCNPJ(ctrl, value) {
	var valid = ctrl.$isEmpty(value) || BrV.cnpj.validate(value);
	ctrl.$setValidity('cnpj', valid);
	return value;
}

function validateCPForCNPJ(ctrl, value) {
	if(!value || value.length <= 11) {
		validateCNPJ(ctrl, '');
		return validateCPF(ctrl, value);
	}else {
		validateCPF(ctrl, '');
		return validateCNPJ(ctrl, value);
	}
}

function removeNonDigits(value) {
	return value.replace(/[^\d]/g, '');
}

var uiBrCpfMask = maskFactory({
	validationErrorKey: 'cpf',
	clearValue: function(rawValue) {
		return rawValue.replace(/[^\d]/g, '').slice(0, 11);
	},
	format: function(cleanValue) {
		return (cpfPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
	},
	validate: function(value) {
		return BrV.cpf.validate(value);
	}
});

var uiBrCnpjMask = maskFactory({
	validationErrorKey: 'cnpj',
	clearValue: function(rawValue) {
		return rawValue.replace(/[^\d]/g, '').slice(0, 14);
	},
	format: function(cleanValue) {
		return (cnpjPattern.apply(cleanValue) || '').trim().replace(/[^0-9]$/, '');
	},
	validate: function(value) {
		return BrV.cnpj.validate(value);
	}
});

function uiBrCpfCnpjMask() {
	function applyCpfCnpjMask(value) {
		var formatedValue;
		if (value.length > 11) {
			formatedValue = cnpjPattern.apply(value);
		} else {
			formatedValue = cpfPattern.apply(value) || '';
		}
		return formatedValue.trim().replace(/[^0-9]$/, '');
	}

	return {
		restrict: 'A',
		require: 'ngModel',
		link: function (scope, element, attrs, ctrl) {
			function formatter(value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				return applyCpfCnpjMask(removeNonDigits(value));
			}

			function parser(value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				var formatedValue = applyCpfCnpjMask(removeNonDigits(value));
				var actualNumber = removeNonDigits(formatedValue);

				if (ctrl.$viewValue !== formatedValue) {
					ctrl.$setViewValue(formatedValue);
					ctrl.$render();
				}

				return actualNumber;
			}

			function validator(value) {
				return validateCPForCNPJ(ctrl, value);
			}

			ctrl.$formatters.push(formatter);
			ctrl.$parsers.push(parser);
			ctrl.$parsers.push(validator);
		}
	};
}

var m = angular.module('ui.utils.masks.br.cpfCnpj', [])
.directive('uiBrCpfMask', [uiBrCpfMask])
.directive('uiBrCnpjMask', [uiBrCnpjMask])
.directive('uiBrCpfcnpjMask', [uiBrCpfCnpjMask])
// deprecated: will be removed in the next major version
.directive('uiCpfMask', [uiBrCpfMask])
// deprecated: will be removed in the next major version
.directive('uiCnpjMask', [uiBrCnpjMask])
// deprecated: will be removed in the next major version
.directive('uiCpfcnpjMask', [uiBrCpfCnpjMask]);

module.exports = m.name;

},{"br-validations":undefined,"mask-factory":"mask-factory","string-mask":undefined}],6:[function(require,module,exports){
var StringMask = require('string-mask');
var BrV = require('br-validations');

function BrIeMaskDirective($parse) {
	var ieMasks = {
		'AC': [{mask: new StringMask('00.000.000/000-00')}],
		'AL': [{mask: new StringMask('000000000')}],
		'AM': [{mask: new StringMask('00.000.000-0')}],
		'AP': [{mask: new StringMask('000000000')}],
		'BA': [{chars: 8, mask: new StringMask('000000-00')},
			   {mask: new StringMask('0000000-00')}],
		'CE': [{mask: new StringMask('00000000-0')}],
		'DF': [{mask: new StringMask('00000000000-00')}],
		'ES': [{mask: new StringMask('00000000-0')}],
		'GO': [{mask: new StringMask('00.000.000-0')}],
		'MA': [{mask: new StringMask('000000000')}],
		'MG': [{mask: new StringMask('000.000.000/0000')}],
		'MS': [{mask: new StringMask('000000000')}],
		'MT': [{mask: new StringMask('0000000000-0')}],
		'PA': [{mask: new StringMask('00-000000-0')}],
		'PB': [{mask: new StringMask('00000000-0')}],
		'PE': [{chars: 9, mask: new StringMask('0000000-00')},
			   {mask: new StringMask('00.0.000.0000000-0')}],
		'PI': [{mask: new StringMask('000000000')}],
		'PR': [{mask: new StringMask('000.00000-00')}],
		'RJ': [{mask: new StringMask('00.000.00-0')}],
		'RN': [{chars: 9, mask: new StringMask('00.000.000-0')},
			   {mask: new StringMask('00.0.000.000-0')}],
		'RO': [{mask: new StringMask('0000000000000-0')}],
		'RR': [{mask: new StringMask('00000000-0')}],
		'RS': [{mask: new StringMask('000/0000000')}],
		'SC': [{mask: new StringMask('000.000.000')}],
		'SE': [{mask: new StringMask('00000000-0')}],
		'SP': [{mask: new StringMask('000.000.000.000')},
			   {mask: new StringMask('-00000000.0/000')}],
		'TO': [{mask: new StringMask('00000000000')}]
	};

	function clearValue (value) {
		if (!value) {
			return value;
		}

		return value.replace(/[^0-9]/g, '');
	}

	function getMask(uf, value) {
		if (!uf || !ieMasks[uf]) {
			return undefined;
		}

		if (uf === 'SP' && /^P/i.test(value)) {
			return ieMasks.SP[1].mask;
		}

		var masks = ieMasks[uf];
		var i = 0;
		while(masks[i].chars && masks[i].chars < clearValue(value).length && i < masks.length - 1) {
			i++;
		}

		return masks[i].mask;
	}

	function applyIEMask(value, uf) {
		var mask = getMask(uf, value);

		if(!mask) {
			return value;
		}

		var processed = mask.process(clearValue(value));
		var formatedValue = processed.result || '';
		formatedValue = formatedValue.trim().replace(/[^0-9]$/, '');

		if (uf === 'SP' && /^p/i.test(value)) {
			return 'P' + formatedValue;
		}

		return formatedValue;
	}

	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, element, attrs, ctrl) {
			var state = $parse(attrs.uiBrIeMask)(scope) || '';
			state = state.toUpperCase();

			function formatter(value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				return applyIEMask(value, state);
			}

			function parser(value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				var formatedValue = applyIEMask(value, state);
				var actualValue = clearValue(formatedValue);

				if (ctrl.$viewValue !== formatedValue) {
					ctrl.$setViewValue(formatedValue);
					ctrl.$render();
				}

				if (state && state.toUpperCase() === 'SP' && /^p/i.test(value)) {
					return 'P' + actualValue;
				}

				return actualValue;
			}

			function validator(value) {
				var isValid = ctrl.$isEmpty(value) || BrV.ie(state).validate(value);
				ctrl.$setValidity('ie', isValid);

				return value;
			}

			ctrl.$formatters.push(formatter);
			ctrl.$formatters.push(validator);
			ctrl.$parsers.push(parser);
			ctrl.$parsers.push(validator);

			scope.$watch(attrs.uiBrIeMask, function(newState) {
				state = newState || '';
				state = state.toUpperCase();

				parser(ctrl.$viewValue);
				validator(ctrl.$viewValue);
			});
		}
	};
}
BrIeMaskDirective.$inject = ['$parse'];

module.exports = BrIeMaskDirective;

},{"br-validations":undefined,"string-mask":undefined}],7:[function(require,module,exports){
var StringMask = require('string-mask');
var maskFactory = require('mask-factory');

var nfeAccessKeyMask = new StringMask('0000 0000 0000 0000 0000' +
	' 0000 0000 0000 0000 0000 0000');

module.exports = maskFactory({
	validationErrorKey: 'nfeAccessKey',
	clearValue: function(rawValue) {
		return rawValue.replace(/[^0-9]/g, '').slice(0, 44);
	},
	format: function(cleanValue) {
		return (nfeAccessKeyMask.apply(cleanValue) || '').replace(/[^0-9]$/, '');
	},
	validate: function(value) {
		return value.length === 44;
	}
});

},{"mask-factory":"mask-factory","string-mask":undefined}],8:[function(require,module,exports){
var StringMask = require('string-mask');
var maskFactory = require('mask-factory');

/**
 * FIXME: all numbers will have 9 digits after 2016.
 * see http://portal.embratel.com.br/embratel/9-digito/
 */
var phoneMask8D = new StringMask('(00) 0000-0000'),
	phoneMask9D = new StringMask('(00) 00000-0000');

module.exports = maskFactory({
	validationErrorKey: 'brPhoneNumber',
	clearValue: function(rawValue) {
		return rawValue.toString().replace(/[^0-9]/g, '').slice(0, 11);
	},
	format: function(cleanValue) {
		var formatedValue;

		if(cleanValue.length < 11){
			formatedValue = phoneMask8D.apply(cleanValue) || '';
		}else{
			formatedValue = phoneMask9D.apply(cleanValue);
		}

		return formatedValue.trim().replace(/[^0-9]$/, '');
	},
	getModelValue: function(formattedValue, originalModelType) {
		var cleanValue = this.clearValue(formattedValue);

		return originalModelType === 'number' ? parseInt(cleanValue) : cleanValue;
	},
	validate: function(value) {
		var valueLength = value && value.toString().length;
		return valueLength === 10 || valueLength === 11;
	}
});

},{"mask-factory":"mask-factory","string-mask":undefined}],9:[function(require,module,exports){
var moment = require('moment');
var StringMask = require('string-mask');

function DateMaskDirective($locale) {
	var dateFormatMapByLocale = {
		'pt-br': 'DD/MM/YYYY',
	};

	var dateFormat = dateFormatMapByLocale[$locale.id] || 'YYYY-MM-DD';

	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, element, attrs, ctrl) {
			var dateMask = new StringMask(dateFormat.replace(/[YMD]/g,'0'));

			function applyMask (value) {
				var cleanValue = value.replace(/[^0-9]/g, '');
				var formatedValue = dateMask.process(cleanValue).result || '';

				return formatedValue.trim().replace(/[^0-9]$/, '');
			}

			function formatter (value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var formatedValue = applyMask(moment(value).format(dateFormat));
				validator(formatedValue);
				return formatedValue;
			}

			function parser(value) {
				if(ctrl.$isEmpty(value)) {
					validator(value);
					return value;
				}

				var formatedValue = applyMask(value);

				if (ctrl.$viewValue !== formatedValue) {
					ctrl.$setViewValue(formatedValue);
					ctrl.$render();
				}
				validator(formatedValue);

				var modelValue = moment(formatedValue, dateFormat);
				return modelValue.toDate();
			}

			function validator(value) {
				var isValid = moment(value, dateFormat).isValid() &&
					value.length === dateFormat.length;
				ctrl.$setValidity('date', ctrl.$isEmpty(value) || isValid);
			}

			ctrl.$formatters.push(formatter);
			ctrl.$parsers.push(parser);
		}
	};
}
DateMaskDirective.$inject = ['$locale'];

module.exports = DateMaskDirective;

},{"moment":undefined,"string-mask":undefined}],10:[function(require,module,exports){
var m = angular.module('ui.utils.masks.global', [
	require('../helpers'),
])
.directive('uiDateMask', require('./date/date'))
.directive('uiMoneyMask', require('./money/money'))
.directive('uiNumberMask', require('./number/number'))
.directive('uiPercentageMask', require('./percentage/percentage'))
.directive('uiScientificNotationMask', require('./scientific-notation/scientific-notation'))
.directive('uiTimeMask', require('./time/time'));

module.exports = m.name;

},{"../helpers":16,"./date/date":9,"./money/money":11,"./number/number":12,"./percentage/percentage":13,"./scientific-notation/scientific-notation":14,"./time/time":15}],11:[function(require,module,exports){
var StringMask = require('string-mask');

function MoneyMaskDirective($locale, $parse, PreFormatters, NumberValidators) {
	return {
		restrict: 'A',
		require: 'ngModel',
		link: function (scope, element, attrs, ctrl) {
			var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
				thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP,
				currencySym = $locale.NUMBER_FORMATS.CURRENCY_SYM,
				decimals = $parse(attrs.uiMoneyMask)(scope);

			if (angular.isDefined(attrs.uiHideGroupSep)){
				thousandsDelimiter = '';
			}

			if(isNaN(decimals)) {
				decimals = 2;
			}

			var decimalsPattern = decimals > 0 ? decimalDelimiter + new Array(decimals + 1).join('0') : '';
			var maskPattern = currencySym+' #'+thousandsDelimiter+'##0'+decimalsPattern;
			var moneyMask = new StringMask(maskPattern, {reverse: true});

			function formatter(value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var valueToFormat = PreFormatters.prepareNumberToFormatter(value, decimals);
				return moneyMask.apply(valueToFormat);
			}

			function parser(value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				var actualNumber = value.replace(/[^\d]+/g,'');
				actualNumber = actualNumber.replace(/^[0]+([1-9])/,'$1');
				var formatedValue = moneyMask.apply(actualNumber);

				if (value !== formatedValue) {
					ctrl.$setViewValue(formatedValue);
					ctrl.$render();
				}

				return formatedValue ? parseInt(formatedValue.replace(/[^\d]+/g,''))/Math.pow(10,decimals) : null;
			}

			ctrl.$formatters.push(formatter);
			ctrl.$parsers.push(parser);

			if (attrs.uiMoneyMask) {
				scope.$watch(attrs.uiMoneyMask, function(decimals) {
					if(isNaN(decimals)) {
						decimals = 2;
					}
					decimalsPattern = decimals > 0 ? decimalDelimiter + new Array(decimals + 1).join('0') : '';
					maskPattern = currencySym+' #'+thousandsDelimiter+'##0'+decimalsPattern;
					moneyMask = new StringMask(maskPattern, {reverse: true});

					parser(ctrl.$viewValue);
				});
			}

			if(attrs.min){
				ctrl.$parsers.push(function(value) {
					var min = $parse(attrs.min)(scope);
					return NumberValidators.minNumber(ctrl, value, min);
				});

				scope.$watch(attrs.min, function(value) {
					NumberValidators.minNumber(ctrl, ctrl.$modelValue, value);
				});
			}

			if(attrs.max) {
				ctrl.$parsers.push(function(value) {
					var max = $parse(attrs.max)(scope);
					return NumberValidators.maxNumber(ctrl, value, max);
				});

				scope.$watch(attrs.max, function(value) {
					NumberValidators.maxNumber(ctrl, ctrl.$modelValue, value);
				});
			}
		}
	};
}
MoneyMaskDirective.$inject = ['$locale', '$parse', 'PreFormatters', 'NumberValidators'];

module.exports = MoneyMaskDirective;

},{"string-mask":undefined}],12:[function(require,module,exports){
function NumberMaskDirective($locale, $parse, PreFormatters, NumberMasks, NumberValidators) {
	return {
		restrict: 'A',
		require: 'ngModel',
		link: function (scope, element, attrs, ctrl) {
			var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
				thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP,
				decimals = $parse(attrs.uiNumberMask)(scope);

			if (angular.isDefined(attrs.uiHideGroupSep)){
				thousandsDelimiter = '';
			}

			if(isNaN(decimals)) {
				decimals = 2;
			}

			var viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter),
				modelMask = NumberMasks.modelMask(decimals);

			function parser(value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var valueToFormat = PreFormatters.clearDelimitersAndLeadingZeros(value) || '0';
				var formatedValue = viewMask.apply(valueToFormat);
				var actualNumber = parseFloat(modelMask.apply(valueToFormat));

				if(angular.isDefined(attrs.uiNegativeNumber)){
					var isNegative = (value[0] === '-'),
						needsToInvertSign = (value.slice(-1) === '-');

					//only apply the minus sign if it is negative or(exclusive)
					//needs to be negative and the number is different from zero
					if(needsToInvertSign ^ isNegative && !!actualNumber) {
						actualNumber *= -1;
						formatedValue = '-' + formatedValue;
					}
				}

				if (ctrl.$viewValue !== formatedValue) {
					ctrl.$setViewValue(formatedValue);
					ctrl.$render();
				}

				return actualNumber;
			}

			function formatter(value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var prefix = '';
				if(angular.isDefined(attrs.uiNegativeNumber) && value < 0){
					prefix = '-';
				}

				var valueToFormat = PreFormatters.prepareNumberToFormatter(value, decimals);
				return prefix + viewMask.apply(valueToFormat);
			}

			ctrl.$formatters.push(formatter);
			ctrl.$parsers.push(parser);

			if (attrs.uiNumberMask) {
				scope.$watch(attrs.uiNumberMask, function(decimals) {
					if(isNaN(decimals)) {
						decimals = 2;
					}
					viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter);
					modelMask = NumberMasks.modelMask(decimals);

					parser(ctrl.$viewValue);
				});
			}

			if(attrs.min){
				ctrl.$parsers.push(function(value) {
					var min = $parse(attrs.min)(scope);
					return NumberValidators.minNumber(ctrl, value, min);
				});

				scope.$watch(attrs.min, function(value) {
					NumberValidators.minNumber(ctrl, ctrl.$modelValue, value);
				});
			}

			if(attrs.max) {
				ctrl.$parsers.push(function(value) {
					var max = $parse(attrs.max)(scope);
					return NumberValidators.maxNumber(ctrl, value, max);
				});

				scope.$watch(attrs.max, function(value) {
					NumberValidators.maxNumber(ctrl, ctrl.$modelValue, value);
				});
			}
		}
	};
}
NumberMaskDirective.$inject = ['$locale', '$parse', 'PreFormatters', 'NumberMasks', 'NumberValidators'];

module.exports = NumberMaskDirective;

},{}],13:[function(require,module,exports){
function PercentageMaskDirective($locale, $parse, PreFormatters, NumberMasks, NumberValidators) {
	function preparePercentageToFormatter (value, decimals, modelMultiplier) {
		return PreFormatters.clearDelimitersAndLeadingZeros((parseFloat(value)*modelMultiplier).toFixed(decimals));
	}

	return {
		restrict: 'A',
		require: 'ngModel',
		link: function (scope, element, attrs, ctrl) {
			var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
				thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP,
				decimals = parseInt(attrs.uiPercentageMask);

			var modelValue = {
				multiplier : 100,
				decimalMask: 2
			};

			if (angular.isDefined(attrs.uiHideGroupSep)){
				thousandsDelimiter = '';
			}

			if (angular.isDefined(attrs.uiPercentageValue)) {
				modelValue.multiplier  = 1;
				modelValue.decimalMask = 0;
			}

			if(isNaN(decimals)) {
				decimals = 2;
			}

			var numberDecimals = decimals + modelValue.decimalMask;
			var viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter),
				modelMask = NumberMasks.modelMask(numberDecimals);

			function formatter(value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var valueToFormat = preparePercentageToFormatter(value, decimals, modelValue.multiplier);
				return viewMask.apply(valueToFormat) + ' %';
			}

			function parse(value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var valueToFormat = PreFormatters.clearDelimitersAndLeadingZeros(value) || '0';
				if(value.length > 1 && value.indexOf('%') === -1) {
					valueToFormat = valueToFormat.slice(0,valueToFormat.length-1);
				}
				var formatedValue = viewMask.apply(valueToFormat) + ' %';
				var actualNumber = parseFloat(modelMask.apply(valueToFormat));

				if (ctrl.$viewValue !== formatedValue) {
					ctrl.$setViewValue(formatedValue);
					ctrl.$render();
				}

				return actualNumber;
			}

			ctrl.$formatters.push(formatter);
			ctrl.$parsers.push(parse);

			if (attrs.uiPercentageMask) {
				scope.$watch(attrs.uiPercentageMask, function(decimals) {
					if(isNaN(decimals)) {
						decimals = 2;
					}

					if (angular.isDefined(attrs.uiPercentageValue)) {
						modelValue.multiplier  = 1;
						modelValue.decimalMask = 0;
					}

					numberDecimals = decimals + modelValue.decimalMask;
					viewMask = NumberMasks.viewMask(decimals, decimalDelimiter, thousandsDelimiter);
					modelMask = NumberMasks.modelMask(numberDecimals);

					parse(ctrl.$viewValue);
				});
			}

			if(attrs.min){
				ctrl.$parsers.push(function(value) {
					var min = $parse(attrs.min)(scope);
					return NumberValidators.minNumber(ctrl, value, min);
				});

				scope.$watch('min', function(value) {
					NumberValidators.minNumber(ctrl, ctrl.$modelValue, value);
				});
			}

			if(attrs.max) {
				ctrl.$parsers.push(function(value) {
					var max = $parse(attrs.max)(scope);
					return NumberValidators.maxNumber(ctrl, value, max);
				});

				scope.$watch('max', function(value) {
					NumberValidators.maxNumber(ctrl, ctrl.$modelValue, value);
				});
			}
		}
	};
}
PercentageMaskDirective.$inject = ['$locale', '$parse', 'PreFormatters', 'NumberMasks', 'NumberValidators'];

module.exports = PercentageMaskDirective;

},{}],14:[function(require,module,exports){
var StringMask = require('string-mask');

function ScientificNotationMaskDirective($locale, $parse) {
	var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
		defaultPrecision = 2;

	function significandMaskBuilder (decimals) {
		var mask = '0';

		if(decimals > 0) {
			mask += decimalDelimiter;
			for (var i = 0; i < decimals; i++) {
				mask += '0';
			}
		}

		return new StringMask(mask, {
			reverse: true
		});
	}

	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, element, attrs, ctrl) {
			var decimals = $parse(attrs.uiScientificNotationMask)(scope);

			if(isNaN(decimals)) {
				decimals = defaultPrecision;
			}

			var significandMask = significandMaskBuilder(decimals);

			function splitNumber (value) {
				var stringValue = value.toString(),
					splittedNumber = stringValue.match(/(-?[0-9]*)[\.]?([0-9]*)?[Ee]?([\+-]?[0-9]*)?/);

				return {
					integerPartOfSignificand: splittedNumber[1],
					decimalPartOfSignificand: splittedNumber[2],
					exponent: splittedNumber[3] | 0
				};
			}

			function formatter (value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				if (typeof value === 'string') {
					value = value.replace(decimalDelimiter, '.');
				} else if (typeof value === 'number') {
					value = value.toExponential(decimals);
				}

				var formattedValue, exponent;
				var splittedNumber = splitNumber(value);

				var integerPartOfSignificand = splittedNumber.integerPartOfSignificand || 0;
				var numberToFormat = integerPartOfSignificand.toString();
				if (angular.isDefined(splittedNumber.decimalPartOfSignificand)) {
					numberToFormat += splittedNumber.decimalPartOfSignificand;
				}

				var needsNormalization =
					(integerPartOfSignificand >= 1 || integerPartOfSignificand <= -1) &&
					(
						(angular.isDefined(splittedNumber.decimalPartOfSignificand) &&
						splittedNumber.decimalPartOfSignificand.length > decimals) ||
						(decimals === 0 && numberToFormat.length >= 2)
					);

				if (needsNormalization) {
					exponent = numberToFormat.slice(decimals + 1, numberToFormat.length);
					numberToFormat = numberToFormat.slice(0, decimals + 1);
				}

				formattedValue = significandMask.apply(numberToFormat);

				if (splittedNumber.exponent !== 0) {
					exponent = splittedNumber.exponent;
				}

				if (angular.isDefined(exponent)) {
					formattedValue += 'e' + exponent;
				}

				return formattedValue;
			}

			function parser (value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var viewValue = formatter(value),
					modelValue = parseFloat(viewValue.replace(decimalDelimiter, '.'));

				if (ctrl.$viewValue !== viewValue) {
					ctrl.$setViewValue(viewValue);
					ctrl.$render();
				}

				return modelValue;
			}

			function validator (value) {
				if(ctrl.$isEmpty(value)) {
					return value;
				}

				var isMaxValid = value < Number.MAX_VALUE;
				ctrl.$setValidity('max', isMaxValid);
				return value;
			}

			ctrl.$formatters.push(formatter);
			ctrl.$formatters.push(validator);
			ctrl.$parsers.push(parser);
			ctrl.$parsers.push(validator);
		}
	};
}
ScientificNotationMaskDirective.$inject = ['$locale', '$parse'];

module.exports = ScientificNotationMaskDirective;

},{"string-mask":undefined}],15:[function(require,module,exports){
var StringMask = require('string-mask');

function TimeMaskDirective() {
	return {
		restrict: 'A',
		require: 'ngModel',
		link: function(scope, element, attrs, ctrl) {
			var unformattedValueLength = 6,
				formattedValueLength = 8,
				timeFormat = '00:00:00';

			if (angular.isDefined(attrs.uiTimeMask) && attrs.uiTimeMask === 'short') {
				unformattedValueLength = 4;
				formattedValueLength = 5;
				timeFormat = '00:00';
			}

			var timeMask = new StringMask(timeFormat);

			function clearValue(value) {
				return value.replace(/[^0-9]/g, '').slice(0, unformattedValueLength);
			}

			function formatter(value) {
				if (ctrl.$isEmpty(value)) {
					return value;
				}

				var cleanValue = clearValue(value);

				if (cleanValue.length === 0) {
					return '';
				}

				var formattedValue = timeMask.process(cleanValue).result;
				return formattedValue.replace(/[^0-9]$/, '');
			}

			function parser (value) {
				var viewValue = formatter(value);
				var modelValue = viewValue;

				if (ctrl.$viewValue !== viewValue) {
					ctrl.$setViewValue(viewValue);
					ctrl.$render();
				}

				return modelValue;
			}

			function validator (value) {
				if (angular.isUndefined(value)) {
					return value;
				}

				var splittedValue = value.toString().split(/:/).filter(function(v) {
					return !!v;
				});

				var hours = parseInt(splittedValue[0]),
					minutes = parseInt(splittedValue[1]),
					seconds = parseInt(splittedValue[2] || 0);

				var isValid = value.toString().length === formattedValueLength &&
					hours < 24 && minutes < 60 && seconds < 60;

				ctrl.$setValidity('time', ctrl.$isEmpty(value) || isValid);
				return value;
			}

			ctrl.$formatters.push(formatter);
			ctrl.$formatters.push(validator);
			ctrl.$parsers.push(parser);
			ctrl.$parsers.push(validator);
		}
	};
}

module.exports = TimeMaskDirective;

},{"string-mask":undefined}],16:[function(require,module,exports){
var StringMask = require('string-mask');

var m = angular.module('ui.utils.masks.helpers', []);

module.exports = m.name;

m.factory('PreFormatters', [function(){
	function clearDelimitersAndLeadingZeros(value) {
		var cleanValue = value.replace(/^-/,'').replace(/^0*/, '');
		cleanValue = cleanValue.replace(/[^0-9]/g, '');
		return cleanValue;
	}

	function prepareNumberToFormatter (value, decimals) {
		return clearDelimitersAndLeadingZeros((parseFloat(value)).toFixed(decimals));
	}

	return {
		clearDelimitersAndLeadingZeros: clearDelimitersAndLeadingZeros,
		prepareNumberToFormatter: prepareNumberToFormatter
	};
}])
.factory('NumberValidators', [function() {
	return {
		maxNumber: function maxValidator(ctrl, value, limit) {
			var max = parseFloat(limit);
			var validity = ctrl.$isEmpty(value) || isNaN(max)|| value <= max;
			ctrl.$setValidity('max', validity);
			return value;
		},
		minNumber: function minValidator(ctrl, value, limit) {
			var min = parseFloat(limit);
			var validity = ctrl.$isEmpty(value) || isNaN(min) || value >= min;
			ctrl.$setValidity('min', validity);
			return value;
		}
	};
}])
.factory('NumberMasks', [function(){
	return {
		viewMask: function (decimals, decimalDelimiter, thousandsDelimiter) {
			var mask = '#' + thousandsDelimiter + '##0';

			if(decimals > 0) {
				mask += decimalDelimiter;
				for (var i = 0; i < decimals; i++) {
					mask += '0';
				}
			}

			return new StringMask(mask, {
				reverse: true
			});
		},
		modelMask: function (decimals) {
			var mask = '###0';

			if(decimals > 0) {
				mask += '.';
				for (var i = 0; i < decimals; i++) {
					mask += '0';
				}
			}

			return new StringMask(mask, {
				reverse: true
			});
		}
	};
}]);

},{"string-mask":undefined}],"mask-factory":[function(require,module,exports){
module.exports = function maskFactory(maskDefinition) {
	return function MaskDirective() {
		return {
			restrict: 'A',
			require: 'ngModel',
			link: function(scope, element, attrs, ctrl) {
				function formatter(value) {
					if (ctrl.$isEmpty(value)) {
						return value;
					}

					var cleanValue = maskDefinition.clearValue(value);
					return maskDefinition.format(cleanValue);
				}

				function parser(value) {
					if (ctrl.$isEmpty(value)) {
						return value;
					}

					var cleanValue = maskDefinition.clearValue(value);
					var formattedValue = maskDefinition.format(cleanValue);

					if (ctrl.$viewValue !== formattedValue) {
						ctrl.$setViewValue(formattedValue);
						ctrl.$render();
					}

					if (angular.isUndefined(maskDefinition.getModelValue)) {
						return cleanValue;
					}

					var actualModelType = typeof ctrl.$modelValue;
					return maskDefinition.getModelValue(formattedValue, actualModelType);
				}

				function validator(value) {
					var isValid = ctrl.$isEmpty(value) || maskDefinition.validate(value);
					ctrl.$setValidity(maskDefinition.validationErrorKey, isValid);

					return value;
				}

				ctrl.$formatters.push(formatter);
				ctrl.$formatters.push(validator);
				ctrl.$parsers.push(parser);
				ctrl.$parsers.push(validator);
			}
		};
	};
};

},{}]},{},[1]);


text:
package com.indeed.proctor.webapp.controllers;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.indeed.proctor.common.PayloadSpecification;
import com.indeed.proctor.common.ProctorUtils;
import com.indeed.proctor.common.TestSpecification;
import com.indeed.proctor.common.model.Allocation;
import com.indeed.proctor.common.model.Audit;
import com.indeed.proctor.common.model.ConsumableTestDefinition;
import com.indeed.proctor.common.model.Payload;
import com.indeed.proctor.common.model.Range;
import com.indeed.proctor.common.model.TestBucket;
import com.indeed.proctor.common.model.TestDefinition;
import com.indeed.proctor.common.model.TestMatrixArtifact;
import com.indeed.proctor.common.model.TestType;
import com.indeed.proctor.webapp.db.Environment;
import com.indeed.proctor.webapp.model.AppVersion;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;

import java.util.Collections;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TestProctorController {
    private static final String TEST_NAME = "atest";

    private TestMatrixArtifact constructArtifact() {
        final TestMatrixArtifact artifact = new TestMatrixArtifact();
        final Audit audit = new Audit();
        audit.setUpdated(0);
        audit.setUpdatedBy("junit");
        audit.setVersion(Audit.EMPTY_VERSION);
        artifact.setAudit(audit);

        final Payload payloadInactive = new Payload();
        payloadInactive.setLongValue(-1L);
        final Payload payloadControl = new Payload();
        payloadControl.setLongValue(0L);

        final ConsumableTestDefinition testDefinition = ProctorUtils.convertToConsumableTestDefinition(
                new TestDefinition(
                        "-1",
                        "lang == 'en'",
                        TestType.ANONYMOUS_USER,
                        "&" + TEST_NAME,
                        Lists.newArrayList(
                                new TestBucket(
                                        "inactive",
                                        -1,
                                        "inactive",
                                        payloadInactive
                                ),
                                new TestBucket(
                                        "control",
                                        0,
                                        "control",
                                        payloadControl
                                )
                        ),
                        Lists.newArrayList(
                                new Allocation(
                                        null,
                                        Lists.newArrayList(
                                                new Range(
                                                        -1,
                                                        1.0
                                                )
                                        ),
                                        "#A1"
                                )
                        ),
                        false,
                        Collections.emptyMap(),
                        Collections.emptyMap(),
                        "test for a unit test"
                )
        );

        artifact.setTests(ImmutableMap.of(TEST_NAME, testDefinition));

        return artifact;
    }

    @Test
    public void testCompatibleSpecificationResultFromRequiredTest() {
        final TestSpecification testSpecification = new TestSpecification();
        testSpecification.setFallbackValue(-1);
        final PayloadSpecification payloadSpecification = new PayloadSpecification();
        payloadSpecification.setType("longValue");
        testSpecification.setPayload(payloadSpecification);
        testSpecification.setBuckets(
                ImmutableMap.of(
                        "inactive", -1,
                        "control", 0
                )
        );
        final AppVersion appVersion = new AppVersion("sample application", "v1");
        final ProctorController.CompatibleSpecificationResult result =
                ProctorController.CompatibleSpecificationResult.fromRequiredTest(
                        Environment.PRODUCTION,
                        appVersion,
                        constructArtifact(),
                        TEST_NAME,
                        testSpecification
                );
        assertTrue(result.isCompatible());
        assertFalse(result.isDynamicTest(TEST_NAME));
        assertTrue(StringUtils.isEmpty(result.getError()));
        assertEquals(appVersion, result.getAppVersion());
    }

    @Test
    public void testCompatibleSpecificationResultFromDynamicTest() {
        final AppVersion appVersion = new AppVersion("sample application", "v1");
        final ProctorController.CompatibleSpecificationResult result =
                ProctorController.CompatibleSpecificationResult.fromDynamicTest(
                        Environment.PRODUCTION,
                        appVersion,
                        constructArtifact(),
                        TEST_NAME
                );
        assertTrue(result.isCompatible());
        assertTrue(result.isDynamicTest(TEST_NAME));
        assertTrue(StringUtils.isEmpty(result.getError()));
        assertEquals(appVersion, result.getAppVersion());
    }
}


text:
<?php
/**
 * This file belongs to the YIT Plugin Framework.
 *
 * This source file is subject to the GNU GENERAL PUBLIC LICENSE (GPL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.txt
 */

if ( !defined( 'ABSPATH' ) ) {
    exit;
} // Exit if accessed directly

/**
 * Implements a custom textarea in YWRR plugin admin tab
 *
 * @class   YWRR_Custom_Textarea
 * @package Yithemes
 * @since   1.0.0
 * @author  Your Inspiration Themes
 *
 */
class YWRR_Custom_Textarea {

    /**
     * Outputs a custom textarea template in plugin options panel
     *
     * @since   1.0.0
     * @return  void
     * @author  Alberto Ruggiero
     */
    public static function output( $option ) {

        $custom_attributes = array();

        if ( !empty( $option['custom_attributes'] ) && is_array( $option['custom_attributes'] ) ) {
            foreach ( $option['custom_attributes'] as $attribute => $attribute_value ) {
                $custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"';
            }
        }

        $option_value = WC_Admin_Settings::get_option( $option['id'], $option['default'] );

        ?>

                <label for="<?php echo esc_attr( $option['id'] ); ?>"><?php echo esc_html( $option['title'] ); ?>

            <td class="forminp forminp-<?php echo sanitize_title( $option['type'] ) ?>">

                <textarea
                    name="<?php echo esc_attr( $option['id'] ); ?>"
                    id="<?php echo esc_attr( $option['id'] ); ?>"
                    style="<?php echo esc_attr( $option['css'] ); ?>"
                    class="<?php echo esc_attr( $option['class'] ); ?>"
                    <?php echo implode( ' ', $custom_attributes ); ?>
                    ><?php echo esc_textarea( $option_value ); ?></textarea>

                <?php echo $option['desc']; ?>

    <?php
    }

}


text:
/* eslint-env mocha, browser*/
/* global proclaim, it */

var arePropertyDescriptorsSupported = function () {
	var obj = {};
	try {
		Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
        /* eslint-disable no-unused-vars, no-restricted-syntax */
        for (var _ in obj) { return false; }
        /* eslint-enable no-unused-vars, no-restricted-syntax */
		return obj.x === obj;
	} catch (e) { // this is IE 8.
		return false;
	}
};
var supportsDescriptors = Object.defineProperty && arePropertyDescriptorsSupported();

it('has the well known symbol toStringTag as static properties on Symbol', function() {
	proclaim.notEqual(Symbol.toStringTag, undefined);

	if (supportsDescriptors) {
		var toStringTag = Symbol.toStringTag;
		Symbol.toStringTag = "nope";
		proclaim.equal(Symbol.toStringTag, toStringTag);
	}
});