<commit_before>// $begin{copyright}
//
// This file is part of WebSharper
//
// Copyright (c) 2008-2014 IntelliFactory
//
// Licensed under the Apache License, Version 2.0 (the "License"); you
// may not use this file except in compliance with the License.  You may
// obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.  See the License for the specific language governing
// permissions and limitations under the License.
//
// $end{copyright}

namespace WebSharper.UI.Next.Templating

open System
open System.Collections.Generic
open System.IO
open System.Reflection
open System.Xml.Linq
open System.Text.RegularExpressions
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Core.CompilerServices
open WebSharper.UI.Next
open WebSharper.UI.Next.Client

open ProviderImplementation.ProvidedTypes

[<AutoOpen>]
module internal Utils =
    let ( +/ ) a b = System.IO.Path.Combine(a, b)
        
    let inline ( |>! ) x f = f x; x

    let xn n = XName.Get n

    let ExprArray (exprs: Expr<'T> seq) : Expr<'T[]> =
        Expr.NewArray(typeof<'T>, [ for e in exprs -> e.Raw ]) |> Expr.Cast

    type StringParts =
        | TextPart of string
        | TextHole of string
        | TextViewHole of string

    let ViewOf ty = typedefof<View<_>>.MakeGenericType([|ty|])
    let VarOf ty = typedefof<Var<_>>.MakeGenericType([|ty|])
    let EventTy = typeof<WebSharper.JavaScript.Dom.Element -> WebSharper.JavaScript.Dom.Event -> unit>

    [<RequireQualifiedAccess>]
    type Hole =
        | Var of valTy: System.Type * hasView: bool
        | View of valTy: System.Type
        | Event
        | Simple of ty: System.Type

        member this.ArgType =
            match this with
            | Var (valTy = t) -> VarOf t
            | View (valTy = t) -> ViewOf t
            | Event -> EventTy
            | Simple (ty = t) -> t

[<TypeProvider>]
type TemplateProvider(cfg: TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces()

    let thisAssembly = Assembly.GetExecutingAssembly()

    let refAssembly name =
        cfg.ReferencedAssemblies
        |> Seq.map (fun an -> Assembly.LoadFrom an)
        |> Seq.tryFind (fun a -> name = a.GetName().Name)
        |> function None -> null | Some a -> a

    do  System.AppDomain.CurrentDomain.add_AssemblyResolve(fun _ args ->
            refAssembly <| AssemblyName(args.Name).Name
        )
    
    let rootNamespace = "WebSharper.UI.Next.Templating"
    let templateTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "Template", None)

    let mutable watcher: FileSystemWatcher = null

    let textHoleRegex = Regex @"\$(!?)\{([^\}]+)\}" 
    let dataHole = xn"data-hole"
    let dataReplace = xn"data-replace"
    let dataTemplate = xn"data-template"
    let dataChildrenTemplate = xn"data-children-template"
    let dataVar = xn"data-var"
    let dataAttr = xn"data-attr"
    let dataEvent = "data-event-"
    let (|SpecialHole|_|) (a: XAttribute) =
        match a.Value.ToLowerInvariant() with
        | "scripts" | "meta" | "styles" -> Some()
        | _ -> None

    do
        this.Disposing.Add <| fun _ ->
            if watcher <> null then watcher.Dispose()

        templateTy.DefineStaticParameters(
            [ProvidedStaticParameter("path", typeof<string>)],
            fun typename pars ->
                match pars with
                | [| :? string as path |] ->
                    let ty = ProvidedTypeDefinition(thisAssembly, rootNamespace, typename, None)

                    let htmlFile = 
                        if Path.IsPathRooted path then path 
                        else cfg.ResolutionFolder +/ path

                    if cfg.IsInvalidationSupported then
                        if watcher <> null then watcher.Dispose()
                        watcher <-
                            new FileSystemWatcher(Path.GetDirectoryName htmlFile, Path.GetFileName htmlFile, 
                                NotifyFilter = (NotifyFilters.LastWrite ||| NotifyFilters.Security ||| NotifyFilters.FileName)
                            )
                        watcher.Changed.Add <| fun _ -> this.Invalidate()
                        watcher.Deleted.Add <| fun _ -> this.Invalidate()
                        watcher.Renamed.Add <| fun _ -> this.Invalidate()
                        watcher.Created.Add <| fun _ -> this.Invalidate()
                        watcher.EnableRaisingEvents <- true

                    let xml =
                        try // Try to load the file as a whole XML document, ie. single root node with optional DTD.
                            let xmlDoc = XDocument.Load(htmlFile)
                            let xml = XElement(xn"wrapper")
                            xml.Add(xmlDoc.Root)
                            xml
                        with :? System.Xml.XmlException ->
                            // Try to load the file as a XML fragment, ie. potentially several root nodes.
                            XDocument.Parse("<wrapper>" + File.ReadAllText htmlFile + "</wrapper>").Root

                    let innerTemplates =
                        xml.Descendants() |> Seq.choose (fun e -> 
                            match e.Attribute(dataTemplate) with
                            | null ->
                                match e.Attribute(dataChildrenTemplate) with
                                | null -> None
                                | a -> Some (a.Value, false, e)
                            | a -> 
                                let name = a.Value 
                                a.Remove()
                                Some (name, true, e)
                        )
                        |> List.ofSeq
                        |> List.map (fun (name, wrap, e) ->
                            e.Remove()
                            name,
                            if wrap then XElement(xn"body", e) else e
                        )    
                                        
                    let addTemplateMethod (t: XElement) (toTy: ProvidedTypeDefinition) =                        
                        let holes = Dictionary()

                        let getSimpleHole name : Expr<'T> =
                            match holes.TryGetValue(name) with
                            | true, _ ->
                                failwithf "Invalid multiple use of variable name in the same template: %s" name
                            | false, _ ->
                                holes.Add(name, Hole.Simple typeof<'T>)
                            Expr.Var (Var (name, typeof<'T>)) |> Expr.Cast

                        let getVarHole name : Expr<Var<'T>> =
                            match holes.TryGetValue(name) with
                            | true, Hole.Var(valTy = valTy) ->
                                if valTy = typeof<'T> then
                                    ()
                                else
                                    failwithf "Invalid multiple use of variable name for differently typed Vars: %s" name
                            | true, Hole.View valTy ->
                                if valTy = typeof<'T> then
                                    holes.Remove(name) |> ignore
                                    holes.Add(name, Hole.Var(valTy = typeof<'T>, hasView = true))
                                else
                                    failwithf "Invalid multiple use of variable name for differently typed View and Var: %s" name
                            | true, Hole.Simple _<commit_msg>Fix #32: Templating: allow an element to be both hole and template<commit_after>// $begin{copyright}
//
// This file is part of WebSharper
//
// Copyright (c) 2008-2014 IntelliFactory
//
// Licensed under the Apache License, Version 2.0 (the "License"); you
// may not use this file except in compliance with the License.  You may
// obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.  See the License for the specific language governing
// permissions and limitations under the License.
//
// $end{copyright}

namespace WebSharper.UI.Next.Templating

open System
open System.Collections.Generic
open System.IO
open System.Reflection
open System.Xml.Linq
open System.Text.RegularExpressions
open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Core.CompilerServices
open WebSharper.UI.Next
open WebSharper.UI.Next.Client

open ProviderImplementation.ProvidedTypes

[<AutoOpen>]
module internal Utils =
    let ( +/ ) a b = System.IO.Path.Combine(a, b)
        
    let inline ( |>! ) x f = f x; x

    let xn n = XName.Get n

    let ExprArray (exprs: Expr<'T> seq) : Expr<'T[]> =
        Expr.NewArray(typeof<'T>, [ for e in exprs -> e.Raw ]) |> Expr.Cast

    type StringParts =
        | TextPart of string
        | TextHole of string
        | TextViewHole of string

    let ViewOf ty = typedefof<View<_>>.MakeGenericType([|ty|])
    let VarOf ty = typedefof<Var<_>>.MakeGenericType([|ty|])
    let EventTy = typeof<WebSharper.JavaScript.Dom.Element -> WebSharper.JavaScript.Dom.Event -> unit>

    [<RequireQualifiedAccess>]
    type Hole =
        | Var of valTy: System.Type * hasView: bool
        | View of valTy: System.Type
        | Event
        | Simple of ty: System.Type

        member this.ArgType =
            match this with
            | Var (valTy = t) -> VarOf t
            | View (valTy = t) -> ViewOf t
            | Event -> EventTy
            | Simple (ty = t) -> t

    type XElement with
        member this.AnyAttributeOf([<ParamArray>] names: XName[]) =
            (null, names)
            ||> Array.fold (fun a n ->
                match a with
                | null -> this.Attribute(n)
                | a -> a
            )

[<TypeProvider>]
type TemplateProvider(cfg: TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces()

    let thisAssembly = Assembly.GetExecutingAssembly()

    let refAssembly name =
        cfg.ReferencedAssemblies
        |> Seq.map (fun an -> Assembly.LoadFrom an)
        |> Seq.tryFind (fun a -> name = a.GetName().Name)
        |> function None -> null | Some a -> a

    do  System.AppDomain.CurrentDomain.add_AssemblyResolve(fun _ args ->
            refAssembly <| AssemblyName(args.Name).Name
        )
    
    let rootNamespace = "WebSharper.UI.Next.Templating"
    let templateTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "Template", None)

    let mutable watcher: FileSystemWatcher = null

    let textHoleRegex = Regex @"\$(!?)\{([^\}]+)\}" 
    let dataHole = xn"data-hole"
    let dataReplace = xn"data-replace"
    let dataTemplate = xn"data-template"
    let dataChildrenTemplate = xn"data-children-template"
    let dataVar = xn"data-var"
    let dataAttr = xn"data-attr"
    let dataEvent = "data-event-"
    let (|SpecialHole|_|) (a: XAttribute) =
        match a.Value.ToLowerInvariant() with
        | "scripts" | "meta" | "styles" -> Some()
        | _ -> None

    do
        this.Disposing.Add <| fun _ ->
            if watcher <> null then watcher.Dispose()

        templateTy.DefineStaticParameters(
            [ProvidedStaticParameter("path", typeof<string>)],
            fun typename pars ->
                match pars with
                | [| :? string as path |] ->
                    let ty = ProvidedTypeDefinition(thisAssembly, rootNamespace, typename, None)

                    let htmlFile = 
                        if Path.IsPathRooted path then path 
                        else cfg.ResolutionFolder +/ path

                    if cfg.IsInvalidationSupported then
                        if watcher <> null then watcher.Dispose()
                        watcher <-
                            new FileSystemWatcher(Path.GetDirectoryName htmlFile, Path.GetFileName htmlFile, 
                                NotifyFilter = (NotifyFilters.LastWrite ||| NotifyFilters.Security ||| NotifyFilters.FileName)
                            )
                        watcher.Changed.Add <| fun _ -> this.Invalidate()
                        watcher.Deleted.Add <| fun _ -> this.Invalidate()
                        watcher.Renamed.Add <| fun _ -> this.Invalidate()
                        watcher.Created.Add <| fun _ -> this.Invalidate()
                        watcher.EnableRaisingEvents <- true

                    let xml =
                        try // Try to load the file as a whole XML document, ie. single root node with optional DTD.
                            let xmlDoc = XDocument.Load(htmlFile)
                            let xml = XElement(xn"wrapper")
                            xml.Add(xmlDoc.Root)
                            xml
                        with :? System.Xml.XmlException ->
                            // Try to load the file as a XML fragment, ie. potentially several root nodes.
                            XDocument.Parse("<wrapper>" + File.ReadAllText htmlFile + "</wrapper>").Root

                    let innerTemplates =
                        xml.Descendants() |> Seq.choose (fun e -> 
                            match e.Attribute(dataTemplate) with
                            | null ->
                                match e.Attribute(dataChildrenTemplate) with
                                | null -> None
                                | a -> Some (e, a, false)
                            | a -> Some (e, a, true)
                        )
                        |> List.ofSeq
                        |> List.map (fun (e, a, wrap) ->
                            let name = a.Value
                            a.Remove()
                            let e =
                                match e.AnyAttributeOf(dataReplace, dataHole) with
                                | null ->
                                    e.Remove()
                                    e
                                | a ->
                                    let e = XElement(e)
                                    e.Attribute(a.Name).Remove()
                                    e
                            name, if wrap then XElement(xn"body", e) else e
                        )
                                        
                    let addTemplateMethod (t: XElement) (toTy: ProvidedTypeDefinition) =                        
                        let holes = Dictionary()

                        let getSimpleHole name : Expr<'T> =
                            match holes.TryGetValue(name) with
                            | true, _ ->
                                failwithf "Invalid multiple use of variable name in the same template: %s" name
                            | false, _ ->
                                holes.Add(name, Hole.Simple typeof<'T>)
                            Expr.Var (Var (name, typeof<'T>)) |> Expr.Cast

                        let getVarHole name : Expr<Var<'T>> =
                            match holes.TryGetValue(name) with
                            | true, Hole.Var(valTy = valTy) ->
                                if valTy = typeof<'T> then
                                    ()
                                else
                                    failwithf "Invalid multiple use of variable name for differently typed Vars: %s" name
                            | true, Hole.View valTy ->
                                if valTy = typeof<'T> then
                                    holes.Remove(name) |> ignore
                                    holes.Add(name, Hole.Var(valTy = typeof<'T>, hasView = true))
                                else
                                    failwithf "Invalid multiple use of variable name for differently typed View and Var: %s" name
                            | true, Hole.Simple _
{'fraction_non_alphanumeric': 0.09166308287768016, 'fraction_numerical': 0.0015973459482705658, 'mean_word_length': 1.3536726431463273, 'pattern_counts': {'":': 0, '<': 68, '<?xml version=': 0, '>': 142, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1542310', 'n_tokens_mistral': 4262, 'n_tokens_neox': 4040, 'n_words': 1163}