diff options
-rw-r--r-- | .gitattributes | 2 | ||||
-rw-r--r-- | build.xml | 89 | ||||
-rw-r--r-- | buildscripts/phing/tasks/PradoPackageTask.php | 5 | ||||
-rw-r--r-- | demos/northwind-db/protected/data/Northwind.db | bin | 583680 -> 583680 bytes | |||
-rw-r--r-- | demos/northwind-db/protected/database/sqlmap.xml | 107 | ||||
-rw-r--r-- | framework/Web/Javascripts/source/prado/colorpicker/colorpicker.js | 2 | ||||
-rw-r--r-- | framework/Web/Javascripts/source/prado/colorpicker/default.css | 25 | ||||
-rw-r--r-- | framework/Web/UI/WebControls/TColorPicker.php | 51 | ||||
-rw-r--r-- | framework/Web/UI/WebControls/THtmlArea.php | 34 | ||||
-rw-r--r-- | tests/FunctionalTests/tickets/protected/pages/Ticket609.page | 11 |
10 files changed, 284 insertions, 42 deletions
diff --git a/.gitattributes b/.gitattributes index eef497b2..8a52fca2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -899,6 +899,7 @@ demos/northwind-db/protected/database/Region.php -text demos/northwind-db/protected/database/Shipper.php -text demos/northwind-db/protected/database/Supplier.php -text demos/northwind-db/protected/database/Territory.php -text +demos/northwind-db/protected/database/sqlmap.xml -text demos/northwind-db/protected/pages/Home.page -text demos/northwind-db/protected/pages/Home.php -text demos/northwind-db/protected/pages/northwind.gif -text @@ -2573,6 +2574,7 @@ tests/FunctionalTests/tickets/protected/pages/Ticket591.php -text tests/FunctionalTests/tickets/protected/pages/Ticket593.page -text tests/FunctionalTests/tickets/protected/pages/Ticket605.page -text tests/FunctionalTests/tickets/protected/pages/Ticket606.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket609.page -text tests/FunctionalTests/tickets/protected/pages/Ticket614.page -text tests/FunctionalTests/tickets/protected/pages/Ticket68.page -text tests/FunctionalTests/tickets/protected/pages/Ticket72.page -text @@ -156,24 +156,24 @@ <target name="compact-collections" description="Collections">
<mkdir dir="${build.compact.dir}" />
<mkdir dir="${build.compact.dir}/docs" /> - <compact-package output="${build.compact.dir}/collections.php" strip="${compact-strip-comments}">
+ <mkdir dir="${build.compact.dir}/prado-db" /> + <compact-package output="${build.compact.dir}/prado-db/collections.php" strip="${compact-strip-comments}">
<filelist dir="framework" files="PradoBase.php,TComponent.php,Exceptions/TException.php,interfaces.php" />
<filelist dir="framework/Collections" files="TList.php,TMap.php,TAttributeCollection.php,TPagedList.php,TPagedDataSource.php" />
</compact-package>
- <delete file="${build.compact.dir}/messages.txt" />
- <copy file="framework/Exceptions/messages.txt" tofile="${build.compact.dir}/messages.txt" />
+ <delete file="${build.compact.dir}/prado-db/messages.txt" />
+ <copy file="framework/Exceptions/messages.txt" tofile="${build.compact.dir}/prado-db/messages.txt" />
<copy file="COPYRIGHT" tofile="${build.compact.dir}/COPYRIGHT" />
<copy file="HISTORY" tofile="${build.compact.dir}/HISTORY" />
<delete file="${build.compact.dir}/readme.txt" />
- <append text="PRADO Framework for PHP 5, version ${prado.version}. See docs/ directory for documentation."
- destFile="${build.compact.dir}/readme.txt" />
+ <append destFile="${build.compact.dir}/readme.txt">PRADO Framework for PHP 5, version ${prado.version}. See docs/ directory for documentation.</append>
<prado-quickstart-docs output="${build.compact.dir}/docs" pages="Advanced/Collections.page,Fundamentals/Components.page"/> </target>
<target name="compact-db" description="Database" depends="compact-collections">
- <compact-package output="${build.compact.dir}/db.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/db.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data"
files="TDbConnection.php, TDbCommand.php, TDbDataReader.php, TDbTransaction.php"/>
<filelist dir="framework/Data/Common"
@@ -183,28 +183,28 @@ </target>
<target name="compact-db-sqlite" description="Sqlite Database" depends="compact-db">
- <compact-package output="${build.compact.dir}/db-sqlite.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/db-sqlite.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/Common/Sqlite"
files="TSqliteCommandBuilder.php,TSqliteMetaData.php,TSqliteTableColumn.php,TSqliteTableInfo.php" />
</compact-package>
</target>
<target name="compact-db-mysql" description="Mysql Database" depends="compact-db">
- <compact-package output="${build.compact.dir}/db-mysql.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/db-mysql.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/Common/Mysql"
files="TMysqlCommandBuilder.php,TMysqlMetaData.php,TMysqlTableColumn.php,TMysqlTableInfo.php" />
</compact-package>
</target>
<target name="compact-db-pgsql" description="Pgsql Database" depends="compact-db">
- <compact-package output="${build.compact.dir}/db-pgsql.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/db-pgsql.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/Common/Pgsql"
files="TPgsqlCommandBuilder.php,TPgsqlMetaData.php,TPgsqlTableColumn.php,TPgsqlTableInfo.php" />
</compact-package>
</target>
<target name="compact-db-mssql" description="Mssql Database" depends="compact-db">
- <compact-package output="${build.compact.dir}/db-mssql.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/db-mssql.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/Common/Mssql"
files="TMssqlCommandBuilder.php,TMssqlMetaData.php,TMssqlTableColumn.php,TMssqlTableInfo.php" />
</compact-package>
@@ -214,26 +214,26 @@ <target name="compact-db-all" depends="compact-db-sqlite,compact-db-mysql,compact-db-pgsql,compact-db-mssql" />
<target name="compact-table-gateway" description="Package Active Record" depends="compact-db-all">
- <compact-package output="${build.compact.dir}/table-gateway.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/table-gateway.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/DataGateway"
files="TDataGatewayCommand.php, TSqlCriteria.php, TTableGateway.php"/>
</compact-package>
</target>
<target name="compact-active-record" description="Package Active Record" depends="compact-table-gateway">
- <compact-package output="${build.compact.dir}/active-record.php" strip="${compact-strip-comments}">
+ <compact-package output="${build.compact.dir}/prado-db/active-record.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/ActiveRecord"
files="TActiveRecord.php,TActiveRecordManager.php,Exceptions/TActiveRecordException.php,TActiveRecordCriteria.php,TActiveRecordGateway.php,TActiveRecordStateRegistry.php" />
<filelist dir="framework/Data/ActiveRecord/Relations"
files="TActiveRecordRelation.php,TActiveRecordRelationContext.php,TActiveRecordHasOne.php,TActiveRecordHasManyAssociation.php,TActiveRecordHasMany.php,TActiveRecordBelongsTo.php" />
</compact-package>
<append file="framework/Data/ActiveRecord/Exceptions/messages.txt"
- destfile="${build.compact.dir}/messages.txt" />
+ destfile="${build.compact.dir}/prado-db/messages.txt" />
<prado-quickstart-docs output="${build.compact.dir}/docs" pages="Database/ActiveRecord.page" /> </target> - <target name="compact-sqlmap" description="Package Active Record" depends="compact-db-all">
- <compact-package output="${build.compact.dir}/sqlmap.php" strip="${compact-strip-comments}">
+ <target name="compact-sqlmap" description="Package SqlMap" depends="compact-db-all">
+ <compact-package output="${build.compact.dir}/prado-db/sqlmap.php" strip="${compact-strip-comments}">
<filelist dir="framework/Data/SqlMap" files="TSqlMapManager.php,TSqlMapGateway.php" />
<filelist dir="framework/Data/SqlMap/DataMapper"
files="TSqlMapException.php,TSqlMapTypeHandlerRegistry.php,TSqlMapCache.php,TPropertyAccess.php,TLazyLoadList.php,TSqlMapPagedList.php"/>
@@ -243,10 +243,65 @@ files="IMappedStatement.php,TMappedStatement.php,TCachingStatement.php,TUpdateMappedStatement.php,TDeleteMappedStatement.php,TInsertMappedStatement.php,TPreparedCommand.php,TPreparedStatement.php,TPreparedStatementFactory.php,TSelectMappedStatement.php,TSimpleDynamicSql.php,TStaticSql.php"/>
</compact-package>
<append file="framework/Data/SqlMap/DataMapper/messages.txt"
- destfile="${build.compact.dir}/messages.txt" />
+ destfile="${build.compact.dir}/prado-db/messages.txt" />
<prado-quickstart-docs output="${build.compact.dir}/docs" pages="Database/SqlMap.page" /> </target>
-
+ + <target name="compact-northwind" description="Northwind example"> + <copy todir="${build.compact.dir}/examples"> + <fileset dir="demos/northwind-db/protected/" > + <include name="database/**/*"/> + <include name="data/**/*"/> + </fileset> + </copy> + <delete file="${build.compact.dir}/examples/example.php" /> + <append destfile="${build.compact.dir}/examples/example.php"><![CDATA[<?php + +include('../prado-db/collections.php'); +include('../prado-db/db.php'); +include('../prado-db/db-sqlite.php'); +include('../prado-db/table-gateway.php'); +include('../prado-db/active-record.php'); +include('../prado-db/sqlmap.php'); + +$sqlite_dir = './data'; +$sqlite_db = $sqlite_dir.'/Northwind.db'; +if(!is_file($sqlite_db)) + die("Unable to find database file $sqlite_db"); +if(!is_writable($sqlite_dir)) + die("Please make sure that the directory $sqlite_dir is writable by PHP process."); +if(!is_writable($sqlite_db)) + die("Please make sure that the sqlite database file $sqlite_db is writable by PHP process."); + +//add directory "database" for autoload +$class_dir = realpath(dirname(__FILE__).'/database'); +set_include_path(get_include_path().PATH_SEPARATOR.$class_dir); +spl_autoload_register(array('PradoBase', 'autoload')); + +$conn = new TDbConnection("sqlite:$sqlite_db"); +//set default database connection +TActiveRecordManager::getInstance()->setDbConnection($conn); + +$manager = new TSqlMapManager($conn); +$manager->configureXml('./database/sqlmap.xml'); +$sqlmap = $manager->getSqlMapGateway(); + +//start playing + +foreach(Employee::finder()->findAll() as $employee) + var_dump($employee->LastName); + +foreach(Region::finder()->withTerritories()->findAll() as $region) +{ + var_dump($region->RegionDescription); + foreach($region->Territories as $territory) + var_dump($territory->TerritoryDescription); +} + +var_dump($sqlmap->queryForList('products-with-price', 50)); + +?>]]></append> + </target>
<target name="compact-all" description="All packages" depends="compact-active-record,compact-sqlmap" />
<!-- end compact packaging -->
diff --git a/buildscripts/phing/tasks/PradoPackageTask.php b/buildscripts/phing/tasks/PradoPackageTask.php index 8784bb77..e54a4093 100644 --- a/buildscripts/phing/tasks/PradoPackageTask.php +++ b/buildscripts/phing/tasks/PradoPackageTask.php @@ -1,5 +1,4 @@ -<?php -
+<?php
require_once 'phing/Task.php';
/**
@@ -55,7 +54,7 @@ class PradoPackageTask extends Task function processPhp($content,$files)
{
$content = preg_replace('/^\s*Prado::trace.*\s*;\s*$/mu','',$content);
- $content = preg_replace('/(PradoBase::using|Prado::using|require_once|include_once)\s*\(.*?\);/mu','',$content);
+ $content = preg_replace('/(PradoBase::using|Prado::using|require_once|include_once)\s*\([^\$].*?\);/mu','',$content);
$content = str_replace('Prado::', 'PradoBase::', $content);
$content = str_replace('PradoBase::getApplication()->getMode()', 'true', $content);
$content = str_replace('TApplicationMode::Debug', 'true', $content);
diff --git a/demos/northwind-db/protected/data/Northwind.db b/demos/northwind-db/protected/data/Northwind.db Binary files differindex 9d6b08b4..9503b1e2 100644 --- a/demos/northwind-db/protected/data/Northwind.db +++ b/demos/northwind-db/protected/data/Northwind.db diff --git a/demos/northwind-db/protected/database/sqlmap.xml b/demos/northwind-db/protected/database/sqlmap.xml new file mode 100644 index 00000000..bf98796e --- /dev/null +++ b/demos/northwind-db/protected/database/sqlmap.xml @@ -0,0 +1,107 @@ +<sqlmap>
+
+ <select id="order-subtotal">
+ SELECT
+ "Order Details".OrderID as OrderID,
+ Sum(("Order Details".UnitPrice*Quantity*(1-Discount)/100)*100) AS Subtotal
+ FROM "Order Details"
+ GROUP BY "Order Details".OrderID
+ </select>
+
+ <!-- Show all the Cities we ship to or where a supplier is located -->
+ <select id="all-cities">
+ SELECT City FROM SUPPLIERS
+ Union
+ SELECT ShipCity FROM ORDERS
+ </select>
+
+ <!-- Find Suppliers that supply the categories such as 'Produce', 'Seafood', 'Condiments' -->
+ <select id="suppliers-with" parameterClass="array">
+ SELECT SupplierID, COUNT(P.CategoryID)
+ FROM (SELECT DISTINCT SupplierID, CategoryID FROM Products) P
+ INNER Join Categories C on C.CategoryID = P.CategoryID
+ WHERE CategoryName IN ('Produce', 'Seafood', 'Condiments')
+ GROUP BY SupplierID
+ HAVING COUNT(P.CategoryID) =
+ (SELECT COUNT(CategoryID)
+ from Categories
+ WHERE CategoryName
+ IN ('Produce', 'Seafood', 'Condiments'))
+ </select>
+
+ <!-- Show Cities we ship to that also have a supplier located there -->
+ <select id="supplier-cities">
+ SELECT DISTINCT ShipCity FROM ORDERS
+ WHERE EXISTS (SELECT 1 from SUPPLIERS WHERE ShipCity = City)
+ </select>
+
+ <!-- Show Cities we ship to that do not have a supplier located there -->
+ <select id="shipping-cities">
+ SELECT DISTINCT ShipCity FROM ORDERS
+ WHERE NOT EXISTS (SELECT 1 from SUPPLIERS WHERE ShipCity = City)
+ </select>
+
+ <!-- Show all possible Supplier Product Combinations -->
+ <select id="supplier-products">
+ SELECT * FROM Suppliers S CROSS Join Products
+ </select>
+
+ <!-- Products over a certain unit price -->
+ <select id="products-with-price">
+ <![CDATA[
+ SELECT
+ p.ProductName,
+ c.CategoryName,
+ p.UnitPrice
+ FROM Products p
+ INNER JOIN Categories c ON
+ c.CategoryID = p.CategoryID
+ WHERE p.UnitPrice < #value#
+ ORDER BY CategoryName ASC, UnitPrice ASC, ProductName ASC
+ ]]>
+ </select>
+
+ <!-- employee's manager's name and number of subordinates (if the employee has a manager) -->
+ <select id="employee-subordinates">
+ SELECT
+ Employee.LastName,
+ Employee.FirstName,
+ Employee.NumberOfSubordinates,
+ Manager.LastName as ManagerLastName,
+ Manager.FirstName as ManagerFirstName,
+ Manager.NumberOfSubordinates as ManagerNumberOfSubordinates
+ FROM EmployeeSubordinatesReport Employee
+ LEFT JOIN EmployeeSubordinatesReport Manager ON
+ Employee.ReportsTo = Manager.EmployeeID
+ </select>
+
+ <select id="pivot-test">
+ SELECT
+ o.customerID,
+ c.CompanyName,
+ p.productName,
+ sum(od.quantity) as Qty
+ FROM orders o
+ INNER JOIN
+ [order details] od on o.orderID = od.orderID
+ INNER JOIN
+ Products p on od.ProductID = p.ProductID
+ INNER JOIN
+ Customers c on o.CustomerID = c.CustomerID
+ GROUP BY
+ o.customerID, c.CompanyName, p.ProductName
+ </select>
+
+ <select id="employee-sales">
+ SELECT
+ e.firstName,
+ c.CompanyName,
+ COUNT(o.orderID)
+ FROM Employees e
+ JOIN Orders o ON e.employeeID=o.employeeID
+ JOIN Customers c ON c.customerID=o.customerID
+ GROUP BY e.firstName, c.CompanyName
+ ORDER BY e.firstName, c.CompanyName
+ </select>
+
+</sqlmap>
\ No newline at end of file diff --git a/framework/Web/Javascripts/source/prado/colorpicker/colorpicker.js b/framework/Web/Javascripts/source/prado/colorpicker/colorpicker.js index 746a0caf..557b4b51 100644 --- a/framework/Web/Javascripts/source/prado/colorpicker/colorpicker.js +++ b/framework/Web/Javascripts/source/prado/colorpicker/colorpicker.js @@ -461,6 +461,8 @@ Object.extend(Prado.WebUI.TColorPicker.prototype, this.button.style.backgroundColor = color.toString();
if(typeof(this.onChange) == "function")
this.onChange(color);
+ if(this.options.OnColorSelected)
+ this.options.OnColorSelected(this,color);
},
getFullPickerContainer : function(pickerID)
diff --git a/framework/Web/Javascripts/source/prado/colorpicker/default.css b/framework/Web/Javascripts/source/prado/colorpicker/default.css index 67235c08..7bbc08d5 100644 --- a/framework/Web/Javascripts/source/prado/colorpicker/default.css +++ b/framework/Web/Javascripts/source/prado/colorpicker/default.css @@ -3,8 +3,6 @@ .TColorPicker_button
{
- border: 1px solid #919EA9;
- background-color: #fff;
}
.TColorPicker
@@ -49,23 +47,16 @@ }
/** UI **/
-.TColorPicker_button
-{
- position: absolute;
- font-size: 0;
- padding: 1px;
- margin-left: 1px;
-}
-
.TColorPicker_button img
{
- width: 18px;
- height: 18px;
-}
-
-* html .TColorPicker_button
-{
- margin-top: 1px;
+ border: 2px ridge ActiveBorder;
+ background-color: #fff;
+ padding: 1px;
+ height: 16px;
+ width: 16px;
+ margin: 1px;
+ margin-bottom: -6px;
+ margin-bottom: expression(-4); /** IE hack **/
}
.TColorPicker
diff --git a/framework/Web/UI/WebControls/TColorPicker.php b/framework/Web/UI/WebControls/TColorPicker.php index 5f0c0b03..51e51ec3 100644 --- a/framework/Web/UI/WebControls/TColorPicker.php +++ b/framework/Web/UI/WebControls/TColorPicker.php @@ -24,6 +24,8 @@ class TColorPicker extends TTextBox {
const SCRIPT_PATH = 'prado/colorpicker';
+ private $_clientSide;
+
/**
* @return boolean whether the color picker should pop up when the button is clicked.
*/
@@ -106,6 +108,24 @@ class TColorPicker extends TTextBox }
/**
+ * @return TColorPickerClientSide javascript event options.
+ */
+ public function getClientSide()
+ {
+ if(is_null($this->_clientSide))
+ $this->_clientSide = $this->createClientSide();
+ return $this->_clientSide;
+ }
+
+ /**
+ * @return TColorPickerClientSide javascript validator event options.
+ */
+ protected function createClientSide()
+ {
+ return new TColorPickerClientSide;
+ }
+
+ /**
* Get javascript color picker options.
* @return array color picker client-side options
*/
@@ -122,7 +142,7 @@ class TColorPicker extends TTextBox $options['OKButtonText'] = $this->getOKButtonText();
$options['CancelButtonText'] = $this->getCancelButtonText();
}
-
+ $options = array_merge($options,$this->getClientSide()->getOptions()->toArray());
return $options;
}
@@ -221,4 +241,33 @@ class TColorPickerMode extends TEnumerable const Full='Full';
}
+/**
+ * TColorPickerClientSide class.
+ *
+ * Client-side javascript code options.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Id$
+ * @package System.Web.UI.WebControls
+ * @since 3.1
+ */
+class TColorPickerClientSide extends TClientSideOptions
+{
+ /**
+ * @return string javascript code for when a color is selected.
+ */
+ public function getOnColorSelected()
+ {
+ return $this->getOption('OnColorSelected');
+ }
+
+ /**
+ * @param string javascript code for when a color is selected.
+ */
+ public function setOnColorSelected($javascript)
+ {
+ $this->setFunction('OnColorSelected', $javascript);
+ }
+}
+
?>
\ No newline at end of file diff --git a/framework/Web/UI/WebControls/THtmlArea.php b/framework/Web/UI/WebControls/THtmlArea.php index 91f0f033..efe3e6d9 100644 --- a/framework/Web/UI/WebControls/THtmlArea.php +++ b/framework/Web/UI/WebControls/THtmlArea.php @@ -268,9 +268,27 @@ class THtmlArea extends TTextBox return $this->getViewState('CustomPluginPath');
}
+ /**
+ * @return boolean enable compression of the javascript files, default is true.
+ */
+ public function getEnableCompression()
+ {
+ return $this->getViewState('EnableCompression', true);
+ }
+
+ /**
+ * @param boolean enable compression of the javascript files, default is true.
+ */
+ public function setEnableCompression($value)
+ {
+ $this->setViewState('EnableCompression', TPropertyValue::ensureBoolean($value));
+ }
+
public function onPreRender($param)
{
- $this->preLoadCompressedScript();
+ $this->loadJavascriptLibrary();
+ if($this->getEnableCompression())
+ $this->preLoadCompressedScript();
}
/**
@@ -310,8 +328,6 @@ class THtmlArea extends TTextBox protected function preLoadCompressedScript()
{
$scripts = $this->getPage()->getClientScript();
- if(!$scripts->isScriptFileRegistered('prado:THtmlArea'))
- $scripts->registerScriptFile('prado:THtmlArea', $this->getScriptUrl());
$key = 'prado:THtmlArea:compressed';
if(!$scripts->isBeginScriptRegistered($key))
{
@@ -326,6 +342,13 @@ class THtmlArea extends TTextBox }
}
+ protected function loadJavascriptLibrary()
+ {
+ $scripts = $this->getPage()->getClientScript();
+ if(!$scripts->isScriptFileRegistered('prado:THtmlArea'))
+ $scripts->registerScriptFile('prado:THtmlArea', $this->getScriptUrl());
+ }
+
/**
* Registers the editor javascript file and code to initialize the editor.
*/
@@ -342,7 +365,10 @@ class THtmlArea extends TTextBox */
protected function getScriptUrl()
{
- return $this->getScriptDeploymentPath().'/tiny_mce/tiny_mce_gzip.js';
+ if($this->getEnableCompression())
+ return $this->getScriptDeploymentPath().'/tiny_mce/tiny_mce_gzip.js';
+ else
+ return $this->getScriptDeploymentPath().'/tiny_mce/tiny_mce.js';
}
/**
diff --git a/tests/FunctionalTests/tickets/protected/pages/Ticket609.page b/tests/FunctionalTests/tickets/protected/pages/Ticket609.page new file mode 100644 index 00000000..741f4d65 --- /dev/null +++ b/tests/FunctionalTests/tickets/protected/pages/Ticket609.page @@ -0,0 +1,11 @@ +<com:TContent ID="Content">
+
+<com:TLabel ForControl="foo" Text="Choose Color:" />
+<com:TColorPicker ID="foo">
+ <prop:ClientSide.OnColorSelected>
+ alert(parameter);
+ </prop:ClientSide.OnColorSelected>
+</com:TColorPicker>
+Description
+
+</com:TContent>
\ No newline at end of file |