Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Unified Diff: source/libvpx/examples/includes/HTML-Toc-0.91/TocUpdator.pm

Issue 148913004: libvpx: Pull from upstream (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/third_party/libvpx/
Patch Set: Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: source/libvpx/examples/includes/HTML-Toc-0.91/TocUpdator.pm
===================================================================
--- source/libvpx/examples/includes/HTML-Toc-0.91/TocUpdator.pm (revision 247498)
+++ source/libvpx/examples/includes/HTML-Toc-0.91/TocUpdator.pm (working copy)
@@ -1,693 +0,0 @@
-#==== HTML::TocUpdator ========================================================
-# function: Update 'HTML::Toc' table of contents.
-# note: - 'TUT' is an abbreviation of 'Toc Update Token'.
-
-
-package HTML::TocUpdator;
-
-
-use strict;
-use HTML::TocInsertor;
-
-
-BEGIN {
- use vars qw(@ISA $VERSION);
-
- $VERSION = '0.91';
-
- @ISA = qw(HTML::TocInsertor);
-}
-
-
-use constant TUT_TOKENTYPE_START => 0;
-use constant TUT_TOKENTYPE_END => 1;
-use constant TUT_TOKENTYPE_TEXT => 2;
-use constant TUT_TOKENTYPE_COMMENT => 3;
-
-use constant MODE_DO_NOTHING => 0; # 0b00
-use constant MODE_DO_INSERT => 1; # 0b01
-use constant MODE_DO_UPDATE => 3; # 0b11
-
-
-END {}
-
-
-#--- HTML::TocUpdator::new() --------------------------------------------------
-# function: Constructor.
-
-sub new {
- # Get arguments
- my ($aType) = @_;
- my $self = $aType->SUPER::new;
- # Bias to not update ToC
- $self->{htu__Mode} = MODE_DO_NOTHING;
- # Bias to not delete tokens
- $self->{_doDeleteTokens} = 0;
- # Reset batch variables
- #$self->_resetBatchVariables;
-
- $self->{options} = {};
-
- # TODO: Initialize output
-
- return $self;
-} # new()
-
-
-#--- HTML::TocUpdator::_deinitializeUpdatorBatch() --------------------------
-# function: Deinitialize updator batch.
-# args: - $aTocs: Reference to array of tocs.
-
-sub _deinitializeUpdatorBatch {
- # Get arguments
- my ($self, $aTocs) = @_;
- # Indicate end of ToC updating
- $self->{htu__Mode} = MODE_DO_NOTHING;
- # Deinitialize insertor batch
- $self->_deinitializeInsertorBatch();
-} # _deinitializeUpdatorBatch()
-
-
-#--- HTML::TokenUpdator::_doesHashEqualHash() ---------------------------------
-# function: Determines whether hash1 equals hash2.
-# args: - $aHash1
-# - $aHash2
-# returns: True (1) if hash1 equals hash2, 0 if not. For example, with the
-# following hashes:
-#
-# %hash1 = { %hash2 = {
-# 'class' => 'header', 'class' => 'header',
-# 'id' => 'intro1' 'id' => 'intro2'
-# } }
-#
-# the routine will return 0, cause the hash fields 'id' differ.
-# note: Class function.
-
-sub _doesHashEqualHash {
- # Get arguments
- my ($aHash1, $aHash2) = @_;
- # Local variables
- my ($key1, $value1, $key2, $value2, $result);
- # Bias to success
- $result = 1;
- # Loop through hash1 while values available
- HASH1: while (($key1, $value1) = each %$aHash1) {
- # Yes, values are available;
- # Value1 differs from value2?
- if ($value1 ne $aHash2->{$key1}) {
- # Yes, hashes differ;
- # Indicate condition fails
- $result = 0;
- # Reset 'each' iterator which we're going to break
- keys %$aHash2;
- # Break loop
- last HASH1;
- }
- }
- # Return value
- return $result;
-} # _doesHashEqualHash()
-
-
-#--- HTML::TokenUpdator::_doesTagExistInArray() -------------------------------
-# function: Check whether tag & attributes matches any of the tags & attributes
-# in the specified array. The array must consist of elements with
-# format:
-#
-# [$tag, \%attributes]
-#
-# args: - $aTag: tag to search for
-# - $aAttributes: tag attributes to search for
-# - $aArray: Array to search in.
-# returns: 1 if tag does exist in array, 0 if not.
-# note: Class function.
-
-sub _doesTagExistInArray {
- # Get arguments
- my ($aTag, $aAttributes, $aArray) = @_;
- # Local variables
- my ($tag, $result);
- # Bias to non-existing tag
- $result = 0;
- # Loop through existing tags
- TAG: foreach $tag (@{$aArray}) {
- if (defined(@{$tag}[0])) {
- # Does tag equals any existing tag?
- if ($aTag eq @{$tag}[0]) {
- # Yes, tag equals existing tag;
- # Do hashes equal?
- if (HTML::TocUpdator::_doesHashEqualHash(
- $aAttributes, @{$tag}[1]
- )) {
- # Yes, hashes are the same;
- # Indicate tag exists in array
- $result = 1;
- # Break loop
- last TAG;
- }
- }
- }
- }
- # Return value
- return $result;
-} # _doesTagExistInArray()
-
-
-#--- HTML::TocUpdator::_initializeUpdatorBatch() ----------------------------
-# function: Initialize insertor batch.
-# args: - $aMode: Mode. Can be either MODE_DO_INSERT or MODE_DO_UPDATE
-# - $aTocs: Reference to array of tocs.
-# - $aOptions: optional options
-# note: Updating actually means: deleting the old ToC and inserting a new
-# ToC. That's why we're calling 'insertor' methods here.
-
-sub _initializeUpdatorBatch {
- # Get arguments
- my ($self, $aMode, $aTocs, $aOptions) = @_;
- # Initialize insertor batch
- $self->_initializeInsertorBatch($aTocs, $aOptions);
- # Parse ToC update templates
- $self->_parseTocUpdateTokens();
- # Indicate start of ToC updating
- $self->{htu__Mode} = $aMode;
-} # _initializeUpdatorBatch()
-
-
-#--- HTML::TocUpdator::_parseTocUpdateTokens() --------------------------------
-# function: Parse ToC insertion point specifier.
-
-sub _parseTocUpdateTokens {
- # Get arguments
- my ($self) = @_;
- # Local variables
- my ($toc, $tokenType, $tokenPreposition, $token);
- my ($tocInsertionPoint, $tocInsertionPointTokenAttributes);
- # Create parser for update begin tokens
- my $tokenUpdateBeginParser = HTML::_TokenUpdateParser->new(
- $self->{_tokensUpdateBegin}
- );
- # Create parser for update end tokens
- my $tokenUpdateEndParser = HTML::_TokenUpdateParser->new(
- $self->{_tokensUpdateEnd}
- );
-
- # Loop through ToCs
- foreach $toc (@{$self->{_tocs}}) {
- # Parse update tokens
- $tokenUpdateBeginParser->parse(
- $toc->{_tokenUpdateBeginOfAnchorNameBegin}
- );
- $tokenUpdateBeginParser->parse($toc->{_tokenUpdateBeginOfAnchorNameEnd});
- $tokenUpdateBeginParser->parse($toc->{_tokenUpdateBeginNumber});
- $tokenUpdateBeginParser->parse($toc->{_tokenUpdateBeginToc});
-
- $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndOfAnchorNameBegin});
- $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndOfAnchorNameEnd});
- $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndNumber});
- $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndToc});
- }
-} # _parseTocUpdateTokens()
-
-
-#--- HTML::TocUpdator::_resetBatchVariables() ---------------------------------
-# function: Reset batch variables
-
-sub _resetBatchVariables {
- # Get arguments
- my ($self) = @_;
- # Call ancestor
- $self->SUPER::_resetBatchVariables();
- # Arrays containing start, end, comment & text tokens which indicate
- # the begin of ToC tokens. The tokens are stored in keys of hashes to
- # avoid storing duplicates as an array would.
- $self->{_tokensUpdateBegin} = [
- [], # ['<start tag>', <attributes>]
- {}, # {'<end tag>' => ''}
- {}, # {'<text>' => ''}
- {} # {'<comment>' => ''}
- ];
- # Arrays containing start, end, comment & text tokens which indicate
- # the end of ToC tokens. The tokens are stored in keys of hashes to
- # avoid storing duplicates as an array would.
- $self->{_tokensUpdateEnd} = [
- [], # ['<start tag>', <attributes>]
- {}, # {'<end tag>' => ''}
- {}, # {'<text>' => ''}
- {} # {'<comment>' => ''}
- ];
-} # _resetBatchVariables()
-
-
-#--- HTML::TocUpdator::_setActiveAnchorName() ---------------------------------
-# function: Set active anchor name.
-# args: - aAnchorName: Name of anchor name to set active.
-
-sub _setActiveAnchorName {
- # Get arguments
- my ($self, $aAnchorName) = @_;
- # Are tokens being deleted?
- if (! $self->{_doDeleteTokens}) {
- # No, tokens aren't being deleted;
- # Call ancestor to set anchor name
- $self->SUPER::_setActiveAnchorName($aAnchorName);
- }
-} # _setActiveAnchorName()
-
-
-#--- HTML::TocUpdator::_update() ----------------------------------------------
-# function: Update ToC in string.
-# args: - $aMode: Mode. Can be either MODE_DO_UPDATE or MODE_DO_INSERT.
-# - $aToc: (reference to array of) ToC object to update
-# - $aString: string to update ToC of
-# - $aOptions: optional updator options
-# note: Used internally.
-
-sub _update {
- # Get arguments
- my ($self, $aMode, $aToc, $aString, $aOptions) = @_;
- # Initialize TocUpdator batch
- $self->_initializeUpdatorBatch($aMode, $aToc, $aOptions);
- # Start updating ToC by starting ToC insertion
- $self->_insert($aString);
- # Deinitialize TocUpdator batch
- $self->_deinitializeUpdatorBatch();
-} # update()
-
-
-#--- HTML::TocUpdator::_updateFile() ------------------------------------------
-# function: Update ToCs in file.
-# args: - $aMode: Mode. Can be either MODE_DO_UPDATE or MODE_DO_INSERT.
-# - $aToc: (reference to array of) ToC object to update
-# - $aFile: (reference to array of) file to parse for updating.
-# - $aOptions: optional updator options
-# note: Used internally.
-
-sub _updateFile {
- # Get arguments
- my ($self, $aMode, $aToc, $aFile, $aOptions) = @_;
- # Initialize TocUpdator batch
- $self->_initializeUpdatorBatch($aMode, $aToc, $aOptions);
- # Start updating ToC by starting ToC insertion
- $self->_insertIntoFile($aFile);
- # Deinitialize TocUpdator batch
- $self->_deinitializeUpdatorBatch();
-} # _updateFile()
-
-
-#--- HTML::TocUpdator::_writeOrBufferOutput() ---------------------------------
-# function: Write processed HTML to output device(s).
-# args: - aOutput: scalar to write
-
-sub _writeOrBufferOutput {
- # Get arguments
- my ($self, $aOutput) = @_;
- # Delete output?
- if (! $self->{_doDeleteTokens}) {
- # No, don't delete output;
- # Call ancestor
- $self->SUPER::_writeOrBufferOutput($aOutput);
- }
-} # _writeOrBufferOutput()
-
-
-#--- HTML::TocUpdator::anchorNameBegin() --------------------------------------
-# function: Process 'anchor name begin' generated by HTML::Toc.
-# args: - $aAnchorName: Anchor name begin tag to output.
-# - $aToc: Reference to ToC to which anchorname belongs.
-
-sub anchorNameBegin {
- # Get arguments
- my ($self, $aAnchorNameBegin, $aToc) = @_;
- # Call ancestor
- $self->SUPER::anchorNameBegin($aAnchorNameBegin);
- # Must ToC be inserted or updated?
- if ($self->{htu__Mode} != MODE_DO_NOTHING) {
- # Yes, ToC must be inserted or updated;
- # Surround anchor name with update tags
- $self->{_outputPrefix} =
- $aToc->{_tokenUpdateBeginOfAnchorNameBegin} .
- $self->{_outputPrefix} .
- $aToc->{_tokenUpdateEndOfAnchorNameBegin};
- }
-} # anchorNameBegin()
-
-
-#--- HTML::TocUpdator::anchorNameEnd() ----------------------------------------
-# function: Process 'anchor name end' generated by HTML::Toc.
-# args: - $aAnchorNameEnd: Anchor name end tag to output.
-# - $aToc: Reference to ToC to which anchorname belongs.
-
-sub anchorNameEnd {
- # Get arguments
- my ($self, $aAnchorNameEnd, $aToc) = @_;
- # Call ancestor
- $self->SUPER::anchorNameEnd($aAnchorNameEnd);
- # Must ToC be inserted or updated?
- if ($self->{htu__Mode} != MODE_DO_NOTHING) {
- # Yes, ToC must be inserted or updated;
- # Surround anchor name with update tags
- $self->{_outputSuffix} =
- $aToc->{_tokenUpdateBeginOfAnchorNameEnd} .
- $self->{_outputSuffix} .
- $aToc->{_tokenUpdateEndOfAnchorNameEnd};
- }
-} # anchorNameEnd()
-
-
-#--- HTML::TocUpdator::comment() ----------------------------------------------
-# function: Process comment.
-# args: - $aComment: comment text with '<!--' and '-->' tags stripped off.
-
-sub comment {
- # Get arguments
- my ($self, $aComment) = @_;
- # Must ToC be updated?
- if ($self->{htu__Mode} == MODE_DO_UPDATE) {
- # Yes, ToC must be updated;
- # Updator is currently deleting tokens?
- if ($self->{_doDeleteTokens}) {
- # Yes, tokens must be deleted;
- # Call ancestor
- $self->SUPER::comment($aComment);
-
- # Look for update end token
-
- # Does comment matches update end token?
- if (defined(
- $self->{_tokensUpdateEnd}[TUT_TOKENTYPE_COMMENT]{$aComment}
- )) {
- # Yes, comment matches update end token;
- # Indicate to stop deleting tokens
- $self->{_doDeleteTokens} = 0;
- }
- }
- else {
- # No, tokens mustn't be deleted;
-
- # Look for update begin token
-
- # Does comment matches update begin token?
- if (defined(
- $self->{_tokensUpdateBegin}[TUT_TOKENTYPE_COMMENT]{$aComment}
- )) {
- # Yes, comment matches update begin token;
- # Indicate to start deleting tokens
- $self->{_doDeleteTokens} = 1;
- }
- # Call ancestor
- $self->SUPER::comment($aComment);
- }
- }
- else {
- # No, ToC mustn't be updated;
- # Call ancestor
- $self->SUPER::comment($aComment);
- }
-} # comment()
-
-
-#--- HTML::TocUpdator::end() --------------------------------------------------
-# function: This function is called every time a closing tag is encountered.
-# args: - $aTag: tag name (in lower case).
-# - $aOrigText: tag name including brackets.
-
-sub end {
- # Get arguments
- my ($self, $aTag, $aOrigText) = @_;
- # Call ancestor
- $self->SUPER::end($aTag, $aOrigText);
- # Must ToC be updated?
- if ($self->{htu__Mode} == MODE_DO_UPDATE) {
- # Yes, ToC must be updated;
- # Updator is currently deleting tokens?
- if ($self->{_doDeleteTokens}) {
- # Yes, tokens must be deleted;
- # Does end tag matches update end token?
- if (defined(
- $self->{_tokensUpdateEnd}[TUT_TOKENTYPE_END]{$aTag}
- )) {
- # Yes, end tag matches update end token;
- # Indicate to stop deleting tokens
- $self->{_doDeleteTokens} = 0;
- }
- }
- }
-} # end()
-
-
-#--- HTML::TocUpdator::insert() -----------------------------------------------
-# function: Insert ToC in string.
-# args: - $aToc: (reference to array of) ToC object to update
-# - $aString: string to insert ToC in.
-# - $aOptions: optional updator options
-
-sub insert {
- # Get arguments
- my ($self, $aToc, $aString, $aOptions) = @_;
- # Do start insert
- $self->_update(MODE_DO_INSERT, $aToc, $aString, $aOptions);
-} # insert()
-
-
-#--- HTML::TocUpdator::insertIntoFile() --------------------------------------
-# function: Insert ToC in file.
-# args: - $aToc: (reference to array of) ToC object to update
-# - $aFile: File to insert ToC in.
-# - $aOptions: optional updator options
-
-sub insertIntoFile {
- # Get arguments
- my ($self, $aToc, $aFile, $aOptions) = @_;
- # Do start insert
- $self->_updateFile(MODE_DO_INSERT, $aToc, $aFile, $aOptions);
-} # insertIntoFile()
-
-
-#--- HTML::TocUpdator::number() -----------------------------------------------
-# function: Process heading number generated by HTML::Toc.
-# args: - $aNumber
-# - $aToc: Reference to ToC to which anchorname belongs.
-
-sub number {
- # Get arguments
- my ($self, $aNumber, $aToc) = @_;
- # Call ancestor
- $self->SUPER::number($aNumber);
- # Must ToC be inserted or updated?
- if ($self->{htu__Mode} != MODE_DO_NOTHING) {
- # Yes, ToC must be inserted or updated;
- # Surround number with update tags
- $self->{_outputSuffix} =
- $aToc->{_tokenUpdateBeginNumber} .
- $self->{_outputSuffix} .
- $aToc->{_tokenUpdateEndNumber};
- }
-} # number()
-
-
-#--- HTML::TocUpdator::start() ------------------------------------------------
-# function: This function is called every time an opening tag is encountered.
-# args: - $aTag: tag name (in lower case).
-# - $aAttr: reference to hash containing all tag attributes (in lower
-# case).
-# - $aAttrSeq: reference to array containing all tag attributes (in
-# lower case) in the original order
-# - $aOrigText: the original HTML text
-
-sub start {
- # Get arguments
- my ($self, $aTag, $aAttr, $aAttrSeq, $aOrigText) = @_;
- # Must ToC be updated?
- if ($self->{htu__Mode} == MODE_DO_UPDATE) {
- # Yes, ToC must be updated;
- # Does start tag matches token update begin tag?
- if (HTML::TocUpdator::_doesTagExistInArray(
- $aTag, $aAttr, $self->{_tokensUpdateBegin}[TUT_TOKENTYPE_START]
- )) {
- # Yes, start tag matches token update tag;
- # Indicate to delete tokens
- $self->{_doDeleteTokens} = 1;
- }
- }
- # Let ancestor process the start tag
- $self->SUPER::start($aTag, $aAttr, $aAttrSeq, $aOrigText);
-} # start()
-
-
-#--- HTML::TocUpdator::toc() --------------------------------------------------
-# function: Toc processing method. Add toc reference to scenario.
-# args: - $aScenario: Scenario to add ToC reference to.
-# - $aToc: Reference to ToC to insert.
-# note: The ToC hasn't been build yet; only a reference to the ToC to be
-# build is inserted.
-
-sub toc {
- # Get arguments
- my ($self, $aScenario, $aToc) = @_;
-
- # Surround toc with update tokens
-
- # Add update begin token
- push(@$aScenario, \$aToc->{_tokenUpdateBeginToc});
- # Call ancestor
- $self->SUPER::toc($aScenario, $aToc);
- # Add update end token
- push(@$aScenario, \$aToc->{_tokenUpdateEndToc});
-} # toc()
-
-
-#--- HTML::TocUpdator::_processTocText() --------------------------------------
-# function: Toc text processing function.
-# args: - $aText: Text to add to ToC.
-# - $aToc: ToC to add text to.
-
-sub _processTocText {
- # Get arguments
- my ($self, $aText, $aToc) = @_;
- # Delete output?
- if (! $self->{_doDeleteTokens}) {
- # No, don't delete output;
- # Call ancestor
- $self->SUPER::_processTocText($aText, $aToc);
- }
-} # _processTocText()
-
-
-#--- HTML::TocUpdator::update() -----------------------------------------------
-# function: Update ToC in string.
-# args: - $aToc: (reference to array of) ToC object to update
-# - $aString: string to update ToC of
-# - $aOptions: optional updator options
-
-sub update {
- # Get arguments
- my ($self, $aToc, $aString, $aOptions) = @_;
- # Do start update
- $self->_update(MODE_DO_UPDATE, $aToc, $aString, $aOptions);
-} # update()
-
-
-#--- HTML::TocUpdator::updateFile() -------------------------------------------
-# function: Update ToC of file.
-# args: - $aToc: (reference to array of) ToC object to update
-# - $aFile: (reference to array of) file to parse for updating.
-# - $aOptions: optional updator options
-
-sub updateFile {
- # Get arguments
- my ($self, $aToc, $aFile, $aOptions) = @_;
- # Do start update
- $self->_updateFile(MODE_DO_UPDATE, $aToc, $aFile, $aOptions);
-} # update()
-
-
-
-
-#=== HTML::_TokenUpdateParser =================================================
-# function: Parse 'update tokens'. 'Update tokens' mark HTML code which is
-# inserted by 'HTML::TocInsertor'.
-# note: Used internally.
-
-package HTML::_TokenUpdateParser;
-
-
-BEGIN {
- use vars qw(@ISA);
-
- @ISA = qw(HTML::Parser);
-}
-
-END {}
-
-
-#--- HTML::_TokenUpdateParser::new() ------------------------------------------
-# function: Constructor
-
-sub new {
- # Get arguments
- my ($aType, $aTokenArray) = @_;
- # Create instance
- my $self = $aType->SUPER::new;
- # Reference token array
- $self->{tokens} = $aTokenArray;
- # Return instance
- return $self;
-} # new()
-
-
-#--- HTML::_TokenUpdateParser::comment() --------------------------------------
-# function: Process comment.
-# args: - $aComment: comment text with '<!--' and '-->' tags stripped off.
-
-sub comment {
- # Get arguments
- my ($self, $aComment) = @_;
- # Add token to array of update tokens
- $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_COMMENT]{$aComment} = '';
-} # comment()
-
-
-#--- HTML::_TokenUpdateParser::end() ------------------------------------------
-# function: This function is called every time a closing tag is encountered
-# by HTML::Parser.
-# args: - $aTag: tag name (in lower case).
-
-sub end {
- # Get arguments
- my ($self, $aTag, $aOrigText) = @_;
- # Add token to array of update tokens
- $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_END]{$aTag} = '';
-} # end()
-
-
-#--- HTML::_TokenUpdateParser::parse() ----------------------------------------
-# function: Parse token.
-# args: - $aToken: 'update token' to parse
-
-sub parse {
- # Get arguments
- my ($self, $aString) = @_;
- # Call ancestor
- $self->SUPER::parse($aString);
-} # parse()
-
-
-#--- HTML::_TokenUpdateParser::start() ----------------------------------------
-# function: This function is called every time an opening tag is encountered.
-# args: - $aTag: tag name (in lower case).
-# - $aAttr: reference to hash containing all tag attributes (in lower
-# case).
-# - $aAttrSeq: reference to array containing all tag attributes (in
-# lower case) in the original order
-# - $aOrigText: the original HTML text
-
-sub start {
- # Get arguments
- my ($self, $aTag, $aAttr, $aAttrSeq, $aOrigText) = @_;
- # Does token exist in array?
- if (! HTML::TocUpdator::_doesTagExistInArray(
- $aTag, $aAttr, $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_START]
- )) {
- # No, token doesn't exist in array;
- # Add token to array of update tokens
- push(
- @{$self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_START]},
- [$aTag, $aAttr]
- );
- }
-} # start()
-
-
-#--- HTML::_TokenUpdateParser::text() -----------------------------------------
-# function: This function is called every time plain text is encountered.
-# args: - @_: array containing data.
-
-sub text {
- # Get arguments
- my ($self, $aText) = @_;
- # Add token to array of update tokens
- $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_TEXT]{$aText} = '';
-} # text()
-
-
-1;

Powered by Google App Engine
This is Rietveld 408576698