initial commit

This commit is contained in:
Michael Becker 2023-10-31 11:47:27 -04:00
parent e19df88193
commit a0dcc72d8c
957 changed files with 92463 additions and 0 deletions

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DBEE1BAC-D658-420D-96D6-417523326650}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Mocha.Compiler</RootNamespace>
<AssemblyName>mcc</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>..\..\Output\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\UniversalEditor\Libraries\UniversalEditor.Core\UniversalEditor.Core.csproj">
<Project>{2D4737E6-6D95-408A-90DB-8DFF38147E85}</Project>
<Name>UniversalEditor.Core</Name>
</ProjectReference>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Plugins.Mocha\UniversalEditor.Plugins.Mocha.csproj">
<Project>{3D0893DC-9961-4EAA-BF6C-604686068D09}</Project>
<Name>UniversalEditor.Plugins.Mocha</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\UniversalEditor\Libraries\UniversalEditor.Essential\UniversalEditor.Essential.csproj">
<Project>{30467E5C-05BC-4856-AADC-13906EF4CADD}</Project>
<Name>UniversalEditor.Essential</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\MBS.Framework\MBS.Framework\MBS.Framework.csproj">
<Project>{00266B21-35C9-4A7F-A6BA-D54D7FDCC25C}</Project>
<Name>MBS.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Libraries\Mocha.MSBuild.Tasks\Mocha.MSBuild.Tasks.csproj">
<Project>{1D5668E8-4540-426B-922A-E3A739D16713}</Project>
<Name>Mocha.MSBuild.Tasks</Name>
</ProjectReference>
<ProjectReference Include="..\..\Libraries\Mocha.Core\Mocha.Core.csproj">
<Project>{8D3211A6-B2D6-4A26-ABE3-5B57636A4196}</Project>
<Name>Mocha.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="test.xml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,203 @@
//
// Program.cs - main entry point for the ZeQuaL compiler (zq)
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2020 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using Mocha.Core;
using UniversalEditor;
using UniversalEditor.Accessors;
using UniversalEditor.Plugins.Mocha.DataFormats.MochaBinary;
using UniversalEditor.Plugins.Mocha.DataFormats.MochaXML;
using UniversalEditor.Plugins.Mocha.ObjectModels.MochaClassLibrary;
namespace Mocha.Compiler
{
public class Program
{
public static void Main(string[] args)
{
List<string> listFileNames = new List<string>();
string outputFileName = "output.mcx";
bool foundFileName = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i].StartsWith("/") && !foundFileName)
{
if (args[i].StartsWith("/out:"))
{
outputFileName = args[i].Substring(5);
}
}
else
{
// is file name
foundFileName = true;
listFileNames.Add(args[i]);
}
}
listFileNames.Sort();
MochaClassLibraryObjectModel mcl = new MochaClassLibraryObjectModel();
MochaBinaryDataFormat mcx = new MochaBinaryDataFormat();
MochaXMLDataFormat xml = new MochaXMLDataFormat();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
int totalInstances = 0, totalRelationships = 0;
for (int i = 0; i < listFileNames.Count; i++)
{
Console.Error.WriteLine("reading {0}", listFileNames[i]);
if (!System.IO.File.Exists(listFileNames[i]))
{
MBS.Framework.ConsoleExtensions.LogMSBuildMessage(MBS.Framework.MessageSeverity.Error, "File not found", "MCX0003", listFileNames[i]);
continue;
}
MochaClassLibraryObjectModel mcl1 = new MochaClassLibraryObjectModel();
FileAccessor fain = new FileAccessor(listFileNames[i]);
Document.Load(mcl1, xml, fain);
for (int j = 0; j < mcl1.Libraries.Count; j++)
{
mcl.Libraries.Merge(mcl1.Libraries[j]);
totalInstances += mcl1.Libraries[j].Instances.Count;
totalRelationships += mcl1.Libraries[j].Relationships.Count;
}
for (int j = 0; j < mcl1.Tenants.Count; j++)
{
Console.Error.WriteLine("registered tenant {0}", mcl1.Tenants[j].Name);
mcl.Tenants.Merge(mcl1.Tenants[j]);
}
}
ZqLinkRelationships(mcl);
Console.Error.WriteLine("wrote {0} libraries with {1} instances and {2} relationships total", mcl.Libraries.Count, totalInstances, totalRelationships);
/*
// LINKER SANITY CHECK
// we need to load all the referenced MCX libraries in a testing
// environment and check MCX relationships for existing instances
foreach (MochaLibrary library in mcl.Libraries)
{
CheckInstancesForMochaStore(mcl, library);
}
foreach (MochaTenant tenant in mcl.Tenants)
{
CheckInstancesForMochaStore(mcl, tenant);
}
*/
FileAccessor faout = new FileAccessor(outputFileName, true, true);
Document.Save(mcl, mcx, faout);
}
private static void ZqLinkRelationships(MochaClassLibraryObjectModel mcl)
{
for (int i = 0; i < mcl.Libraries.Count; i++)
{
ZqLinkRelationships(mcl, mcl.Libraries[i]);
}
for (int i = 0; i < mcl.Tenants.Count; i++)
{
ZqLinkRelationships(mcl, mcl.Tenants[i]);
}
}
private static void ZqLinkRelationships(MochaClassLibraryObjectModel mcl, IMochaStore store)
{
for (int j = 0; j < store.Relationships.Count; j++)
{
MochaInstance instRel = mcl.FindInstance(store.Relationships[j].RelationshipInstanceID);
if (instRel != null)
{
MochaRelationship relSiblingRel = mcl.FindRelationship(new RelationshipKey(instRel.ID, KnownRelationshipGuids.Relationship__has_sibling__Relationship));
if (relSiblingRel != null)
{
MochaInstance instSibling = mcl.FindInstance(relSiblingRel.DestinationInstanceIDs[0]);
if (instSibling != null)
{
for (int k = 0; k < store.Relationships[j].DestinationInstanceIDs.Count; k++)
{
RelationshipKey rkSibling = new RelationshipKey(store.Relationships[j].DestinationInstanceIDs[k], instSibling.ID);
MochaRelationship relSibling = store.Relationships[rkSibling];
if (relSibling == null)
{
Console.Error.WriteLine("zq2: linking relationship {0}", instSibling.ID.ToString("B"));
relSibling = new MochaRelationship() { SourceInstanceID = store.Relationships[j].DestinationInstanceIDs[k], RelationshipInstanceID = instSibling.ID };
store.Relationships.Add(relSibling);
}
if (!relSibling.DestinationInstanceIDs.Contains(store.Relationships[j].SourceInstanceID))
{
relSibling.DestinationInstanceIDs.Add(store.Relationships[j].SourceInstanceID);
}
}
}
else
{
Console.Error.WriteLine("zq2: no sibling relationship '{0}' found", relSiblingRel.DestinationInstanceIDs[0].ToString("B"));
}
}
}
}
}
private static void CheckInstancesForMochaStore(MochaClassLibraryObjectModel mcl, IMochaStore library)
{
foreach (MochaRelationship rel in library.Relationships)
{
if (FindInstance(mcl, library, rel.SourceInstanceID) == null)
{
MBS.Framework.ConsoleExtensions.LogMSBuildMessage(MBS.Framework.MessageSeverity.Error, String.Format("relationship references nonexistent sourceInstanceId '{0}'", rel.SourceInstanceID));
}
if (FindInstance(mcl, library, rel.RelationshipInstanceID) == null)
{
MBS.Framework.ConsoleExtensions.LogMSBuildMessage(MBS.Framework.MessageSeverity.Error, String.Format("relationship references nonexistent relationshipInstanceId '{0}'", rel.RelationshipInstanceID));
}
foreach (Guid id in rel.DestinationInstanceIDs)
{
if (FindInstance(mcl, library, id) == null)
{
MBS.Framework.ConsoleExtensions.LogMSBuildMessage(MBS.Framework.MessageSeverity.Error, String.Format("relationship references nonexistent target instanceReference '{0}'", id));
}
}
}
}
private static MochaInstance FindInstance(MochaClassLibraryObjectModel mcl, IMochaStore store, Guid id)
{
MochaInstance inst = store.FindInstance(id);
if (inst != null)
{
return inst;
}
foreach (Guid libraryId in store.LibraryReferences)
{
inst = mcl.Libraries[libraryId].FindInstance(id);
if (inst != null)
return inst;
}
return null;
}
}
}

View File

@ -0,0 +1,46 @@
//
// AssemblyInfo.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2020 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Mocha.Compiler")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Mike Becker's Software")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Mike Becker's Software")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

View File

@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.0", FrameworkDisplayName = ".NET Framework 4")]

View File

@ -0,0 +1 @@
5cdeef7b70eda6eae969492610195a3ac3d59ee6

View File

@ -0,0 +1,7 @@
/home/beckermj/Documents/Projects/Mocha/Output/Debug/mcc.exe
/home/beckermj/Documents/Projects/Mocha/Output/Debug/mcc.pdb
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Compiler/obj/Debug/Mocha.Compiler.csproj.AssemblyReference.cache
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Compiler/obj/Debug/Mocha.Compiler.csproj.CoreCompileInputs.cache
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Compiler/obj/Debug/Mocha.Compiler.csproj.CopyComplete
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Compiler/obj/Debug/mcc.exe
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Compiler/obj/Debug/mcc.pdb

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,369 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
//
// LoginPage.xml - XML definition for the initial Mocha `Login Page` system page
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2020 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<mocha xmlns="urn:net.alcetech.schemas.Mocha">
<libraries>
<library id="{1c8cfb35-24a8-4b1f-9b44-7cde72a7168e}">
<instances>
<instance id="{E753D577-90AA-44EE-90A9-99D002B43367}" classInstanceId="{C269A1F3-E014-4230-B78D-38EAF6EA8A81}">
<!-- Style Rule -->
<relationships>
<relationship relationshipInstanceId="{B69C2708-E78D-413A-B491-ABB6F1D2A6E0}">
<!-- Style Rule.has Style Property -->
<targetInstances>
<instanceReference instanceId="{1AE4BE55-312D-461C-9A4B-5F3F13CCF7F9}" />
</targetInstances>
</relationship>
</relationships>
<attributeValues>
<attributeValue attributeInstanceId="&IDA_Value;" value="center" />
</attributeValues>
</instance>
<instance id="{372D78D0-ED49-4C16-9841-084AC3224A19}" classInstanceId="{C269A1F3-E014-4230-B78D-38EAF6EA8A81}">
<!-- Style Rule -->
<relationships>
<relationship relationshipInstanceId="{B69C2708-E78D-413A-B491-ABB6F1D2A6E0}">
<!-- Style Rule.has Style Property -->
<targetInstances>
<instanceReference instanceId="{F936088C-8D65-4476-BDBC-421EE8BC41A9}" />
</targetInstances>
</relationship>
</relationships>
<attributeValues>
<attributeValue attributeInstanceId="&IDA_Value;" value="14pt" />
</attributeValues>
</instance>
<instance id="{C7C4790B-4D81-4E1E-965F-1DD1A7D788F0}" classInstanceId="{C269A1F3-E014-4230-B78D-38EAF6EA8A81}">
<!-- Style Rule -->
<relationships>
<relationship relationshipInstanceId="{B69C2708-E78D-413A-B491-ABB6F1D2A6E0}">
<!-- Style Rule.has Style Property -->
<targetInstances>
<instanceReference instanceId="{354D0A44-6816-4E65-BC84-4910CE0CE6F5}" />
</targetInstances>
</relationship>
</relationships>
<attributeValues>
<attributeValue attributeInstanceId="&IDA_Value;" value="16px" />
</attributeValues>
</instance>
<instance id="{EED15C64-0279-4219-A920-E9635AA84468}" classInstanceId="{A48C843A-B24B-4BC3-BE6F-E2D069229B0A}">
<!-- Style: Login Page Header Style -->
<translations>
<translation relationshipInstanceId="{963233D5-88F6-41F0-9DE0-63BAB95FA228}">
<!-- Style.has title Translation -->
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Login Header Text Style" />
</translationValues>
</translation>
</translations>
<relationships>
<relationship relationshipInstanceId="{B69C2708-E78D-413A-B491-ABB6F1D2A6E0}">
<!-- Style.has Style Property -->
<targetInstances>
<instanceReference instanceId="{E753D577-90AA-44EE-90A9-99D002B43367}" />
<instanceReference instanceId="{372D78D0-ED49-4C16-9841-084AC3224A19}" />
<instanceReference instanceId="{C7C4790B-4D81-4E1E-965F-1DD1A7D788F0}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{F0C52DD4-B074-49AE-A96F-5F907359DB6D}" classInstanceId="{ADFF93CE-9E85-4168-A7D4-5239B99BE36D}">
<!-- Paragraph Page Component: -->
<relationships>
<relationship relationshipInstanceId="{818CFF50-7D42-43B2-B6A7-92C3C54D450D}">
<!-- Page Component.has Style -->
<targetInstances>
<instanceReference instanceId="{EED15C64-0279-4219-A920-E9635AA84468}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="{0E002E6F-AA79-457C-93B8-2CCE1AEF5F7E}">
<!-- Text Page Component.gets content from Method -->
<targetInstances>
<!-- Tenant@get Login Page Header Text -->
<instanceReference instanceId="{781e25f0-28cc-4590-9fe9-54f449494d44}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{A77F672B-7F9C-4F20-B5CE-1990529407F4}" classInstanceId="{798B67FA-D4BE-42B9-B4BD-6F8E02C953C0}">
<relationships>
<relationship relationshipInstanceId="{481E3FBE-B82A-4C76-9DDF-D66C6BA8C590}">
<targetInstances>
<!--
this USED TO BE a hardcoded reference to a File on a particular Tenant
in other words, a GSI - Get Specific Instance method returning File
{E6A5D3B3-7E81-41FD-9F5C-1D460175C60E}
this IS NOW a reference to a method, Tenant@get Logo Image File
which is a GRS - Get Referenced Instance Set using relationship
`Tenant.has logo image File` for the instance `Current Tenant`
-->
<instanceReference instanceId="{4f0ecfdd-e991-4d9c-9ac9-5afe218637a1}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{c5027dc2-53ee-4fc0-9ba6-f2b883f7dad8}" classInstanceId="&IDC_Translation;">
<relationships>
<relationship relationshipInstanceId="&IDR_Translation__has__Translation_Value;">
<targetInstances>
<instanceReference instanceId="{29C02384-57B0-45F5-9C15-747F9DFD2C69}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{29C02384-57B0-45F5-9C15-747F9DFD2C69}" classInstanceId="&IDC_TranslationValue;">
<attributeValues>
<attributeValue attributeInstanceId="&IDA_Value;" value="Please log in to continue" />
</attributeValues>
<relationships>
<relationship relationshipInstanceId="&IDR_Translation_Value__has__Language;">
<targetInstances>
<instanceReference instanceId="&IDI_Language_English;" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{ed1093d2-3522-4687-869b-d84d99b70aab}" classInstanceId="&IDC_ReturnInstanceSetMethodBinding;">
</instance>
<instance id="{de0650d4-06a4-4be7-a2ba-8fd8bb26b917}" classInstanceId="&IDC_GetSpecifiedInstancesMethod;">
<attributeValues>
<attributeValue attributeInstanceId="&IDA_Verb;" value="get" />
</attributeValues>
<translations>
<translation relationshipInstanceId="{52B65829-4A3F-44FB-BEE8-D9A240F1E9C9}">
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Default Login Page Welcome Text" />
</translationValues>
</translation>
</translations>
<relationships>
<relationship relationshipInstanceId="{dea1aa0b-2bef-4bac-b4f9-0ce8cf7006fc}">
<!-- GSI Method.has Instance -->
<targetInstances>
<!-- build in TTC "Please log in to continue" -->
<instanceReference instanceId="{c5027dc2-53ee-4fc0-9ba6-f2b883f7dad8}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="&IDR_Method__has__Method_Binding;">
<!-- Method.has Method Binding return instance set method binding [rsmb] -->
<targetInstances>
<instanceReference instanceId="{ed1093d2-3522-4687-869b-d84d99b70aab}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="&IDR_Method__for__Class;">
<targetInstances>
<instanceReference instanceId="{34241fb3-ca55-4e23-80c2-5e0058311657}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{23164023-03A8-4DB2-9C15-DB8321F3486B}" classInstanceId="{FD86551E-E4CE-4B8B-95CB-BEC1E6A0EE2B}">
<attributeValues>
<!-- Level [Numeric Attribute] -->
<attributeValue attributeInstanceId="{8C528FB0-4063-47B0-BC56-85E387A41BD2}" value="4" />
</attributeValues>
<relationships>
<relationship relationshipInstanceId="{0E002E6F-AA79-457C-93B8-2CCE1AEF5F7E}">
<targetInstances>
<instanceReference instanceId="{de0650d4-06a4-4be7-a2ba-8fd8bb26b917}" />
</targetInstances>
</relationship>
</relationships>
<translations>
<translation relationshipInstanceId="{C5027DC2-53EE-4FC0-9BA6-F2B883F7DAD8}">
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Please sign in to access this feature PAGEBUILDER TEST" />
</translationValues>
</translation>
</translations>
</instance>
<instance id="{4f416621-fb60-4349-988e-7c876e38c6a8}" classInstanceId="{5EBA7BD6-BA0A-45B2-835C-C92489FD7E74}">
<!-- SummaryPageComponent, Editable=True -->
<attributeValues>
<!-- Editable (Boolean Attribute) -->
<attributeValue attributeInstanceId="{957fd8b3-fdc4-4f35-87d6-db1c0682f53c}" value="true" />
</attributeValues>
</instance>
<instance id="{DF0FCBC5-E1E5-40AD-B1FF-FD001FB680F6}" classInstanceId="{ADFF93CE-9E85-4168-A7D4-5239B99BE36D}">
<relationships>
<relationship relationshipInstanceId="{0E002E6F-AA79-457C-93B8-2CCE1AEF5F7E}">
<!-- Text Page Component.gets content from Method -->
<targetInstances>
<!-- Tenant@get Login Page Footer Text -->
<instanceReference instanceId="{52bc7c5a-359a-4843-b807-809e3763c56a}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{10C3078C-F2FC-4B8E-9875-450449549351}" classInstanceId="{A48C843A-B24B-4BC3-BE6F-E2D069229B0A}">
<!-- Style: Login Page Header Style -->
<translations>
<translation relationshipInstanceId="{963233D5-88F6-41F0-9DE0-63BAB95FA228}">
<!-- Style.has title Translation -->
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Primary Theme Color Style" />
</translationValues>
</translation>
</translations>
<integrationIDs>
<integrationID name="StyleName" value="LoginPage" />
</integrationIDs>
<attributeValues>
<attributeValue attributeInstanceId="{0F42BCB6-549A-4407-BB91-DD147443F68F}" value="Primary" />
</attributeValues>
</instance>
<instance id="{3A52305F-4D96-44F9-B50C-4AC1A75DB1B8}" classInstanceId="{F480787D-F51E-498A-8972-72128D808AEB}">
<translations>
<translation relationshipInstanceId="{C25230B1-4D23-4CFE-8B75-56C33E8293AF}">
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Log In" />
</translationValues>
</translation>
</translations>
</instance>
<instance id="{4DEEAB31-9A57-4CDB-9770-6AFDF19F153C}" classInstanceId="{F480787D-F51E-498A-8972-72128D808AEB}">
<translations>
<translation relationshipInstanceId="{C25230B1-4D23-4CFE-8B75-56C33E8293AF}">
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Cancel" />
</translationValues>
</translation>
</translations>
</instance>
<instance id="{8F9C429D-2FF5-46E1-B906-CD1D3DFF7BDE}" classInstanceId="{D349C489-9684-4A5A-9843-B906A7F803BC}">
<!-- Panel Page Component: -->
<relationships>
<relationship relationshipInstanceId="{AD8C5FAE-2444-4700-896E-C5F968C0F85B}">
<targetInstances>
<instanceReference instanceId="{23164023-03A8-4DB2-9C15-DB8321F3486B}" />
<instanceReference instanceId="{4f416621-fb60-4349-988e-7c876e38c6a8}" />
<instanceReference instanceId="{DF0FCBC5-E1E5-40AD-B1FF-FD001FB680F6}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="{56E339BD-6189-4BAC-AB83-999543FB8060}">
<targetInstances>
<instanceReference instanceId="{3A52305F-4D96-44F9-B50C-4AC1A75DB1B8}" />
<instanceReference instanceId="{4DEEAB31-9A57-4CDB-9770-6AFDF19F153C}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="{818CFF50-7D42-43B2-B6A7-92C3C54D450D}">
<targetInstances>
<instanceReference instanceId="{10C3078C-F2FC-4B8E-9875-450449549351}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{E7C69CF3-CB71-43D8-91AB-B8F5030350AB}" classInstanceId="{A66D9AE2-3BEC-4083-A5CB-7DE3B03A9CC7}">
<!-- Sequential Container Page Component: -->
<relationships>
<relationship relationshipInstanceId="&IDR_Sequential_Container_Page_Component__has__Sequential_Container_Orientation;">
<targetInstances>
<instanceReference instanceId="{9B7B7F14-0925-456D-98E6-E3FFEFDC272C}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="{CB7B8162-1C9E-4E72-BBB8-C1C37CA69CD5}">
<!-- Container Page Component.has Page Component -->
<targetInstances>
<instanceReference instanceId="{A77F672B-7F9C-4F20-B5CE-1990529407F4}" />
<instanceReference instanceId="{F0C52DD4-B074-49AE-A96F-5F907359DB6D}" />
<instanceReference instanceId="{8F9C429D-2FF5-46E1-B906-CD1D3DFF7BDE}" />
</targetInstances>
</relationship>
</relationships>
</instance>
<instance id="{96B81CE8-DEDA-4EFF-BF5F-0F988ABCC3EC}" classInstanceId="{A48C843A-B24B-4BC3-BE6F-E2D069229B0A}">
<integrationIDs>
<integrationID name="StyleName" value="LoginPage" />
</integrationIDs>
<attributeValues>
<attributeValue attributeInstanceId="{0F42BCB6-549A-4407-BB91-DD147443F68F}" value="LoginPage" />
</attributeValues>
<translations>
<translation relationshipInstanceId="{963233D5-88F6-41F0-9DE0-63BAB95FA228}">
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Login Page Style" />
</translationValues>
</translation>
</translations>
</instance>
<instance id="{9E272BC3-0358-4EB7-8B3B-581964A59634}" classInstanceId="{D9626359-48E3-4840-A089-CD8DA6731690}">
<integrationIDs>
<integrationID name="PageName" value="LoginPage" />
</integrationIDs>
<translations>
<translation relationshipInstanceId="{7BE6522A-4BE8-4CD3-8701-C8353F7DF630}">
<!-- Page.has title Translation -->
<translationValues>
<translationValue languageInstanceId="&IDI_Language_English;" value="Login Page" />
</translationValues>
</translation>
</translations>
<relationships>
<relationship relationshipInstanceId="{6E6E1A85-3EA9-4939-B13E-CBF645CB8B59}">
<!-- Page.has Style -->
<targetInstances>
<instanceReference instanceId="{96B81CE8-DEDA-4EFF-BF5F-0F988ABCC3EC}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="{24F6C596-D77D-4754-B023-00321DEBA924}">
<!-- Page.has Page Component -->
<targetInstances>
<instanceReference instanceId="{E7C69CF3-CB71-43D8-91AB-B8F5030350AB}" />
</targetInstances>
</relationship>
<relationship relationshipInstanceId="{15199c49-9595-4288-846d-13b0ad5dcd4b}">
<!-- Securable Object.secured by domains from Method -->
<targetInstances>
<!-- Anyone: {01a86f06-9f1e-45ce-ad0b-24655baa936a} -->
<!-- Authenticated Users: {843dcb86-be54-4c81-9ff7-8235215623ba} -->
<!-- Security Domain@get Anyone instance -->
<instanceReference instanceId="{01a86f06-9f1e-45ce-ad0b-24655baa936a}" />
</targetInstances>
</relationship>
</relationships>
<attributeValues>
<attributeValue attributeInstanceId="{970F79A0-9EFE-4E7D-9286-9908C6F06A67}" value="account/login" />
</attributeValues>
</instance>
</instances>
</library>
</libraries>
</mocha>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FF61C373-835F-4160-8499-30324E8B9909}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Mocha.Debugger</RootNamespace>
<AssemblyName>Mocha.Debugger</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
<AssemblyName>mcxdebug</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\Mocha.OMS\Mocha.OMS.csproj">
<Project>{8588928F-2868-4248-8993-533307A05C0C}</Project>
<Name>Mocha.OMS</Name>
</ProjectReference>
<ProjectReference Include="..\..\Libraries\Mocha.Storage\Mocha.Storage.csproj">
<Project>{C9A3AE1D-0658-4A70-BC48-9D8DEF9C205B}</Project>
<Name>Mocha.Storage</Name>
</ProjectReference>
<ProjectReference Include="..\..\Libraries\Mocha.Storage.Local\Mocha.Storage.Local.csproj">
<Project>{A4B8D6D8-3365-49FC-8123-58B3749A5427}</Project>
<Name>Mocha.Storage.Local</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\MBS.Networking\Plugins\MBS.Networking.Plugins.HyperTextTransfer\MBS.Networking.Plugins.HyperTextTransfer.csproj">
<Project>{5743EE32-F3ED-4162-A0E3-B105503BF139}</Project>
<Name>MBS.Networking.Plugins.HyperTextTransfer</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\MBS.Networking\Libraries\MBS.Networking\MBS.Networking.csproj">
<Project>{DBD65B3F-81C8-4E44-B268-3FAB3B12AA1E}</Project>
<Name>MBS.Networking</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\MBS.Framework\MBS.Framework\MBS.Framework.csproj">
<Project>{00266B21-35C9-4A7F-A6BA-D54D7FDCC25C}</Project>
<Name>MBS.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Libraries\Mocha.Core\Mocha.Core.csproj">
<Project>{8D3211A6-B2D6-4A26-ABE3-5B57636A4196}</Project>
<Name>Mocha.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,175 @@
//
// Program.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Net.Sockets;
using System.Text;
using Mocha.Core;
using Mocha.OMS;
namespace Mocha.Debugger
{
class MainClass
{
private static Oms _CurrentOms = null;
public static void Main(string[] args)
{
_CurrentOms = InitializeOms();
_CurrentOms.TenantName = "default";
System.Net.Sockets.TcpListener listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, 63320);
listener.Start();
while (true)
{
System.Net.Sockets.TcpClient client = listener.AcceptTcpClient();
System.Threading.Thread t = new System.Threading.Thread(t_ParameterizedThreadStart);
t.Start(client);
}
}
public static Oms InitializeOms()
{
Oms oms = null;
bool useLocalOms = true;
if (useLocalOms)
{
oms = new LocalOms();
(oms as LocalOms).Environment = new OmsEnvironment(new Storage.Local.LocalStorageProvider("/usr/share/mocha/system"));
oms.Environment.Initialize();
oms.Environment.StorageProvider.DefaultTenantName = oms.TenantName;
}
else
{
/*
// attempt to connect
oms = new OMSClient();
string serverName = Mocha.Web.Configuration.ConfigurationSections.OMS.OMSConfigurationSection.Settings.Server.HostName;
int portNumber = Mocha.Web.Configuration.ConfigurationSections.OMS.OMSConfigurationSection.Settings.Server.PortNumber;
System.Net.IPHostEntry entry = System.Net.Dns.GetHostEntry(serverName);
if (entry != null)
{
try
{
oms.Connect(entry.AddressList[0], portNumber);
}
catch (System.Net.Sockets.SocketException ex)
{
// OMSUnavailable.Visible = true;
}
}
*/
}
return oms;
}
private static void t_ParameterizedThreadStart(object parm)
{
System.Net.Sockets.TcpClient client = (System.Net.Sockets.TcpClient)parm;
System.IO.StreamReader sr = new System.IO.StreamReader(client.GetStream());
if (sr.EndOfStream)
return;
string firstline = sr.ReadLine();
if (String.IsNullOrEmpty(firstline))
return;
MBS.Networking.Protocols.HyperTextTransfer.Request req = MBS.Networking.Protocols.HyperTextTransfer.Request.Parse(firstline);
if (req.Method == "GET")
{
Uri uri = new Uri(String.Format("http://localhost:63320{0}", req.Path));
System.Collections.Specialized.NameValueCollection nvc = System.Web.HttpUtility.ParseQueryString(uri.Query);
string reff = nvc["ref"];
if (!String.IsNullOrEmpty(reff))
{
if (Guid.TryParse(reff, out Guid guid))
{
lock (_CurrentOms)
{
if (nvc["tenant"] != null)
{
_CurrentOms.TenantName = nvc["tenant"];
}
else
{
_CurrentOms.TenantName = "default";
}
Instance inst = _CurrentOms.GetInstance(guid);
if (inst != null)
{
Instance instDefinition = _CurrentOms.GetRelatedInstance(inst, KnownRelationshipGuids.Instance__has__Instance_Definition);
// FIXME: we don't know how to pass the project name into this
string rootpath = "/home/beckermj/Documents/Projects/Mocha.0/Content/Mocha.System";
string filename = _CurrentOms.GetAttributeValue<string>(instDefinition, KnownAttributeGuids.Text.DebugDefinitionFileName);
decimal linenum = _CurrentOms.GetAttributeValue<decimal>(instDefinition, KnownAttributeGuids.Numeric.DebugDefinitionLineNumber);
decimal colnum = _CurrentOms.GetAttributeValue<decimal>(instDefinition, KnownAttributeGuids.Numeric.DebugDefinitionColumnNumber);
string args = String.Format("\"{0}/{1}\";{2};{3}", rootpath, filename, linenum, colnum);
Console.WriteLine("mcxdebug: launching monodevelop {0}", args);
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo("monodevelop", args);
psi.UseShellExecute = true;
System.Diagnostics.Process.Start(psi);
RespondWith(client, _CurrentOms.GetInstanceText(inst), _CurrentOms.GetInstanceText(_CurrentOms.GetRelatedInstance(inst, KnownRelationshipGuids.Instance__for__Module)));
return;
}
}
}
}
}
while (!sr.EndOfStream)
{
string nextline = sr.ReadLine();
if (String.IsNullOrEmpty(nextline))
break;
string[] nextparts = nextline.Split(new char[] { ':' }, 2);
}
}
private static void RespondWith(TcpClient client, string nodename, string projname)
{
StringBuilder resp = new StringBuilder();
resp.AppendLine("HTTP/1.1 200 OK");
resp.AppendLine();
resp.AppendLine("<html><head><title>Mocha HTTP Debugger Plugin</title></head><body>");
resp.AppendLine("<strong>Mocha HTTP Debugger Plugin</strong>");
resp.AppendLine("<p>The requested node has been opened in your IDE</p>");
resp.AppendLine(String.Format("<p><strong>Node:</strong> {0}</p><p><strong>Project:</strong> {1}</p>", nodename, projname));
resp.AppendLine("</body></html>");
resp.AppendLine();
byte[] respdata = Encoding.UTF8.GetBytes(resp.ToString());
client.GetStream().Write(respdata, 0, respdata.Length);
client.Close();
}
}
}

View File

@ -0,0 +1,46 @@
//
// AssemblyInfo.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Mocha.Debugger")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Mike Becker's Software")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Mike Becker's Software")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

View File

@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7", FrameworkDisplayName = ".NET Framework 4.7")]

View File

@ -0,0 +1 @@
65bc769232bbd0a8cc10bbb13b101907578febc4

View File

@ -0,0 +1,7 @@
/home/beckermj/Documents/Projects/Mocha/Output/Debug/mcxdebug.exe
/home/beckermj/Documents/Projects/Mocha/Output/Debug/mcxdebug.pdb
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Debugger/obj/Debug/Mocha.Debugger.csproj.AssemblyReference.cache
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Debugger/obj/Debug/Mocha.Debugger.csproj.CoreCompileInputs.cache
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Debugger/obj/Debug/Mocha.Debugger.csproj.CopyComplete
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Debugger/obj/Debug/mcxdebug.exe
/home/beckermj/Documents/Projects/Mocha/Applications/Mocha.Debugger/obj/Debug/mcxdebug.pdb

View File

@ -0,0 +1,11 @@
namespace dotless.Core.Cache
{
using System.Collections.Generic;
public interface ICache
{
void Insert(string cacheKey, IEnumerable<string> fileDependancies, string css);
bool Exists(string cacheKey);
string Retrieve(string cacheKey);
}
}

View File

@ -0,0 +1,32 @@
namespace dotless.Core.Cache
{
using System.Collections.Generic;
public class InMemoryCache : ICache
{
private readonly Dictionary<string, string> _cache;
public InMemoryCache()
{
_cache = new Dictionary<string, string>();
}
public void Insert(string fileName, IEnumerable<string> imports, string css)
{
_cache[fileName] = css;
}
public bool Exists(string filename)
{
return _cache.ContainsKey(filename);
}
public string Retrieve(string filename)
{
if (_cache.ContainsKey(filename))
return _cache[filename];
return "";
}
}
}

View File

@ -0,0 +1,78 @@
namespace dotless.Core
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Cache;
using Loggers;
public class CacheDecorator : ILessEngine
{
public readonly ILessEngine Underlying;
public readonly ICache Cache;
public ILogger Logger { get; set; }
public CacheDecorator(ILessEngine underlying, ICache cache) : this(underlying, cache, NullLogger.Instance)
{}
public CacheDecorator(ILessEngine underlying, ICache cache, ILogger logger)
{
Underlying = underlying;
Cache = cache;
Logger = logger;
}
public string TransformToCss(string source, string fileName)
{
//Compute Cache Key
var hash = ComputeContentHash(source);
var cacheKey = fileName + hash;
if (!Cache.Exists(cacheKey))
{
Logger.Debug(String.Format("Inserting cache entry for {0}", cacheKey));
var css = Underlying.TransformToCss(source, fileName);
var dependancies = new[] { fileName }.Concat(GetImports());
Cache.Insert(cacheKey, dependancies, css);
return css;
}
Logger.Debug(String.Format("Retrieving cache entry {0}", cacheKey));
return Cache.Retrieve(cacheKey);
}
private string ComputeContentHash(string source)
{
SHA1 sha1 = SHA1.Create();
byte[] computeHash = sha1.ComputeHash(Encoding.Default.GetBytes(source));
return Convert.ToBase64String(computeHash);
}
public IEnumerable<string> GetImports()
{
return Underlying.GetImports();
}
public void ResetImports()
{
Underlying.ResetImports();
}
public bool LastTransformationSuccessful
{
get
{
return Underlying.LastTransformationSuccessful;
}
}
public string CurrentDirectory
{
get { return Underlying.CurrentDirectory; }
set { Underlying.CurrentDirectory = value; }
}
}
}

View File

@ -0,0 +1,15 @@
namespace dotless.Core
{
using System.Collections.Generic;
using System;
public interface ILessEngine
{
string TransformToCss(string source, string fileName);
void ResetImports();
IEnumerable<string> GetImports();
bool LastTransformationSuccessful { get; }
string CurrentDirectory { get; set; }
}
}

View File

@ -0,0 +1,145 @@
using System;
using dotless.Core.Parser;
using dotless.Core.Parser.Tree;
using dotless.Core.Plugins;
using dotless.Core.Stylizers;
namespace dotless.Core
{
using System.Collections.Generic;
using System.Linq;
using Exceptions;
using Loggers;
using Parser.Infrastructure;
public class LessEngine : ILessEngine
{
public Parser.Parser Parser { get; set; }
public ILogger Logger { get; set; }
public bool Compress { get; set; }
public bool Debug { get; set; }
[Obsolete("The Variable Redefines feature has been removed to align with less.js")]
public bool DisableVariableRedefines { get; set; }
[Obsolete("The Color Compression feature has been removed to align with less.js")]
public bool DisableColorCompression { get; set; }
public bool KeepFirstSpecialComment { get; set; }
public bool StrictMath { get; set; }
public Env Env { get; set; }
public IEnumerable<IPluginConfigurator> Plugins { get; set; }
public bool LastTransformationSuccessful { get; private set; }
public string CurrentDirectory
{
get { return Parser.CurrentDirectory; }
set { Parser.CurrentDirectory = value; }
}
public LessEngine(Parser.Parser parser, ILogger logger, dotless.Core.configuration.DotlessConfiguration config)
: this(parser, logger, config.MinifyOutput, config.Debug, config.DisableVariableRedefines, config.DisableColorCompression, config.KeepFirstSpecialComment, config.Plugins)
{
}
public LessEngine(Parser.Parser parser, ILogger logger, bool compress, bool debug, bool disableVariableRedefines, bool disableColorCompression, bool keepFirstSpecialComment, bool strictMath, IEnumerable<IPluginConfigurator> plugins)
{
Parser = parser;
Logger = logger;
Compress = compress;
Debug = debug;
Plugins = plugins;
KeepFirstSpecialComment = keepFirstSpecialComment;
StrictMath = strictMath;
}
public LessEngine(Parser.Parser parser, ILogger logger, bool compress, bool debug, bool disableVariableRedefines, bool disableColorCompression, bool keepFirstSpecialComment, IEnumerable<IPluginConfigurator> plugins)
: this(parser, logger, compress, debug, disableVariableRedefines, disableColorCompression, keepFirstSpecialComment, false, plugins)
{
}
public LessEngine(Parser.Parser parser, ILogger logger, bool compress, bool debug)
: this(parser, logger, compress, debug, false, false, false, null)
{
}
public LessEngine(Parser.Parser parser, ILogger logger, bool compress, bool debug, bool disableVariableRedefines)
: this(parser, logger, compress, debug, disableVariableRedefines, false, false, null)
{
}
public LessEngine(Parser.Parser parser)
: this(parser, new ConsoleLogger(LogLevel.Error), false, false, false, false, false, null)
{
}
public LessEngine()
: this(new Parser.Parser())
{
}
public string TransformToCss(string source, string fileName)
{
try
{
Parser.StrictMath = StrictMath;
var tree = Parser.Parse(source, fileName);
var env = Env ??
new Env(Parser)
{
Compress = Compress,
Debug = Debug,
KeepFirstSpecialComment = KeepFirstSpecialComment,
};
if (Plugins != null)
{
foreach (IPluginConfigurator configurator in Plugins)
{
env.AddPlugin(configurator.CreatePlugin());
}
}
var css = tree.ToCSS(env);
var stylizer = new PlainStylizer();
foreach (var unmatchedExtension in env.FindUnmatchedExtensions()) {
Logger.Warn("Warning: extend '{0}' has no matches {1}\n",
unmatchedExtension.BaseSelector.ToCSS(env).Trim(),
stylizer.Stylize(new Zone(unmatchedExtension.Extend.Location)).Trim());
}
tree.Accept(DelegateVisitor.For<Media>(m => {
foreach (var unmatchedExtension in m.FindUnmatchedExtensions()) {
Logger.Warn("Warning: extend '{0}' has no matches {1}\n",
unmatchedExtension.BaseSelector.ToCSS(env).Trim(),
stylizer.Stylize(new Zone(unmatchedExtension.Extend.Location)).Trim());
}
}));
LastTransformationSuccessful = true;
return css;
}
catch (ParserException e)
{
LastTransformationSuccessful = false;
LastTransformationError = e;
Logger.Error(e.Message);
}
return "";
}
public ParserException LastTransformationError { get; set; }
public IEnumerable<string> GetImports()
{
return Parser.Importer.GetImports();
}
public void ResetImports()
{
Parser.Importer.ResetImports();
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using dotless.Core.Exceptions;
using dotless.Core.Parser.Infrastructure;
namespace dotless.Core
{
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Parameters;
public class ParameterDecorator : ILessEngine
{
public readonly ILessEngine Underlying;
private readonly IParameterSource parameterSource;
public ParameterDecorator(ILessEngine underlying, IParameterSource parameterSource)
{
this.Underlying = underlying;
this.parameterSource = parameterSource;
}
public string TransformToCss(string source, string fileName)
{
var sb = new StringBuilder();
var parameters = parameterSource.GetParameters()
.Where(ValueIsNotNullOrEmpty);
var parser = new Parser.Parser();
sb.Append(source);
foreach (var parameter in parameters)
{
sb.AppendLine();
var variableDeclaration = string.Format("@{0}: {1};", parameter.Key, parameter.Value);
try
{
// Attempt to evaluate the generated variable to see if it's OK
parser.Parse(variableDeclaration, "").ToCSS(new Env());
sb.Append(variableDeclaration);
}
catch (ParserException)
{
// Result wasn't valid LESS, output a comment instead
sb.AppendFormat("/* Omitting variable '{0}'. The expression '{1}' is not valid. */", parameter.Key,
parameter.Value);
}
}
return Underlying.TransformToCss(sb.ToString(), fileName);
}
public IEnumerable<string> GetImports()
{
return Underlying.GetImports();
}
public void ResetImports()
{
Underlying.ResetImports();
}
public bool LastTransformationSuccessful
{
get
{
return Underlying.LastTransformationSuccessful;
}
}
private static bool ValueIsNotNullOrEmpty(KeyValuePair<string, string> kvp)
{
return !string.IsNullOrEmpty(kvp.Value);
}
public string CurrentDirectory
{
get { return Underlying.CurrentDirectory; }
set { Underlying.CurrentDirectory = value; }
}
}
}

View File

@ -0,0 +1,26 @@
using dotless.Core.Parser;
namespace dotless.Core.Exceptions
{
using System;
public class ParserException : Exception
{
public ParserException(string message)
: base(message)
{
}
public ParserException(string message, Exception innerException)
: base(message, innerException)
{
}
public ParserException(string message, Exception innerException, Zone errorLocation)
: base(message, innerException) {
ErrorLocation = errorLocation;
}
public Zone ErrorLocation { get; set; }
}
}

View File

@ -0,0 +1,28 @@
namespace dotless.Core.Exceptions
{
using System;
using dotless.Core.Parser;
public class ParsingException : Exception
{
public NodeLocation Location { get; set; }
public NodeLocation CallLocation { get; set; }
public ParsingException(string message, NodeLocation location) : this(message, null, location, null) { }
public ParsingException(string message, NodeLocation location, NodeLocation callLocation) : this(message, null, location, callLocation) { }
public ParsingException(Exception innerException, NodeLocation location) : this(innerException, location, null) { }
public ParsingException(Exception innerException, NodeLocation location, NodeLocation callLocation) : this(innerException.Message, innerException, location, callLocation) { }
public ParsingException(string message, Exception innerException, NodeLocation location) : this(message, innerException, location, null) { }
public ParsingException(string message, Exception innerException, NodeLocation location, NodeLocation callLocation)
: base(message, innerException)
{
Location = location;
CallLocation = callLocation;
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) 2015 Kristian Hellang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace dotless.Core.Extensions
{
public class MissingTypeRegistrationException : InvalidOperationException
{
public MissingTypeRegistrationException(Type serviceType)
: base($"Could not find any registered services for type '{GetFriendlyName(serviceType)}'.")
{
ServiceType = serviceType;
}
public Type ServiceType { get; }
private static string GetFriendlyName(Type type)
{
if (type == typeof(int)) return "int";
if (type == typeof(short)) return "short";
if (type == typeof(byte)) return "byte";
if (type == typeof(bool)) return "bool";
if (type == typeof(char)) return "char";
if (type == typeof(long)) return "long";
if (type == typeof(float)) return "float";
if (type == typeof(double)) return "double";
if (type == typeof(decimal)) return "decimal";
if (type == typeof(string)) return "string";
if (type == typeof(object)) return "object";
// var typeInfo = type.GetTypeInfo();
if (type.IsGenericType) return GetGenericFriendlyName(type);
return type.Name;
}
private static string GetGenericFriendlyName(Type typeInfo)
{
var argumentNames = typeInfo.GetGenericArguments().Select(GetFriendlyName).ToArray();
var baseName = typeInfo.Name.Split('`').First();
return $"{baseName}<{string.Join(", ", argumentNames)}>";
}
}
}

View File

@ -0,0 +1,72 @@
namespace dotless.Core.Importers
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Parser;
using Parser.Tree;
public interface IImporter
{
/// <summary>
/// Get a list of the current paths, used to pass back in to alter url's after evaluation
/// </summary>
/// <returns></returns>
List<string> GetCurrentPathsClone();
/// <summary>
/// Imports an import and return true if successful
/// </summary>
ImportAction Import(Import import);
/// <summary>
/// A method set by the parser implementation in order to get a new parser for use in importing
/// </summary>
Func<Parser> Parser { get; set; }
/// <summary>
/// Called for every Url and allows the importer to adjust relative url's to be relative to the
/// primary url
/// </summary>
string AlterUrl(string url, List<string> pathList);
string CurrentDirectory { get; set; }
IDisposable BeginScope(Import parent);
/// <summary>
/// Resets the imports.
/// </summary>
void ResetImports();
/// <summary>
/// Gets the already imported files
/// </summary>
/// <returns></returns>
IEnumerable<string> GetImports();
}
/// <summary>
/// The action to do with the @import statement
/// </summary>
public enum ImportAction
{
/// <summary>
/// Import as less (process the file and include)
/// </summary>
ImportLess,
/// <summary>
/// Import verbatim as CSS
/// </summary>
ImportCss,
/// <summary>
/// Leave a @import statement
/// </summary>
LeaveImport,
/// <summary>
/// Do nothing (e.g. when it is an import-once and has already been imported)
/// </summary>
ImportNothing,
}
}

View File

@ -0,0 +1,488 @@
namespace dotless.Core.Importers
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Input;
using Parser;
using Parser.Tree;
using Utils;
using System.Text.RegularExpressions;
using System.Reflection;
public class Importer : IImporter
{
private static readonly Regex _embeddedResourceRegex = new Regex(@"^dll://(?<Assembly>.+?)#(?<Resource>.+)$");
public static Regex EmbeddedResourceRegex { get { return _embeddedResourceRegex; } }
public IFileReader FileReader { get; set; }
/// <summary>
/// List of successful imports
/// </summary>
public List<string> Imports { get; set; }
public Func<Parser> Parser { get; set; }
private readonly List<string> _paths = new List<string>();
/// <summary>
/// The raw imports of every @import node, for use with @import
/// </summary>
protected readonly List<string> _rawImports = new List<string>();
/// <summary>
/// Duplicates of reference imports should be ignored just like normal imports
/// but a reference import must not interfere with a regular import, hence a different list
/// </summary>
private readonly List<string> _referenceImports = new List<string>();
public virtual string CurrentDirectory { get; set; }
/// <summary>
/// Whether or not the importer should alter urls
/// </summary>
public bool IsUrlRewritingDisabled { get; set; }
public string RootPath { get; set; }
/// <summary>
/// Import all files as if they are less regardless of file extension
/// </summary>
public bool ImportAllFilesAsLess { get; set; }
/// <summary>
/// Import the css and include inline
/// </summary>
public bool InlineCssFiles { get; set; }
public Importer() : this(new FileReader())
{
}
public Importer(IFileReader fileReader) : this(fileReader, false, "", false, false)
{
}
public Importer(IFileReader fileReader, bool disableUrlReWriting, string rootPath, bool inlineCssFiles, bool importAllFilesAsLess)
{
FileReader = fileReader;
IsUrlRewritingDisabled = disableUrlReWriting;
RootPath = rootPath;
InlineCssFiles = inlineCssFiles;
ImportAllFilesAsLess = importAllFilesAsLess;
Imports = new List<string>();
CurrentDirectory = "";
}
/// <summary>
/// Whether a url has a protocol on it
/// </summary>
private static bool IsProtocolUrl(string url)
{
return Regex.IsMatch(url, @"^([a-zA-Z]{2,}:)");
}
/// <summary>
/// Whether a url has a protocol on it
/// </summary>
private static bool IsNonRelativeUrl(string url)
{
return url.StartsWith(@"/") || url.StartsWith(@"~/") || Regex.IsMatch(url, @"^[a-zA-Z]:");
}
/// <summary>
/// Whether a url represents an embedded resource
/// </summary>
private static bool IsEmbeddedResource(string path)
{
return _embeddedResourceRegex.IsMatch(path);
}
/// <summary>
/// Get a list of the current paths, used to pass back in to alter url's after evaluation
/// </summary>
/// <returns></returns>
public List<string> GetCurrentPathsClone()
{
return new List<string>(_paths);
}
/// <summary>
/// returns true if the import should be ignored because it is a duplicate and import-once was used
/// </summary>
/// <param name="import"></param>
/// <returns></returns>
protected bool CheckIgnoreImport(Import import)
{
return CheckIgnoreImport(import, import.Path);
}
/// <summary>
/// returns true if the import should be ignored because it is a duplicate and import-once was used
/// </summary>
/// <param name="import"></param>
/// <returns></returns>
protected bool CheckIgnoreImport(Import import, string path)
{
if (IsOptionSet(import.ImportOptions, ImportOptions.Multiple))
{
return false;
}
// The import option Reference is set at parse time,
// but the IsReference bit is set at evaluation time (inherited from parent)
// so we check both.
if (import.IsReference || IsOptionSet(import.ImportOptions, ImportOptions.Reference))
{
if (_rawImports.Contains(path, StringComparer.InvariantCultureIgnoreCase))
{
// Already imported as a regular import, so the reference import is redundant
return true;
}
return CheckIgnoreImport(_referenceImports, path);
}
return CheckIgnoreImport(_rawImports, path);
}
private bool CheckIgnoreImport(List<string> importList, string path) {
if (importList.Contains(path, StringComparer.InvariantCultureIgnoreCase))
{
return true;
}
importList.Add(path);
return false;
}
/// <summary>
/// Imports the file inside the import as a dot-less file.
/// </summary>
/// <param name="import"></param>
/// <returns> The action for the import node to process</returns>
public virtual ImportAction Import(Import import)
{
// if the import is protocol'd (e.g. http://www.opencss.com/css?blah) then leave the import alone
if (IsProtocolUrl(import.Path) && !IsEmbeddedResource(import.Path))
{
if (import.Path.EndsWith(".less"))
{
throw new FileNotFoundException(string.Format(".less cannot import non local less files [{0}].", import.Path), import.Path);
}
if (CheckIgnoreImport(import))
{
return ImportAction.ImportNothing;
}
return ImportAction.LeaveImport;
}
var file = import.Path;
if (!IsNonRelativeUrl(file))
{
file = GetAdjustedFilePath(import.Path, _paths);
}
if (CheckIgnoreImport(import, file))
{
return ImportAction.ImportNothing;
}
bool importAsless = ImportAllFilesAsLess || IsOptionSet(import.ImportOptions, ImportOptions.Less);
if (!importAsless && import.Path.EndsWith(".css") && !import.Path.EndsWith(".less.css"))
{
if (InlineCssFiles || IsOptionSet(import.ImportOptions, ImportOptions.Inline))
{
if (IsEmbeddedResource(import.Path) && ImportEmbeddedCssContents(file, import))
return ImportAction.ImportCss;
if (ImportCssFileContents(file, import))
return ImportAction.ImportCss;
}
return ImportAction.LeaveImport;
}
if (Parser == null)
throw new InvalidOperationException("Parser cannot be null.");
string fullPath = String.Empty;
if (!ImportLessFile(file, import, out fullPath))
{
if (IsOptionSet(import.ImportOptions, ImportOptions.Optional))
{
return ImportAction.ImportNothing;
}
if (IsOptionSet(import.ImportOptions, ImportOptions.Css))
{
return ImportAction.LeaveImport;
}
if (import.Path.EndsWith(".less", StringComparison.InvariantCultureIgnoreCase))
{
throw new FileNotFoundException(string.Format("You are importing a file ending in .less that cannot be found [{0}].", fullPath), file);
}
return ImportAction.LeaveImport;
}
return ImportAction.ImportLess;
}
public IDisposable BeginScope(Import parentScope) {
return new ImportScope(this, Path.GetDirectoryName(parentScope.Path));
}
/// <summary>
/// Uses the paths to adjust the file path
/// </summary>
protected string GetAdjustedFilePath(string path, IEnumerable<string> pathList)
{
return pathList.Concat(new[] { path }).AggregatePaths(CurrentDirectory);
}
/// <summary>
/// Imports a less file and puts the root into the import node
/// </summary>
protected bool ImportLessFile(string lessPath, Import import, out string fullName)
{
string contents, file = null;
if (IsEmbeddedResource(lessPath))
{
contents = ResourceLoader.GetResource(lessPath, FileReader, out file);
if (contents == null)
{
fullName = lessPath;
return false;
}
}
else
{
fullName = RootPath + '/' + lessPath;
if (Path.IsPathRooted(lessPath))
{
fullName = lessPath;
}
else if (!string.IsNullOrEmpty(CurrentDirectory)) {
fullName = CurrentDirectory.Replace(@"\", "/").TrimEnd('/') + '/' + lessPath;
}
bool fileExists = FileReader.DoesFileExist(fullName);
if (!fileExists && !fullName.EndsWith(".less"))
{
fullName += ".less";
fileExists = FileReader.DoesFileExist(fullName);
}
if (!fileExists) return false;
contents = FileReader.GetFileContents(fullName);
file = fullName;
}
_paths.Add(Path.GetDirectoryName(import.Path));
try
{
if (!string.IsNullOrEmpty(file))
{
Imports.Add(file);
}
import.InnerRoot = Parser().Parse(contents, lessPath);
}
catch
{
Imports.Remove(file);
throw;
}
finally
{
_paths.RemoveAt(_paths.Count - 1);
}
fullName = file;
return true;
}
/// <summary>
/// Imports a css file from an embedded resource and puts the contents into the import node
/// </summary>
/// <param name="file"></param>
/// <param name="import"></param>
/// <returns></returns>
private bool ImportEmbeddedCssContents(string file, Import import)
{
string content = ResourceLoader.GetResource(file, FileReader, out file);
if (content == null) return false;
import.InnerContent = content;
return true;
}
/// <summary>
/// Imports a css file and puts the contents into the import node
/// </summary>
protected bool ImportCssFileContents(string file, Import import)
{
if (!FileReader.DoesFileExist(file))
{
return false;
}
import.InnerContent = FileReader.GetFileContents(file);
Imports.Add(file);
return true;
}
/// <summary>
/// Called for every Url and allows the importer to adjust relative url's to be relative to the
/// primary url
/// </summary>
public string AlterUrl(string url, List<string> pathList)
{
if (!IsProtocolUrl (url) && !IsNonRelativeUrl (url))
{
if (pathList.Any() && !IsUrlRewritingDisabled)
{
url = GetAdjustedFilePath(url, pathList);
}
return RootPath + url;
}
return url;
}
public void ResetImports()
{
Imports.Clear();
_rawImports.Clear();
}
public IEnumerable<string> GetImports()
{
return Imports.Distinct();
}
private class ImportScope : IDisposable {
private readonly Importer importer;
public ImportScope(Importer importer, string path) {
this.importer = importer;
this.importer._paths.Add(path);
}
public void Dispose() {
this.importer._paths.RemoveAt(this.importer._paths.Count - 1);
}
}
private bool IsOptionSet(ImportOptions options, ImportOptions test)
{
return (options & test) == test;
}
}
/// <summary>
/// Utility class used to retrieve the content of an embedded resource using a separate app domain in order to unload the assembly when done.
/// </summary>
class ResourceLoader : MarshalByRefObject
{
private byte[] _fileContents;
private string _resourceName;
private string _resourceContent;
/// <summary>
/// Gets the text content of an embedded resource.
/// </summary>
/// <param name="file">The path in the form: dll://AssemblyName#ResourceName</param>
/// <returns>The content of the resource</returns>
public static string GetResource(string file, IFileReader fileReader, out string fileDependency)
{
fileDependency = null;
var match = Importer.EmbeddedResourceRegex.Match(file);
if (!match.Success) return null;
var loader = new ResourceLoader
{
_resourceName = match.Groups["Resource"].Value
};
try
{
fileDependency = match.Groups["Assembly"].Value;
LoadFromCurrentAppDomain(loader, fileDependency);
if (String.IsNullOrEmpty(loader._resourceContent))
LoadFromNewAppDomain(loader, fileReader, fileDependency);
}
catch (Exception)
{
throw new FileNotFoundException("Unable to load resource [" + loader._resourceName + "] in assembly [" + fileDependency + "]");
}
finally
{
loader._fileContents = null;
}
return loader._resourceContent;
}
private static void LoadFromCurrentAppDomain(ResourceLoader loader, String assemblyName)
{
foreach (var assembly in AppDomain.CurrentDomain
.GetAssemblies()
.Where(x => !IsDynamicAssembly(x) && x.Location.EndsWith(assemblyName, StringComparison.InvariantCultureIgnoreCase)))
{
if (assembly.GetManifestResourceNames().Contains(loader._resourceName))
{
using (var stream = assembly.GetManifestResourceStream(loader._resourceName))
using (var reader = new StreamReader(stream))
{
loader._resourceContent = reader.ReadToEnd();
if (!String.IsNullOrEmpty(loader._resourceContent))
return;
}
}
}
}
private static bool IsDynamicAssembly(Assembly assembly) {
try {
string loc = assembly.Location;
return false;
} catch (NotSupportedException) {
// Location is not supported for dynamic assemblies, so it will throw a NotSupportedException
return true;
}
}
private static void LoadFromNewAppDomain(ResourceLoader loader, IFileReader fileReader, String assemblyName)
{
if (!fileReader.DoesFileExist(assemblyName))
{
throw new FileNotFoundException("Unable to locate assembly file [" + assemblyName + "]");
}
loader._fileContents = fileReader.GetBinaryFileContents(assemblyName);
var domain = AppDomain.CreateDomain("LoaderDomain");
var assembly = domain.Load(loader._fileContents);
using (var stream = assembly.GetManifestResourceStream(loader._resourceName))
using (var reader = new StreamReader(stream))
{
loader._resourceContent = reader.ReadToEnd();
}
AppDomain.Unload(domain);
}
}
}

View File

@ -0,0 +1,41 @@
namespace dotless.Core.Input
{
using System.IO;
public class FileReader : IFileReader
{
public IPathResolver PathResolver { get; set; }
public FileReader() : this(new RelativePathResolver())
{
}
public FileReader(IPathResolver pathResolver)
{
PathResolver = pathResolver;
}
public byte[] GetBinaryFileContents(string fileName)
{
fileName = PathResolver.GetFullPath(fileName);
return File.ReadAllBytes(fileName);
}
public string GetFileContents(string fileName)
{
fileName = PathResolver.GetFullPath(fileName);
return File.ReadAllText(fileName);
}
public bool DoesFileExist(string fileName)
{
fileName = PathResolver.GetFullPath(fileName);
return File.Exists(fileName);
}
public bool UseCacheDependencies { get { return true; } }
}
}

View File

@ -0,0 +1,13 @@
namespace dotless.Core.Input
{
public interface IFileReader
{
byte[] GetBinaryFileContents(string fileName);
string GetFileContents(string fileName);
bool DoesFileExist(string fileName);
bool UseCacheDependencies { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace dotless.Core.Input
{
public interface IPathResolver
{
string GetFullPath(string path);
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Input
{
public class RelativePathResolver : IPathResolver
{
public string GetFullPath(string path)
{
return path;
}
}
}

View File

@ -0,0 +1,25 @@
namespace dotless.Core
{
using configuration;
using System;
public static class Less
{
public static string Parse(string less)
{
return Parse(less, DotlessConfiguration.GetDefault());
}
public static string Parse(string less, DotlessConfiguration config)
{
if (config.Web)
{
// throw new Exception("Please use dotless.Core.LessWeb.Parse for web applications. This makes sure all web features are available.");
}
LessEngine engine = new LessEngine();
engine.CurrentDirectory = config.RootPath;
return engine.TransformToCss(less, null);
}
}
}

View File

@ -0,0 +1,20 @@
namespace dotless.Core.Loggers
{
using dotless.Core.configuration;
using System;
public class ConsoleLogger : Logger
{
public ConsoleLogger(LogLevel level) : base(level) { }
public ConsoleLogger(DotlessConfiguration config) : this(config.LogLevel)
{
}
protected override void Log(string message)
{
Console.WriteLine(message);
}
}
}

View File

@ -0,0 +1,14 @@
namespace dotless.Core.Loggers
{
public class DiagnosticsLogger : Logger
{
public DiagnosticsLogger(LogLevel level) : base(level)
{
}
protected override void Log(string message)
{
System.Diagnostics.Debug.WriteLine(message);
}
}
}

View File

@ -0,0 +1,23 @@
namespace dotless.Core.Loggers
{
public interface ILogger
{
void Log(LogLevel level, string message);
void Info(string message);
void Info(string message, params object[] args);
void Debug(string message);
void Debug(string message, params object[] args);
void Warn(string message);
void Warn(string message, params object[] args);
void Error(string message);
void Error(string message, params object[] args);
}
public enum LogLevel
{
Info = 1,
Debug,
Warn,
Error
}
}

View File

@ -0,0 +1,30 @@
namespace dotless.Core.Loggers
{
public abstract class Logger : ILogger
{
public LogLevel Level { get; set; }
protected Logger(LogLevel level)
{
Level = level;
}
public void Log(LogLevel level, string message)
{
if (Level <= level)
Log(message);
}
protected abstract void Log(string message);
public void Info(string message) { Log(LogLevel.Info, message); }
public void Debug(string message) { Log(LogLevel.Debug, message); }
public void Warn(string message) { Log(LogLevel.Warn, message); }
public void Error(string message) { Log(LogLevel.Error, message); }
public void Info(string message, params object[] args) { Log(LogLevel.Info, string.Format(message, args)); }
public void Debug(string message, params object[] args) { Log(LogLevel.Debug, string.Format(message, args)); }
public void Warn(string message, params object[] args) { Log(LogLevel.Warn, string.Format(message, args)); }
public void Error(string message, params object[] args) { Log(LogLevel.Error, string.Format(message, args)); }
}
}

View File

@ -0,0 +1,23 @@
namespace dotless.Core.Loggers
{
public class NullLogger : Logger
{
public NullLogger(LogLevel level) : base(level)
{
}
protected override void Log(string message)
{
//Swallow
}
private static readonly NullLogger instance = new NullLogger(LogLevel.Warn);
public static NullLogger Instance
{
get
{
return instance;
}
}
}
}

View File

@ -0,0 +1,13 @@
namespace dotless.Core.Parameters
{
using System.Collections.Generic;
public class ConsoleArgumentParameterSource : IParameterSource
{
public static IDictionary<string, string> ConsoleArguments = new Dictionary<string, string>();
public IDictionary<string, string> GetParameters()
{
return ConsoleArguments;
}
}
}

View File

@ -0,0 +1,9 @@
namespace dotless.Core.Parameters
{
using System.Collections.Generic;
public interface IParameterSource
{
IDictionary<string, string> GetParameters();
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace dotless.Core.Parameters {
public class NullParameterSource : IParameterSource
{
public IDictionary<string, string> GetParameters()
{
return new Dictionary<string, string>();
}
}
}

View File

@ -0,0 +1,17 @@
namespace dotless.Core.Parser.Functions
{
using System;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
public class AbsFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
WarnNotSupportedByLessJS("abs(number)");
return new Number(Math.Abs(number.Value), number.Unit);
}
}
}

View File

@ -0,0 +1,20 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class AddFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectAllNodes<Number>(Arguments, this, Location);
var value = Arguments.Cast<Number>().Select(d => d.Value).Aggregate(0d, (a, b) => a + b);
return new Number(value);
}
}
}

View File

@ -0,0 +1,51 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
public class FadeInFunction : ColorFunctionBase
{
protected override Node Eval(Color color)
{
return new Number(color.Alpha);
}
protected override Node EditColor(Color color, Number number)
{
var alpha = number.Value/100d;
return new Color(color.R, color.G, color.B, ProcessAlpha( color.Alpha, alpha));
}
protected virtual double ProcessAlpha(double originalAlpha, double newAlpha)
{
return originalAlpha + newAlpha;
}
}
public class AlphaFunction : FadeInFunction
{
protected override Node EditColor(Color color, Number number)
{
WarnNotSupportedByLessJS("alpha(color, number)", "fadein(color, number) or the opposite fadeout(color, number),");
return base.EditColor(color, number);
}
}
public class FadeOutFunction : AlphaFunction
{
protected override Node EditColor(Color color, Number number)
{
return base.EditColor(color, -number);
}
}
public class FadeFunction : AlphaFunction
{
protected override double ProcessAlpha(double originalAlpha, double newAlpha)
{
return newAlpha;
}
}
}

View File

@ -0,0 +1,18 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class ArgbFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(1, Arguments.Count, this, Location);
var color = Guard.ExpectNode<Color>(Arguments[0], this, Location);
return new TextNode(color.ToArgb());
}
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Parser.Functions
{
public class AverageFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return (a + b) / 2;
}
}
}

View File

@ -0,0 +1,25 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
public class BlueFunction : ColorFunctionBase
{
protected override Node Eval(Color color)
{
return new Number(color.B);
}
protected override Node EditColor(Color color, Number number)
{
WarnNotSupportedByLessJS("blue(color, number)");
var value = number.Value;
if (number.Unit == "%")
value = (value*255)/100d;
return new Color(color.R, color.G, color.B + value);
}
}
}

View File

@ -0,0 +1,15 @@
namespace dotless.Core.Parser.Functions
{
using System;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
public class CeilFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
return new Number(Math.Ceiling(number.Value), number.Unit);
}
}
}

View File

@ -0,0 +1,28 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
using System;
using Exceptions;
public class ColorFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(1, Arguments.Count, this, Location);
var node = Guard.ExpectNode<TextNode>(Arguments[0], this, Location);
try
{
return Color.From(node.Value);
}
catch (FormatException ex)
{
var message = string.Format("Invalid RGB color string '{0}'", node.Value);
throw new ParsingException(message, ex, Location, null);
}
}
}
}

View File

@ -0,0 +1,39 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public abstract class ColorFunctionBase : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMinArguments(1, Arguments.Count(), this, Location);
Guard.ExpectNode<Color>(Arguments[0], this, Arguments[0].Location);
var color = Arguments[0] as Color;
if (Arguments.Count == 2)
{
Guard.ExpectNode<Number>(Arguments[1], this, Arguments[1].Location);
var number = Arguments[1] as Number;
var edit = EditColor(color, number);
if (edit != null)
return edit;
}
return Eval(color);
}
protected abstract Node Eval(Color color);
protected virtual Node EditColor(Color color, Number number)
{
return null;
}
}
}

View File

@ -0,0 +1,48 @@
using System;
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public abstract class ColorMixFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(2, Arguments.Count, this, Location);
Guard.ExpectAllNodes<Color>(Arguments, this, Location);
var color1 = (Color) Arguments[0];
var color2 = (Color) Arguments[1];
var resultAlpha = color2.Alpha + color1.Alpha*(1 - color2.Alpha);
return new Color(
Compose(color1, color2, resultAlpha, c => c.R),
Compose(color1, color2, resultAlpha, c => c.G),
Compose(color1, color2, resultAlpha, c => c.B),
resultAlpha);
}
/// <summary>
/// Color composition rules from http://www.w3.org/TR/compositing-1/
/// (translated from the less.js version
/// </summary>
private double Compose(Color backdrop, Color source, double ar, Func<Color, double> channel) {
var cb = channel(backdrop);
var cs = channel(source);
var ab = backdrop.Alpha;
var @as = source.Alpha;
double result = Operate(cb, cs);
if (ar > 0)
{
result = (@as * cs + ab * (cb - @as * (cb + cs - result))) / ar;
}
return result;
}
protected abstract double Operate(double a, double b);
}
}

View File

@ -0,0 +1,22 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
using Utils;
public class ComplementFunction : HslColorFunctionBase
{
protected override Node EvalHsl(HslColor color)
{
WarnNotSupportedByLessJS("complement(color)");
color.Hue += 0.5;
return color.ToRgbColor();
}
protected override Node EditHsl(HslColor color, Number number)
{
return null;
}
}
}

View File

@ -0,0 +1,40 @@

namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class ContrastFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMinArguments(1, Arguments.Count, this, Location);
Guard.ExpectMaxArguments(4, Arguments.Count, this, Location);
Guard.ExpectNode<Color>(Arguments[0], this, Location);
var color = (Color) Arguments[0];
if (Arguments.Count > 1)
Guard.ExpectNode<Color>(Arguments[1], this, Location);
if (Arguments.Count > 2)
Guard.ExpectNode<Color>(Arguments[2], this, Location);
if (Arguments.Count > 3)
Guard.ExpectNode<Number>(Arguments[3], this, Location);
var lightColor = Arguments.Count > 1 ? (Color)Arguments[1] : new Color(255d, 255d, 255d);
var darkColor = Arguments.Count > 2 ? (Color)Arguments[2] : new Color(0d, 0d, 0d);
var threshold = Arguments.Count > 3 ? ((Number) Arguments[3]).ToNumber() : 0.43d;
if (darkColor.Luma > lightColor.Luma)
{
var tempColor = lightColor;
lightColor = darkColor;
darkColor = tempColor;
}
return (color.Luma < threshold) ? lightColor : darkColor;
}
}
}

View File

@ -0,0 +1,72 @@
namespace dotless.Core.Parser.Functions
{
using System;
using System.IO;
using Exceptions;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class DataUriFunction : Function
{
protected override Node Evaluate(Env env)
{
var filename = GetDataUriFilename();
string base64 = ConvertFileToBase64(filename);
string mimeType = GetMimeType(filename);
return new TextNode(string.Format("url(\"data:{0};base64,{1}\")", mimeType, base64));
}
private string GetDataUriFilename()
{
Guard.ExpectMinArguments(1, Arguments.Count, this, Location);
// Thanks a lot LESS for putting the optional parameter first!
var filenameNode = Arguments[0];
if (Arguments.Count > 1)
filenameNode = Arguments[1];
Guard.ExpectNode<Quoted>(filenameNode, this, Location);
var filename = ((Quoted)filenameNode).Value;
Guard.Expect(!(filename.StartsWith("http://") || filename.StartsWith("https://")),
string.Format("Invalid filename passed to data-uri '{0}'. Filename must be a local file", filename), Location);
return filename;
}
private string ConvertFileToBase64(string filename)
{
string base64;
try
{
base64 = Convert.ToBase64String(File.ReadAllBytes(filename));
}
catch (IOException e)
{
// this is more general than just a check to see whether the file exists
// it could fail for other reasons like security permissions
throw new ParsingException(String.Format("Data-uri function could not read file '{0}'", filename), e, Location);
}
return base64;
}
private string GetMimeType(string filename)
{
if (Arguments.Count > 1)
{
Guard.ExpectNode<Quoted>(Arguments[0], this, Location);
var mimeType = ((Quoted) Arguments[0]).Value;
if (mimeType.IndexOf(';') > -1)
mimeType = mimeType.Split(';')[0];
return mimeType;
}
return new MimeTypeLookup().ByFilename(filename);
}
}
}

View File

@ -0,0 +1,13 @@
using dotless.Core.Parser.Infrastructure;
using dotless.Core.Parser.Infrastructure.Nodes;
namespace dotless.Core.Parser.Functions
{
public class DefaultFunction : Function
{
protected override Node Evaluate(Env env)
{
return new TextNode("default()");
}
}
}

View File

@ -0,0 +1,11 @@
namespace dotless.Core.Parser.Functions
{
using System;
public class DifferenceFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return Math.Abs(a - b);
}
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Parser.Functions
{
public class ExclusionFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return a + b * (255 - a - a) / 255;
}
}
}

View File

@ -0,0 +1,21 @@
using dotless.Core.Parser.Infrastructure;
using dotless.Core.Parser.Infrastructure.Nodes;
using dotless.Core.Parser.Tree;
using dotless.Core.Utils;
namespace dotless.Core.Parser.Functions
{
public class ExtractFunction : ListFunctionBase
{
protected override Node Eval(Env env, Node[] list, Node[] args)
{
Guard.ExpectNumArguments(1, args.Length, this, Location);
Guard.ExpectNode<Number>(args[0], this, args[0].Location);
var index = (int)(args[0] as Number).Value;
// Extract function indecies are 1-based
return list[index-1];
}
}
}

View File

@ -0,0 +1,15 @@
namespace dotless.Core.Parser.Functions
{
using System;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
public class FloorFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
return new Number(Math.Floor(number.Value), number.Unit);
}
}
}

View File

@ -0,0 +1,39 @@
namespace dotless.Core.Parser.Functions
{
using System;
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using dotless.Core.Exceptions;
public class FormatStringFunction : Function
{
protected override Node Evaluate(Env env)
{
WarnNotSupportedByLessJS("formatstring(string, args...)", null, @" You may want to consider using string interpolation (""@{variable}"") which does the same thing and is supported.");
if (Arguments.Count == 0)
return new Quoted("", false);
Func<Node, string> unescape = n => n is Quoted ? ((Quoted) n).UnescapeContents() : n.ToCSS(env);
var format = unescape(Arguments[0]);
var args = Arguments.Skip(1).Select(unescape).ToArray();
string result;
try
{
result = string.Format(format, args);
}
catch (FormatException e)
{
throw new ParserException(string.Format("Error in formatString :{0}", e.Message), e);
}
return new Quoted(result, false);
}
}
}

View File

@ -0,0 +1,70 @@
namespace dotless.Core.Parser.Functions
{
using System.Collections.Generic;
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using dotless.Core.Loggers;
public abstract class Function
{
public string Name { get; set; }
protected List<Node> Arguments { get; set; }
public ILogger Logger { get; set; }
public NodeLocation Location { get; set; }
public Node Call(Env env, IEnumerable<Node> arguments)
{
Arguments = arguments.ToList();
var node = Evaluate(env);
node.Location = Location;
return node;
}
protected abstract Node Evaluate(Env env);
public override string ToString()
{
return string.Format("function '{0}'", Name.ToLowerInvariant());
}
/// <summary>
/// Warns that a function is not supported by less.js
/// </summary>
/// <param name="functionPattern">unsupported pattern function call e.g. alpha(color number)</param>
protected void WarnNotSupportedByLessJS(string functionPattern)
{
WarnNotSupportedByLessJS(functionPattern, null, null);
}
/// <summary>
/// Warns that a function is not supported by less.js
/// </summary>
/// <param name="functionPattern">unsupported pattern function call e.g. alpha(color number)</param>
/// <param name="replacementPattern">Replacement pattern function call e.g. fadein(color, number)</param>
protected void WarnNotSupportedByLessJS(string functionPattern, string replacementPattern)
{
WarnNotSupportedByLessJS(functionPattern, replacementPattern, null);
}
/// <summary>
/// Warns that a function is not supported by less.js
/// </summary>
/// <param name="functionPattern">unsupported pattern function call e.g. alpha(color number)</param>
/// <param name="replacementPattern">Replacement pattern function call e.g. fadein(color, number)</param>
/// <param name="extraInfo">Extra information to put on the end.</param>
protected void WarnNotSupportedByLessJS(string functionPattern, string replacementPattern, string extraInfo)
{
if (string.IsNullOrEmpty(replacementPattern))
{
Logger.Info("{0} is not supported by less.js, so this will work but not compile with other less implementations.{1}", functionPattern, extraInfo);
}
else
{
Logger.Warn("{0} is not supported by less.js, so this will work but not compile with other less implementations." +
" You may want to consider using {1} which does the same thing and is supported.{2}", functionPattern, replacementPattern, extraInfo);
}
}
}
}

View File

@ -0,0 +1,220 @@
using System.Threading;
namespace dotless.Core.Parser.Functions
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using Exceptions;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
using Color = Tree.Color;
/// <summary>
/// Outputs <see cref="Url"/> node with gradient image implemented as described in RFC-2397 (The "data" URL scheme, http://tools.ietf.org/html/rfc2397)
/// </summary>
/// <remarks>
/// Acepts color point definitions and outputs image data URL. Usage:
/// <c>gradient(#color1, #color2[, position2][, #color3[, position3]]...)</c>
/// Position is a zero-based offset of the color stop. If omitted, it is increased by 50 (px) or by the previous offset, if any.
///
/// Currently only vertical 1px width gradients with many color stops are supported.
/// Image data URLs are not supported by IE7 and below. IE8 restricts the size of URL to 32k.
/// See details here: http://en.wikipedia.org/wiki/Data_URI_scheme.
/// <example>
/// The following example shows how to render a gradient (.less file):
/// <code>
/// .ui-widget-header { background: @headerBgColor gradientImage(@headerBgColor, desaturate(lighten(@headerBgColor, 30), 30)) 50% 50% repeat-x; }
/// </code>
/// </example>
/// </remarks>
public class GradientImageFunction : Function
{
/*
* TODO:
* 1. Add URI length check for IE8: max 0x8000 - needs access to config and Context.Request.Browser
* 2. Add fallback for IE7 and lower - dump the image to disk and refer it as ordinal url(...) - needs access to config and Context.Request.Browser + disk write permissions.
* 3. Implement horisontal gradients (1px height)
*
* Open questions:
* 1. PNG 32bpp - is it ok for all cases?
*/
#region Nested classes
private class ColorPoint
{
public ColorPoint(System.Drawing.Color color, int position)
{
Color = color;
Position = position;
}
public static string Stringify(IEnumerable<ColorPoint> points)
{
return points.Aggregate(
"", (s, point) => string.Format("{0}{1}#{2:X2}{3:X2}{4:X2}{5:X2},{6}", s, s == "" ? "" : ",",
point.Color.A, point.Color.R, point.Color.G, point.Color.B, point.Position));
}
public System.Drawing.Color Color { get; private set; }
public int Position { get; private set; }
}
private class CacheItem
{
public readonly string _def;
public readonly string _url;
public CacheItem(string def, string url)
{
_def = def;
_url = url;
}
}
#endregion Nested classes
#region Fields
public const int DEFAULT_COLOR_OFFSET = 50;
private const int CACHE_LIMIT = 50;
private static readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim();
private static readonly List<CacheItem> _cache = new List<CacheItem>();
#endregion Fields
protected override Node Evaluate(Env env)
{
ColorPoint[] points = GetColorPoints();
WarnNotSupportedByLessJS("gradientImage(color, color[, position])");
string colorDefs = ColorPoint.Stringify(points);
string imageUrl = GetFromCache(colorDefs);
if (imageUrl == null)
{
imageUrl = "data:image/png;base64," + Convert.ToBase64String(GetImageData(points));
AddToCache(colorDefs, imageUrl);
}
return new Url(new TextNode(imageUrl));
}
private byte[] GetImageData(ColorPoint[] points)
{
ColorPoint last = points.Last();
int size = last.Position + 1;
using (var ms = new MemoryStream())
{
using (var bmp = new Bitmap(1, size, PixelFormat.Format32bppArgb))
{
using (Graphics g = Graphics.FromImage(bmp))
{
for (int i = 1; i < points.Length; i++)
{
var rect = new Rectangle(0, points[i - 1].Position, 1, points[i].Position);
var brush = new LinearGradientBrush(
rect,
points[i - 1].Color,
points[i].Color,
LinearGradientMode.Vertical);
g.FillRectangle(brush, rect);
}
bmp.SetPixel(0, last.Position, last.Color);
bmp.Save(ms, ImageFormat.Png);
}
}
return ms.ToArray();
}
}
private ColorPoint[] GetColorPoints()
{
int argCount = Arguments.Count;
Guard.ExpectMinArguments(2, argCount, this, Location);
Guard.ExpectAllNodes<Color>(Arguments.Take(2), this, Location);
var first = (Color) Arguments[0];
var points = new List<ColorPoint>
{
new ColorPoint((System.Drawing.Color) first, 0)
};
int prevPos = 0;
int prevOffset = DEFAULT_COLOR_OFFSET;
for (int i = 1; i < argCount; i++)
{
Node arg = Arguments[i];
Guard.ExpectNode<Color>(arg, this, Location);
var color = arg as Color;
int pos = prevPos + prevOffset;
if (i < argCount - 1)
{
var numberArg = Arguments[i + 1] as Number;
if (numberArg)
{
pos = Convert.ToInt32(numberArg.Value);
if (pos <= prevPos)
{
throw new ParsingException(
string.Format("Incrementing color point position expected, at least {0}, found {1}", prevPos + 1, numberArg.Value), Location);
}
prevOffset = pos - prevPos;
i++;
}
}
points.Add(new ColorPoint((System.Drawing.Color) color, pos));
prevPos = pos;
}
return points.ToArray();
}
private static string GetFromCache(string colorDefs)
{
_cacheLock.EnterReadLock();
try
{
var cached = _cache.FirstOrDefault(item => item._def == colorDefs);
return cached != null ? cached._url : null;
}
finally
{
_cacheLock.ExitReadLock();
}
}
private static void AddToCache(string colorDefs, string imageUrl)
{
_cacheLock.EnterUpgradeableReadLock();
try
{
if (_cache.All(item => item._def != colorDefs))
{
_cacheLock.EnterWriteLock();
try
{
if (_cache.Count >= CACHE_LIMIT)
_cache.RemoveRange(0, CACHE_LIMIT / 2);
var item = new CacheItem(colorDefs, imageUrl);
_cache.Add(item);
}
finally
{
_cacheLock.ExitWriteLock();
}
}
}
finally
{
_cacheLock.ExitUpgradeableReadLock();
}
}
}
}

View File

@ -0,0 +1,25 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure.Nodes;
using Tree;
public class GreyscaleFunction : ColorFunctionBase
{
protected override Node Eval(Color color)
{
var grey = (color.RGB.Max() + color.RGB.Min())/2;
return new Color(grey, grey, grey);
}
}
public class GrayscaleFunction : GreyscaleFunction
{
protected override Node Eval(Color color)
{
WarnNotSupportedByLessJS("grayscale(color)", "greyscale(color)");
return base.Eval(color);
}
}
}

View File

@ -0,0 +1,25 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
public class GreenFunction : ColorFunctionBase
{
protected override Node Eval(Color color)
{
return new Number(color.G);
}
protected override Node EditColor(Color color, Number number)
{
WarnNotSupportedByLessJS("green(color, number)");
var value = number.Value;
if (number.Unit == "%")
value = (value*255)/100d;
return new Color(color.R, color.G + value, color.B);
}
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Parser.Functions
{
public class HardlightFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return b < 128 ? 2 * b * a / 255 : 255 - 2 * (255 - b) * (255 - a) / 255;
}
}
}

View File

@ -0,0 +1,29 @@
namespace dotless.Core.Parser.Functions
{
using Exceptions;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
public class HexFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
WarnNotSupportedByLessJS("hex(number)");
if (!string.IsNullOrEmpty(number.Unit))
throw new ParsingException(string.Format("Expected unitless number in function 'hex', found {0}", number.ToCSS(env)), number.Location);
number.Value = Clamp(number.Value, 255, 0);
return new TextNode(((int)number.Value).ToString("X2"));
}
private static double Clamp(double value, double max, double min)
{
if (value < min) value = min;
if (value > max) value = max;
return value;
}
}
}

View File

@ -0,0 +1,27 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
using Utils;
public abstract class HslColorFunctionBase : ColorFunctionBase
{
protected override Node Eval(Color color)
{
var hsl = HslColor.FromRgbColor(color);
return EvalHsl(hsl);
}
protected override Node EditColor(Color color, Number number)
{
var hsl = HslColor.FromRgbColor(color);
return EditHsl(hsl, number);
}
protected abstract Node EvalHsl(HslColor color);
protected abstract Node EditHsl(HslColor color, Number number);
}
}

View File

@ -0,0 +1,19 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class HslFunction : HslaFunction
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(3, Arguments.Count, this, Location);
Arguments.Add(new Number(1d, ""));
return base.Evaluate(env);
}
}
}

View File

@ -0,0 +1,21 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class HslaFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(4, Arguments.Count, this, Location);
Guard.ExpectAllNodes<Number>(Arguments, this, Location);
var args = Arguments.Cast<Number>().ToArray();
return new HslColor(args[0], args[1], args[2], args[3]).ToRgbColor();
}
}
}

View File

@ -0,0 +1,24 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
using Utils;
public class HueFunction : HslColorFunctionBase
{
protected override Node EvalHsl(HslColor color)
{
return color.GetHueInDegrees();
}
protected override Node EditHsl(HslColor color, Number number)
{
WarnNotSupportedByLessJS("hue(color, number)");
color.Hue += number.Value/360d;
return color.ToRgbColor();
}
}
public class SpinFunction : HueFunction { }
}

View File

@ -0,0 +1,14 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
public class IncrementFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
return new Number(number.Value + 1, number.Unit);
}
}
}

View File

@ -0,0 +1,101 @@
namespace dotless.Core.Parser.Functions
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using dotless.Core.Utils;
using dotless.Core.Parser.Infrastructure.Nodes;
using dotless.Core.Parser.Tree;
public abstract class IsFunction : Function
{
protected override Infrastructure.Nodes.Node Evaluate(Infrastructure.Env env)
{
Guard.ExpectNumArguments(1, Arguments.Count, this, Location);
return new Keyword(IsEvaluator(Arguments[0]) ? "true" : "false");
}
protected abstract bool IsEvaluator(Node node);
}
public abstract class IsTypeFunction<T> : IsFunction where T:Node
{
protected override bool IsEvaluator(Node node)
{
return node is T;
}
}
public class IsColorFunction : IsTypeFunction<Color>
{
}
public class IsNumber : IsTypeFunction<Number>
{
}
public class IsString : IsTypeFunction<Quoted>
{
}
public class IsKeyword : IsTypeFunction<Keyword>
{
}
public class IsUrl : IsTypeFunction<Url>
{
}
public abstract class IsDimensionUnitFunction : IsTypeFunction<Number>
{
protected abstract string Unit { get; }
protected override bool IsEvaluator(Node node)
{
bool isNumber = base.IsEvaluator(node);
if (isNumber)
{
if (((Number)node).Unit == Unit)
{
return true;
}
}
return false;
}
}
public class IsEm : IsDimensionUnitFunction
{
protected override string Unit
{
get
{
return "em";
}
}
}
public class IsPercentage : IsDimensionUnitFunction
{
protected override string Unit
{
get
{
return "%";
}
}
}
public class IsPixel : IsDimensionUnitFunction
{
protected override string Unit
{
get
{
return "px";
}
}
}
}

View File

@ -0,0 +1,14 @@
using dotless.Core.Parser.Infrastructure;
using dotless.Core.Parser.Infrastructure.Nodes;
using dotless.Core.Parser.Tree;
namespace dotless.Core.Parser.Functions
{
public class LengthFunction : ListFunctionBase
{
protected override Node Eval(Env env, Node[] list, Node[] args)
{
return new Number(list.Length);
}
}
}

View File

@ -0,0 +1,38 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
using Utils;
public class LightenFunction : HslColorFunctionBase
{
protected override Node EvalHsl(HslColor color)
{
return color.GetLightness();
}
protected override Node EditHsl(HslColor color, Number number)
{
color.Lightness += number.Value/100;
return color.ToRgbColor();
}
}
public class DarkenFunction : LightenFunction
{
protected override Node EditHsl(HslColor color, Number number)
{
return base.EditHsl(color, -number);
}
}
public class LightnessFunction : LightenFunction
{
protected override Node EditHsl(HslColor color, Number number)
{
WarnNotSupportedByLessJS("lightness(color, number)", "lighten(color, number) or its opposite darken(color, number),");
return base.EditHsl(color, number);
}
}
}

View File

@ -0,0 +1,38 @@
namespace dotless.Core.Parser.Functions
{
using System;
using System.Linq;
using dotless.Core.Exceptions;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public abstract class ListFunctionBase : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMinArguments(1, Arguments.Count, this, Location);
Guard.ExpectNodeToBeOneOf<Expression, Value>(Arguments[0], this, Arguments[0].Location);
if(Arguments[0] is Expression)
{
var list = Arguments[0] as Expression;
var args = Arguments.Skip(1).ToArray();
return Eval(env, list.Value.ToArray(), args);
}
if(Arguments[0] is Value)
{
var list = Arguments[0] as Value;
var args = Arguments.Skip(1).ToArray();
return Eval(env, list.Values.ToArray(), args);
}
// We should never get here due to the type guard...
throw new ParsingException(string.Format("First argument to the list function was a {0}", Arguments[0].GetType().Name.ToLowerInvariant()), Location);
}
protected abstract Node Eval(Env env, Node[] list, Node[] args);
}
}

View File

@ -0,0 +1,77 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class MixFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMinArguments(2, Arguments.Count, this, Location);
Guard.ExpectMaxArguments(3, Arguments.Count, this, Location);
Guard.ExpectAllNodes<Color>(Arguments.Take(2), this, Location);
double weight = 50;
if (Arguments.Count == 3)
{
Guard.ExpectNode<Number>(Arguments[2], this, Location);
weight = ((Number)Arguments[2]).Value;
}
var colors = Arguments.Take(2).Cast<Color>().ToArray();
return Mix(colors[0], colors[1], weight);
}
protected Color Mix(Color color1, Color color2, double weight)
{
// Note: this algorithm taken from http://github.com/nex3/haml/blob/0e249c844f66bd0872ed68d99de22b774794e967/lib/sass/script/functions.rb
var p = weight / 100.0;
var w = p * 2 - 1;
var a = color1.Alpha - color2.Alpha;
var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
var w2 = 1 - w1;
var rgb = color1.RGB.Select((x, i) => x * w1 + color2.RGB[i] * w2).ToArray();
var alpha = color1.Alpha * p + color2.Alpha * (1 - p);
var color = new Color(rgb[0], rgb[1], rgb[2], alpha);
return color;
}
}
public class TintFunction : MixFunction
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(2, Arguments.Count, this, Location);
Guard.ExpectNode<Color>(Arguments[0], this, Location);
Guard.ExpectNode<Number>(Arguments[1], this, Location);
double weight = ((Number)Arguments[1]).Value;
return Mix(new Color(255, 255, 255),(Color)Arguments[0], weight);
}
}
public class ShadeFunction : MixFunction
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(2, Arguments.Count, this, Location);
Guard.ExpectNode<Color>(Arguments[0], this, Location);
Guard.ExpectNode<Number>(Arguments[1], this, Location);
double weight = ((Number)Arguments[1]).Value;
return Mix(new Color(0, 0, 0), (Color)Arguments[0], weight);
}
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Parser.Functions
{
public class MultiplyFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return a * b / 255;
}
}
}

View File

@ -0,0 +1,11 @@
namespace dotless.Core.Parser.Functions
{
using System;
public class NegationFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return 255 - Math.Abs(255 - b - a);
}
}
}

View File

@ -0,0 +1,24 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public abstract class NumberFunctionBase : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMinArguments(1, Arguments.Count, this, Location);
Guard.ExpectNode<Number>(Arguments[0], this, Arguments[0].Location);
var number = Arguments[0] as Number;
var args = Arguments.Skip(1).ToArray();
return Eval(env, number, args);
}
protected abstract Node Eval(Env env, Number number, Node[] args);
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Parser.Functions
{
public class OverlayFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return a < 128 ? 2 * a * b / 255 : 255 - 2 * (255 - a) * (255 - b) / 255;
}
}
}

View File

@ -0,0 +1,15 @@
namespace dotless.Core.Parser.Functions
{
using Exceptions;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
public class PercentageFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
return new Number(number.Value * 100, "%");
}
}
}

View File

@ -0,0 +1,27 @@
namespace dotless.Core.Parser.Functions
{
using System;
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class PowFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMinArguments(2, Arguments.Count, this, Location);
Guard.ExpectMaxArguments(2, Arguments.Count, this, Location);
Guard.ExpectAllNodes<Number>(Arguments, this, Location);
WarnNotSupportedByLessJS("pow(number, number)");
var first = Arguments.Cast<Number>().First();
var second = Arguments.Cast<Number>().ElementAt(1);
var value = Math.Pow(first.Value, second.Value);
return new Number(value);
}
}
}

View File

@ -0,0 +1,25 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
public class RedFunction : ColorFunctionBase
{
protected override Node Eval(Color color)
{
return new Number(color.RGB[0]);
}
protected override Node EditColor(Color color, Number number)
{
WarnNotSupportedByLessJS("red(color, number)");
var value = number.Value;
if (number.Unit == "%")
value = (value*255)/100d;
return new Color(color.R + value, color.G, color.B);
}
}
}

View File

@ -0,0 +1,19 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class RgbFunction : RgbaFunction
{
protected override Node Evaluate(Env env)
{
Guard.ExpectNumArguments(3, Arguments.Count, this, Location);
Arguments.Add(new Number(1d, ""));
return base.Evaluate(env);
}
}
}

View File

@ -0,0 +1,30 @@
namespace dotless.Core.Parser.Functions
{
using System.Linq;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class RgbaFunction : Function
{
protected override Node Evaluate(Env env)
{
if (Arguments.Count == 2)
{
var color = Guard.ExpectNode<Color>(Arguments[0], this, Location);
var number = Guard.ExpectNode<Number>(Arguments[1], this, Location);
return new Color(color.RGB, number.Value);
}
Guard.ExpectNumArguments(4, Arguments.Count, this, Location);
var args = Guard.ExpectAllNodes<Number>(Arguments, this, Location);
var rgb = args.Take(3).Select(n => n.ToNumber(255.0)).ToArray();
var alpha = args[3].ToNumber(1.0);
return new Color(rgb, alpha);
}
}
}

View File

@ -0,0 +1,24 @@
namespace dotless.Core.Parser.Functions
{
using System;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using dotless.Core.Utils;
public class RoundFunction : NumberFunctionBase
{
protected override Node Eval(Env env, Number number, Node[] args)
{
if (args.Length == 0)
{
return new Number(Math.Round(number.Value, MidpointRounding.AwayFromZero), number.Unit);
}
else
{
Guard.ExpectNode<Number>(args[0], this, args[0].Location);
return new Number(Math.Round(number.Value, (int)((Number)args[0]).Value, MidpointRounding.AwayFromZero), number.Unit);
}
}
}
}

View File

@ -0,0 +1,39 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure.Nodes;
using Tree;
using Utils;
public class SaturateFunction : HslColorFunctionBase
{
protected override Node EvalHsl(HslColor color)
{
return color.GetSaturation();
}
protected override Node EditHsl(HslColor color, Number number)
{
color.Saturation += number.Value/100;
return color.ToRgbColor();
}
}
public class DesaturateFunction : SaturateFunction
{
protected override Node EditHsl(HslColor color, Number number)
{
return base.EditHsl(color, -number);
}
}
public class SaturationFunction : SaturateFunction
{
protected override Node EditHsl(HslColor color, Number number)
{
WarnNotSupportedByLessJS("saturation(color, number)", "saturate(color, number) or its opposite desaturate(color, number),");
return base.EditHsl(color, number);
}
}
}

View File

@ -0,0 +1,10 @@
namespace dotless.Core.Parser.Functions
{
public class ScreenFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
return 255 - (255 - a) * (255 - b) / 255;
}
}
}

View File

@ -0,0 +1,11 @@
namespace dotless.Core.Parser.Functions
{
public class SoftlightFunction : ColorMixFunction
{
protected override double Operate(double a, double b)
{
double t = b * a / 255;
return t + a * (255 - (255 - a) * (255 - b) / 255 - t) / 255;
}
}
}

View File

@ -0,0 +1,65 @@
namespace dotless.Core.Parser.Functions
{
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class EFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMaxArguments(1, Arguments.Count, this, Location);
WarnNotSupportedByLessJS("e(string)", @"~""""");
if (Arguments.Count == 0)
return new TextNode("");
var str = Arguments[0];
if (str is Quoted)
return new TextNode((str as Quoted).UnescapeContents());
return new TextNode(str.ToCSS(env));
}
}
public class CFormatString : Function
{
protected override Node Evaluate(Env env)
{
WarnNotSupportedByLessJS("%(string, args...)", @"~"""" and string interpolation");
if (Arguments.Count == 0)
return new Quoted("", false);
Func<Node, string> stringValue = n => n is Quoted ? ((Quoted)n).Value : n.ToCSS(env);
var str = stringValue(Arguments[0]);
var args = Arguments.Skip(1).ToList();
var i = 0;
MatchEvaluator replacement = m =>
{
var value = (m.Value == "%s") ?
stringValue(args[i++]) :
args[i++].ToCSS(env);
return char.IsUpper(m.Value[1]) ?
Uri.EscapeDataString(value) :
value;
};
str = Regex.Replace(str, "%[sda]", replacement, RegexOptions.IgnoreCase);
var quote = Arguments[0] is Quoted ? (Arguments[0] as Quoted).Quote : null;
return new Quoted(str, quote);
}
}
}

View File

@ -0,0 +1,33 @@
namespace dotless.Core.Parser.Functions
{
using Infrastructure;
using Infrastructure.Nodes;
using Tree;
using Utils;
public class UnitFunction : Function
{
protected override Node Evaluate(Env env)
{
Guard.ExpectMaxArguments(2, Arguments.Count, this, Location);
Guard.ExpectNode<Number>(Arguments[0], this, Location);
var number = Arguments[0] as Number;
var unit = string.Empty;
if (Arguments.Count == 2)
{
if (Arguments[1] is Keyword)
{
unit = ((Keyword)Arguments[1]).Value;
}
else
{
unit = Arguments[1].ToCSS(env);
}
}
return new Number(number.Value, unit);
}
}
}

View File

@ -0,0 +1,11 @@
namespace dotless.Core.Parser.Infrastructure
{
using System.Collections.Generic;
using Tree;
public class Closure
{
public Ruleset Ruleset { get; set; }
public List<Ruleset> Context { get; set; }
}
}

View File

@ -0,0 +1,263 @@
namespace dotless.Core.Parser.Infrastructure
{
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Tree;
using Utils;
using dotless.Core.Parser.Infrastructure.Nodes;
public class Context : IEnumerable<IEnumerable<Selector>>
{
private List<List<Selector>> Paths { get; set; }
public Context()
{
Paths = new List<List<Selector>>();
}
public Context Clone()
{
var newPathList = new List<List<Selector>>();
return new Context {Paths = newPathList};
}
public void AppendSelectors(Context context, IEnumerable<Selector> selectors)
{
foreach (var selector in selectors)
{
AppendSelector(context, selector);
}
}
private void AppendSelector(Context context, Selector selector)
{
if (!selector.Elements.Any(e => e.Value == "&"))
{
if (context != null && context.Paths.Count > 0)
{
Paths.AddRange(context.Paths.Select(path => path.Concat(new[] { selector }).ToList()));
}
else
{
Paths.Add(new List<Selector>() { selector });
}
return;
}
// The paths are List<List<Selector>>
// The first list is a list of comma seperated selectors
// The inner list is a list of inheritance seperated selectors
// e.g.
// .a, .b {
// .c {
// }
// }
// == {{.a} {.c}} {{.b} {.c}}
//
// the elements from the current selector so far
var currentElements = new NodeList<Element>();
// the current list of new selectors to add to the path.
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
// by the parents
var newSelectors = new List<List<Selector>>() { new List<Selector>() };
foreach (Element el in selector.Elements)
{
// non parent reference elements just get added
if (el.Value != "&")
{
currentElements.Add(el);
} else
{
// the new list of selectors to add
var selectorsMultiplied = new List<List<Selector>>();
// merge the current list of non parent selector elements
// on to the current list of selectors to add
if (currentElements.Count > 0)
{
MergeElementsOnToSelectors(currentElements, newSelectors);
}
// loop through our current selectors
foreach(List<Selector> sel in newSelectors)
{
// if we don't have any parent paths, the & might be in a mixin so that it can be used
// whether there are parents or not
if (context.Paths.Count == 0)
{
// the combinator used on el should now be applied to the next element instead so that
// it is not lost
if (sel.Count > 0)
{
sel[0].Elements = new NodeList<Element>(sel[0].Elements);
sel[0].Elements.Add(new Element(el.Combinator, ""));
}
selectorsMultiplied.Add(sel);
}
else
{
// and the parent selectors
foreach (List<Selector> parentSel in context.Paths)
{
// We need to put the current selectors
// then join the last selector's elements on to the parents selectors
// our new selector path
List<Selector> newSelectorPath = new List<Selector>();
// selectors from the parent after the join
List<Selector> afterParentJoin = new List<Selector>();
Selector newJoinedSelector;
bool newJoinedSelectorEmpty = true;
//construct the joined selector - if & is the first thing this will be empty,
// if not newJoinedSelector will be the last set of elements in the selector
if (sel.Count > 0)
{
newJoinedSelector = new Selector(new NodeList<Element>(sel[sel.Count - 1].Elements));
newSelectorPath.AddRange(sel.Take(sel.Count - 1));
newJoinedSelectorEmpty = false;
}
else
{
newJoinedSelector = new Selector(new NodeList<Element>());
}
//put together the parent selectors after the join
if (parentSel.Count > 1)
{
afterParentJoin.AddRange(parentSel.Skip(1));
}
if (parentSel.Count > 0)
{
newJoinedSelectorEmpty = false;
// join the elements so far with the first part of the parent
if (parentSel[0].Elements[0].Value == null)
{
newJoinedSelector.Elements.Add(new Element(el.Combinator, parentSel[0].Elements[0].NodeValue));
}
else
{
newJoinedSelector.Elements.Add(new Element(el.Combinator, parentSel[0].Elements[0].Value));
}
newJoinedSelector.Elements.AddRange(parentSel[0].Elements.Skip(1));
}
if (!newJoinedSelectorEmpty)
{
// now add the joined selector
newSelectorPath.Add(newJoinedSelector);
}
// and the rest of the parent
newSelectorPath.AddRange(afterParentJoin);
// add that to our new set of selectors
selectorsMultiplied.Add(newSelectorPath);
}
}
}
// our new selectors has been multiplied, so reset the state
newSelectors = selectorsMultiplied;
currentElements = new NodeList<Element>();
}
}
// if we have any elements left over (e.g. .a& .b == .b)
// add them on to all the current selectors
if (currentElements.Count > 0)
{
MergeElementsOnToSelectors(currentElements, newSelectors);
}
Paths.AddRange(newSelectors.Select(sel => sel.Select(MergeJoinedElements).ToList()));
}
private void MergeElementsOnToSelectors(NodeList<Element> elements, List<List<Selector>> selectors)
{
if (selectors.Count == 0)
{
selectors.Add(new List<Selector>() { new Selector(elements) });
return;
}
foreach (List<Selector> sel in selectors)
{
// if the previous thing in sel is a parent this needs to join on to it?
if (sel.Count > 0)
{
sel[sel.Count - 1] = new Selector(sel[sel.Count - 1].Elements.Concat(elements));
}
else
{
sel.Add(new Selector(elements));
}
}
}
private static readonly char[] LeaveUnmerged = {'.', '#', ':'};
private Selector MergeJoinedElements(Selector selector) {
var elements = selector.Elements
.Select(e => e.Clone())
.ToList();
for (int i = 1; i < elements.Count; i++) {
var previousElement = elements[i - 1];
var currentElement = elements[i];
if (string.IsNullOrEmpty(currentElement.Value)) {
continue;
}
if (LeaveUnmerged.Contains(currentElement.Value[0])) {
continue;
}
if (currentElement.Combinator.Value != "") {
continue;
}
elements[i - 1] = new Element(previousElement.Combinator, previousElement.Value += currentElement.Value);
elements.RemoveAt(i);
i--;
}
return new Selector(elements);
}
public void AppendCSS(Env env) {
var selectors = Paths
.Where(p => p.Any(s => !s.IsReference))
.Select(path => path.Select(p => p.ToCSS(env)).JoinStrings("").Trim())
.Distinct();
env.Output.AppendMany(selectors, env.Compress ? "," : ",\n");
}
public string ToCss(Env env)
{
return string.Join(env.Compress ? "," : ",\n",Paths.Select(path => path.Select(p => p.ToCSS(env)).JoinStrings("").Trim()).ToArray());
}
public int Count
{
get { return Paths.Count; }
}
public IEnumerator<IEnumerable<Selector>> GetEnumerator()
{
return Paths.Cast<IEnumerable<Selector>>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,198 @@
namespace dotless.Core.Parser.Infrastructure
{
using System.Collections.Generic;
using Importers;
using Nodes;
using Tree;
public class DefaultNodeProvider : INodeProvider
{
public Element Element(Combinator combinator, Node value, NodeLocation location)
{
return new Element(combinator, value) { Location = location };
}
public Combinator Combinator(string value, NodeLocation location)
{
return new Combinator(value) { Location = location };
}
public Selector Selector(NodeList<Element> elements, NodeLocation location)
{
return new Selector(elements) { Location = location };
}
public Rule Rule(string name, Node value, NodeLocation location)
{
return new Rule(name, value) { Location = location };
}
public Rule Rule(string name, Node value, bool variadic, NodeLocation location)
{
return new Rule(name, value, variadic) { Location = location };
}
public Ruleset Ruleset(NodeList<Selector> selectors, NodeList rules, NodeLocation location)
{
return new Ruleset(selectors, rules) { Location = location };
}
public CssFunction CssFunction(string name, Node value, NodeLocation location)
{
return new CssFunction() { Name = name, Value = value, Location = location };
}
public Alpha Alpha(Node value, NodeLocation location)
{
return new Alpha(value) { Location = location };
}
public Call Call(string name, NodeList<Node> arguments, NodeLocation location)
{
return new Call(name, arguments) { Location = location };
}
public Color Color(string rgb, NodeLocation location)
{
var color = Tree.Color.FromHex(rgb);
color.Location = location;
return color;
}
public Keyword Keyword(string value, NodeLocation location)
{
return new Keyword(value) { Location = location };
}
public Number Number(string value, string unit, NodeLocation location)
{
return new Number(value, unit) { Location = location };
}
public Shorthand Shorthand(Node first, Node second, NodeLocation location)
{
return new Shorthand(first, second) { Location = location };
}
public Variable Variable(string name, NodeLocation location)
{
return new Variable(name) { Location = location };
}
public Url Url(Node value, IImporter importer, NodeLocation location)
{
return new Url(value, importer) { Location = location };
}
public Script Script(string script, NodeLocation location)
{
return new Script(script) { Location = location };
}
public GuardedRuleset GuardedRuleset(NodeList<Selector> selectors, NodeList rules, Condition condition, NodeLocation location)
{
return new GuardedRuleset(selectors, rules, condition) { Location = location };
}
public MixinCall MixinCall(NodeList<Element> elements, List<NamedArgument> arguments, bool important, NodeLocation location)
{
return new MixinCall(elements, arguments, important) { Location = location };
}
public MixinDefinition MixinDefinition(string name, NodeList<Rule> parameters, NodeList rules, Condition condition, bool variadic, NodeLocation location)
{
return new MixinDefinition(name, parameters, rules, condition, variadic) { Location = location };
}
public Import Import(Url path, Value features, ImportOptions option, NodeLocation location)
{
return new Import(path, features, option) { Location = location };
}
public Import Import(Quoted path, Value features, ImportOptions option, NodeLocation location)
{
return new Import(path, features, option) { Location = location };
}
public Directive Directive(string name, string identifier, NodeList rules, NodeLocation location)
{
return new Directive(name, identifier, rules) { Location = location };
}
public Media Media(NodeList rules, Value features, NodeLocation location)
{
return new Media(features, rules) { Location = location };
}
public KeyFrame KeyFrame(NodeList identifier, NodeList rules, NodeLocation location)
{
return new KeyFrame(identifier, rules) { Location = location };
}
public Directive Directive(string name, Node value, NodeLocation location)
{
return new Directive(name, value) { Location = location };
}
public Expression Expression(NodeList expression, NodeLocation location)
{
return new Expression(expression) { Location = location };
}
public Value Value(IEnumerable<Node> values, string important, NodeLocation location)
{
return new Value(values, important) { Location = location };
}
public Operation Operation(string operation, Node left, Node right, NodeLocation location)
{
return new Operation(operation, left, right) { Location = location };
}
public Assignment Assignment(string key, Node value, NodeLocation location)
{
return new Assignment(key, value) {Location = location};
}
public Comment Comment(string value, NodeLocation location)
{
return new Comment(value) { Location = location };
}
public TextNode TextNode(string contents, NodeLocation location)
{
return new TextNode(contents) { Location = location };
}
public Quoted Quoted(string value, string contents, bool escaped, NodeLocation location)
{
return new Quoted(value, contents, escaped) { Location = location };
}
public Extend Extend(List<Selector> exact, List<Selector> partial, NodeLocation location)
{
return new Extend(exact,partial) { Location = location };
}
public Node Attribute(Node key, Node op, Node val, NodeLocation location)
{
return new Attribute(key, op, val) { Location = location };
}
public Paren Paren(Node value, NodeLocation location)
{
return new Paren(value) { Location = location };
}
public Condition Condition(Node left, string operation, Node right, bool negate, NodeLocation location)
{
return new Condition(left, operation, right, negate) { Location = location };
}
#if CSS3EXPERIMENTAL
public RepeatEntity RepeatEntity(Node value, Node repeatCount, int index)
{
return new RepeatEntity(value, repeatCount) { Location = location };
}
#endif
}
}

View File

@ -0,0 +1,446 @@
using dotless.Core.Utils;
namespace dotless.Core.Parser.Infrastructure
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Functions;
using Nodes;
using Plugins;
using Tree;
using dotless.Core.Loggers;
public class Env
{
private Dictionary<string, Type> _functionTypes;
private readonly List<IPlugin> _plugins;
private readonly List<Extender> _extensions;
public Stack<Ruleset> Frames { get; protected set; }
public bool Compress { get; set; }
public bool Debug { get; set; }
public Node Rule { get; set; }
public ILogger Logger { get; set; }
public Output Output { get; private set; }
public Stack<Media> MediaPath { get; private set; }
public List<Media> MediaBlocks { get; private set; }
[Obsolete("The Variable Redefines feature has been removed to align with less.js")]
public bool DisableVariableRedefines { get; set; }
[Obsolete("The Color Compression feature has been removed to align with less.js")]
public bool DisableColorCompression { get; set; }
public bool KeepFirstSpecialComment { get; set; }
public bool IsFirstSpecialCommentOutput { get; set; }
public Parser Parser { get; set; }
public Env() : this(new Parser())
{
}
public Env(Parser parser) : this(parser, null, null)
{
}
protected Env(Parser parser, Stack<Ruleset> frames, Dictionary<string, Type> functions) : this(frames, functions) {
Parser = parser;
}
protected Env(Stack<Ruleset> frames, Dictionary<string, Type> functions) {
Frames = frames ?? new Stack<Ruleset>();
Output = new Output(this);
MediaPath = new Stack<Media>();
MediaBlocks = new List<Media>();
Logger = new NullLogger(LogLevel.Info);
_plugins = new List<IPlugin>();
_functionTypes = functions ?? new Dictionary<string, Type>();
_extensions = new List<Extender>();
ExtendMediaScope = new Stack<Media>();
if (_functionTypes.Count == 0)
AddCoreFunctions();
}
/// <summary>
/// Creates a new Env variable for the purposes of scope
/// </summary>
[Obsolete("Argument is ignored as of version 1.4.3.0. Use the parameterless overload of CreateChildEnv instead.", false)]
public virtual Env CreateChildEnv(Stack<Ruleset> ruleset)
{
return CreateChildEnv();
}
/// <summary>
/// Creates a new Env variable for the purposes of scope
/// </summary>
public virtual Env CreateChildEnv()
{
return new Env(null, _functionTypes)
{
Parser = Parser,
Parent = this,
Debug = Debug,
Compress = Compress,
DisableVariableRedefines = DisableVariableRedefines
};
}
private Env Parent { get; set; }
/// <summary>
/// Creates a new Env variable for the purposes of scope
/// </summary>
public virtual Env CreateVariableEvaluationEnv(string variableName) {
var env = CreateChildEnv();
env.EvaluatingVariable = variableName;
return env;
}
private string EvaluatingVariable { get; set; }
public bool IsEvaluatingVariable(string variableName) {
if (string.Equals(variableName, EvaluatingVariable, StringComparison.InvariantCulture)) {
return true;
}
if (Parent != null) {
return Parent.IsEvaluatingVariable(variableName);
}
return false;
}
public virtual Env CreateChildEnvWithClosure(Closure closure) {
var env = CreateChildEnv();
env.Rule = Rule;
env.ClosureEnvironment = new Env(new Stack<Ruleset>(closure.Context), this._functionTypes);
return env;
}
private Env ClosureEnvironment { get; set; }
/// <summary>
/// Adds a plugin to this Env
/// </summary>
public void AddPlugin(IPlugin plugin)
{
if (plugin == null) throw new ArgumentNullException("plugin");
_plugins.Add(plugin);
IFunctionPlugin functionPlugin = plugin as IFunctionPlugin;
if (functionPlugin != null)
{
foreach(KeyValuePair<string, Type> function in functionPlugin.GetFunctions())
{
string functionName = function.Key.ToLowerInvariant();
if (_functionTypes.ContainsKey(functionName))
{
string message = string.Format("Function '{0}' already exists in environment but is added by plugin {1}",
functionName, plugin.GetName());
throw new InvalidOperationException(message);
}
AddFunction(functionName, function.Value);
}
}
}
/// <summary>
/// All the visitor plugins to use
/// </summary>
public IEnumerable<IVisitorPlugin> VisitorPlugins
{
get
{
return _plugins.OfType<IVisitorPlugin>();
}
}
//Keep track of media scoping for extenders
public Stack<Media> ExtendMediaScope { get; set; }
/// <summary>
/// Returns whether the comment should be silent
/// </summary>
/// <param name="isDoubleStarComment"></param>
/// <returns></returns>
public bool IsCommentSilent(bool isValidCss, bool isCssHack, bool isSpecialComment)
{
if (!isValidCss)
return true;
if (isCssHack)
return false;
if (Compress && KeepFirstSpecialComment && !IsFirstSpecialCommentOutput && isSpecialComment)
{
IsFirstSpecialCommentOutput = true;
return false;
}
return Compress;
}
/// <summary>
/// Finds the first scoped variable with this name
/// </summary>
public Rule FindVariable(string name)
{
return FindVariable(name, Rule);
}
/// <summary>
/// Finds the first scoped variable matching the name, using Rule as the current rule to work backwards from
/// </summary>
public Rule FindVariable(string name, Node rule)
{
var previousNode = rule;
foreach (var frame in Frames)
{
var v = frame.Variable(name, null);
if (v)
return v;
previousNode = frame;
}
Rule result = null;
if (Parent != null) {
result = Parent.FindVariable(name, rule);
}
if (result != null) {
return result;
}
if (ClosureEnvironment != null) {
return ClosureEnvironment.FindVariable(name, rule);
}
return null;
}
[Obsolete("This method will be removed in a future release.", false)]
public IEnumerable<Closure> FindRulesets<TRuleset>(Selector selector) where TRuleset : Ruleset
{
return FindRulesets(selector).Where(c => c.Ruleset is TRuleset);
}
/// <summary>
/// Finds the first Ruleset matching the selector argument that inherits from or is of type TRuleset (pass this as Ruleset if
/// you are trying to find ANY Ruleset that matches the selector)
/// </summary>
public IEnumerable<Closure> FindRulesets(Selector selector)
{
var matchingRuleSets = Frames
.Reverse()
.SelectMany(frame => frame.Find<Ruleset>(this, selector, null))
.Where(matchedClosure => {
if (!Frames.Any(frame => frame.IsEqualOrClonedFrom(matchedClosure.Ruleset)))
return true;
var mixinDef = matchedClosure.Ruleset as MixinDefinition;
if (mixinDef != null)
return mixinDef.Condition != null;
return false;
}).ToList();
if (matchingRuleSets.Any())
{
return matchingRuleSets;
}
if (Parent != null)
{
var parentRulesets = Parent.FindRulesets(selector);
if (parentRulesets != null)
{
return parentRulesets;
}
}
if (ClosureEnvironment != null)
{
return ClosureEnvironment.FindRulesets(selector);
}
return null;
}
/// <summary>
/// Adds a Function to this Env object
/// </summary>
public void AddFunction(string name, Type type)
{
if (name == null) throw new ArgumentNullException("name");
if (type == null) throw new ArgumentNullException("type");
_functionTypes[name] = type;
}
/// <summary>
/// Given an assembly, adds all the dotless Functions in that assembly into this Env.
/// </summary>
public void AddFunctionsFromAssembly(Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException("assembly");
var functions = GetFunctionsFromAssembly(assembly);
AddFunctionsToRegistry(functions);
}
private void AddFunctionsToRegistry(IEnumerable<KeyValuePair<string, Type>> functions) {
foreach (var func in functions) {
AddFunction(func.Key, func.Value);
}
}
private static Dictionary<string, Type> GetFunctionsFromAssembly(Assembly assembly) {
var functionType = typeof (Function);
return assembly
.GetTypes()
.Where(t => functionType.IsAssignableFrom(t) && t != functionType)
.Where(t => !t.IsAbstract)
.SelectMany(GetFunctionNames)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
private static Dictionary<string, Type> GetCoreFunctions() {
var functions = GetFunctionsFromAssembly(Assembly.GetExecutingAssembly());
functions["%"] = typeof (CFormatString);
return functions;
}
private static readonly Dictionary<string, Type> CoreFunctions = GetCoreFunctions();
private void AddCoreFunctions() {
_functionTypes = new Dictionary<string, Type>(CoreFunctions);
}
/// <summary>
/// Given a function name, returns a new Function matching that name.
/// </summary>
public virtual Function GetFunction(string name)
{
Function function = null;
name = name.ToLowerInvariant();
if (_functionTypes.ContainsKey(name))
{
function = (Function)Activator.CreateInstance(_functionTypes[name]);
function.Logger = Logger;
}
return function;
}
private static IEnumerable<KeyValuePair<string, Type>> GetFunctionNames(Type t)
{
var name = t.Name;
if (name.EndsWith("function", StringComparison.InvariantCultureIgnoreCase))
name = name.Substring(0, name.Length - 8);
name = Regex.Replace(name, @"\B[A-Z]", "-$0");
name = name.ToLowerInvariant();
yield return new KeyValuePair<string, Type>(name, t);
if(name.Contains("-"))
yield return new KeyValuePair<string, Type>(name.Replace("-", ""), t);
}
public void AddExtension(Selector selector, Extend extends, Env env)
{
foreach (var extending in extends.Exact)
{
Extender match = null;
if ((match = _extensions.OfType<ExactExtender>().FirstOrDefault(e => e.BaseSelector.ToString().Trim() == extending.ToString().Trim())) == null)
{
match = new ExactExtender(extending, extends);
_extensions.Add(match);
}
match.AddExtension(selector,env);
}
foreach (var extending in extends.Partial)
{
Extender match = null;
if ((match = _extensions.OfType<PartialExtender>().FirstOrDefault(e => e.BaseSelector.ToString().Trim() == extending.ToString().Trim())) == null)
{
match = new PartialExtender(extending, extends);
_extensions.Add(match);
}
match.AddExtension(selector,env);
}
if (Parent != null) {
Parent.AddExtension(selector, extends, env);
}
}
public void RegisterExtensionsFrom(Env child) {
_extensions.AddRange(child._extensions);
}
public IEnumerable<Extender> FindUnmatchedExtensions() {
return _extensions.Where(e => !e.IsMatched);
}
public ExactExtender FindExactExtension(string selection)
{
if (ExtendMediaScope.Any())
{
var mediaScopedExtensions = ExtendMediaScope.Select(media => media.FindExactExtension(selection)).FirstOrDefault(result => result != null);
if (mediaScopedExtensions != null) {
return mediaScopedExtensions;
}
}
return _extensions.OfType<ExactExtender>().FirstOrDefault(e => e.BaseSelector.ToString().Trim() == selection);
}
public PartialExtender[] FindPartialExtensions(Context selection)
{
if (ExtendMediaScope.Any())
{
var mediaScopedExtensions = ExtendMediaScope.Select(media => media.FindPartialExtensions(selection)).FirstOrDefault(result => result.Any());
if (mediaScopedExtensions != null) {
return mediaScopedExtensions;
}
}
return _extensions.OfType<PartialExtender>()
.WhereExtenderMatches(selection)
.ToArray();
}
[Obsolete("This method doesn't return the correct results. Use FindPartialExtensions(Context) instead.", false)]
public PartialExtender[] FindPartialExtensions(string selection)
{
if (ExtendMediaScope.Any())
{
var mediaScopedExtensions = ExtendMediaScope.Select(media => media.FindPartialExtensions(selection)).FirstOrDefault(result => result.Any());
if (mediaScopedExtensions != null) {
return mediaScopedExtensions;
}
}
return _extensions.OfType<PartialExtender>().Where(e => selection.Contains(e.BaseSelector.ToString().Trim())).ToArray();
}
public override string ToString()
{
return Frames.Select(f => f.ToString()).JoinStrings(" <- ");
}
}
}

View File

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using System.Text;
using System.Threading;
using dotless.Core.Parser.Tree;
using dotless.Core.Utils;
namespace dotless.Core.Parser.Infrastructure
{
public class ExactExtender : Extender
{
[Obsolete("Use the overload that accepts the Extend node")]
public ExactExtender(Selector baseSelector) : this(baseSelector, null)
{
}
public ExactExtender(Selector baseSelector, Extend extend) : base(baseSelector, extend)
{
}
}
public class PartialExtender : Extender
{
[Obsolete("Use the overload that accepts the Extend node")]
public PartialExtender(Selector baseSelector) : this(baseSelector, null)
{
}
public PartialExtender(Selector baseSelector, Extend extend) : base(baseSelector, extend)
{
}
public IEnumerable<Selector> Replacements(string selection)
{
foreach (var ex in ExtendedBy)
{
yield return new Selector(new []{new Element(null, selection.Replace(BaseSelector.ToString().Trim(), ex.ToString().Trim()))});
}
}
}
internal static class ExtenderMatcherExtensions {
internal static IEnumerable<PartialExtender> WhereExtenderMatches(this IEnumerable<PartialExtender> extenders, Context selection) {
var selectionElements = selection.SelectMany(selectors => selectors.SelectMany(s => s.Elements)).ToList();
return extenders.Where(e => e.ElementListMatches(selectionElements));
}
/// <summary>
/// Tests whether or not this extender matches the selector elements in <paramref name="list"/>
/// by checking if the elements in <see cref="Extender.BaseSelector"/> are a subsequence of the ones in
/// <paramref name="list"/>.
///
/// The special case in the comparison is that if the subsequence element we are comparing is the last one in its
/// sequence, we don't compare combinators.
///
/// A practical example of why we do that is an extendee with the selector:
///
/// .test .a
///
/// and an extender with the selector
///
/// .test
///
/// The extender should match, even though the .test element in the extendee will have the descendant combinator " "
/// and the .test element in the extender won't.
/// </summary>
private static bool ElementListMatches(this PartialExtender extender, IList<Element> list) {
int count = extender.BaseSelector.Elements.Count;
return extender.BaseSelector.Elements.IsSubsequenceOf(list, (subIndex, subElement, index, seqelement) => {
if (subIndex < count - 1) {
return Equals(subElement.Combinator, seqelement.Combinator) &&
string.Equals(subElement.Value, seqelement.Value) &&
Equals(subElement.NodeValue, seqelement.NodeValue);
}
return string.Equals(subElement.Value, seqelement.Value) &&
Equals(subElement.NodeValue, seqelement.NodeValue);
});
}
}
public class Extender
{
public Selector BaseSelector { get; private set; }
public List<Selector> ExtendedBy { get; private set; }
public bool IsReference { get; set; }
public bool IsMatched { get; set; }
public Extend Extend { get; private set; }
[Obsolete("Use the overload that accepts the Extend node")]
public Extender(Selector baseSelector)
{
BaseSelector = baseSelector;
ExtendedBy = new List<Selector>();
IsReference = baseSelector.IsReference;
}
public Extender(Selector baseSelector, Extend extend) : this(baseSelector)
{
Extend = extend;
}
public static string FullPathSelector()
{
throw new NotImplementedException();
}
public void AddExtension(Selector selector, Env env)
{
var selectorPath = new List<IEnumerable<Selector>> {new [] {selector} };
selectorPath.AddRange(env.Frames.Skip(1).Select(f => f.Selectors.Where(partialSelector => partialSelector != null)));
ExtendedBy.Add(GenerateExtenderSelector(env, selectorPath));
}
private Selector GenerateExtenderSelector(Env env, List<IEnumerable<Selector>> selectorPath) {
var context = GenerateExtenderSelector(selectorPath);
return new Selector(new[] {new Element(null, context.ToCss(env)) }) { IsReference = IsReference };
}
private Context GenerateExtenderSelector(List<IEnumerable<Selector>> selectorStack) {
if (!selectorStack.Any()) {
return null;
}
var parentContext = GenerateExtenderSelector(selectorStack.Skip(1).ToList());
var childContext = new Context();
childContext.AppendSelectors(parentContext, selectorStack.First());
return childContext;
}
}
}

Some files were not shown because too many files have changed in this diff Show More