How to relativize URLs in css files in Hakyll? - css

In my Hakyll site I have a stylesheet linked into the page:
<link rel="stylesheet" type="text/css" href="/css/my.css">
This CSS contains a #font-face directive linking to a font file:
#font-face {
font-family: "Bla";
src: url("/data/bla.ttf") format("truetype");
}
The problem is that font's URL doesn't get relativized by relativizeUrls even if I move it into a <script> tag inside the page itself. How to solve this problem?

tl;dr – You could use Beerend Lauwers's hakyll-extra package (doesn't seem to be on hackage yet), which provides a relativizeUrl macro. Or, implement your own as follows:
If you don't have too many links, and don't fancy introducing a CSS parser just in order to do this, you could just create a function field - effectively, a macro -- which allows you to call, e.g. relativize("/some/url") from within the page. (I ran into a similar problem, because I wanted to relativize links to a stylesheet for use only by old versions of Internet Explorer; and to TagSoup, the links looked as if they were within comments, so it didn't process them.)
First, we need to write a version of relativizeUrls which just operates on a single URL:
import Data.List as L
-- | Relativize URL. Same logic as "relativizeUrlsWith" in
-- Hakyll.Web.Html.RelativizeUrls, but for just one url.
relativizeUrl :: String -- ^ Path to the site root
-> String -- ^ link to relativize
-> String -- ^ Resulting link
relativizeUrl root = rel
where
isRel :: String -> Bool
isRel x = "/" `L.isPrefixOf` x && not ("//" `L.isPrefixOf` x)
rel x = if isRel x then root ++ x else x
Then, we define a "function field" which can be added to contexts.
import Data.Maybe (maybe)
-- ugh. ugly name.
relativizeFuncField :: Context a
relativizeFuncField = functionField "relativize" relativize
where
relativize :: [String] -> Item a -> Compiler String
relativize args item = do
siteRoot <- getRoot <$> (getRoute $ itemIdentifier item)
arg <- case args of
[arg] -> return arg
_ -> error "relativize: expected only 1 arg"
return $ relativizeUrl siteRoot arg
getRoot :: Maybe String -> String
getRoot = maybe (error "relativize: couldn't get route") toSiteRoot
Then, anywhere you want to use this macro, instead of using, say, defaultContext, use relativizeFuncField <> defaultContext. e.g.:
import Data.Monoid( (<>) )
main =
-- ...
match (fromList ["about.rst", "contact.markdown"]) $ do
route $ setExtension "html"
compile $ pandocCompiler
>>= loadAndApplyTemplate "templates/default.html" (relativizeFuncField <> defaultContext)
>>= relativizeUrls
So, finally, that means that within a file, you can write $relativize("/path/to/file")$ in any spot where TagSoup isn't already relativizing links.
Hope that's of use :)
(This was using Hakyll 4.9.0.0, but I assume other 4.X versions are much the same.)
edited: p.s., many thanks to Beerend Lauwers, who explained Hakyll function fields in his post here
edited again: d'oh. I didn't see that Beerend has actually already put a relativizeUrl function in his hakyll-extra package.

Hakyll's relativizeURLs uses TagSoup to parse and pretty-print HTML, so it can only work on URLs found inside HTML attributes. I don't know of any existing functionality to extend this to CSS rather than just HTML attributes.
The relevant code goes through every tag parsed by TagSoup and applies a function to attributes it recognizes as URLs:
-- | Apply a function to each URL on a webpage
withUrls :: (String -> String) -> String -> String
withUrls f = withTags tag
where
tag (TS.TagOpen s a) = TS.TagOpen s $ map attr a
tag x = x
attr (k, v) = (k, if isUrlAttribute k then f v else v)
(From Hakyll.Web.HTML)
There's no way to change this traversal logic from the provided relativizeURLs compiler so you'll probably have to write your own. Luckily it's pretty simple: it gets the site root (with toSiteRoot), then uses withURLs to apply a function to every URL that turns absolute paths into relative ones.
relativizeUrls item = do
route <- getRoute $ itemIdentifier item
return $ case route of
Nothing -> item
Just r -> fmap (relativizeUrlsWith $ toSiteRoot r) item
relativizeUrlsWith root = withUrls rel
where
isRel x = "/" `isPrefixOf` x && not ("//" `isPrefixOf` x)
rel x = if isRel x then root ++ x else x
(Excerpts from Hakyll.Web.RelativizeURLs).
You'll need to combine this sort of process with a lightweight CSS parser of some sort. It'll look something like this (in pseudocode):
relativizeCssUrls root = renderCSS . fmap relativize . parseCSS
where relativize (URL url)
| isRel url = URL (root <> url)
| otherwise = URL url
relativize other = other
I haven't used any CSS parsing/printing libraries so I can't give you a good suggestion here, but css-text seems like a decent starting point.

I've gone an easier way. I've used this code to obtain root path relative to a current item:
rootPath :: Compiler String
rootPath = (toSiteRoot . fromJust) <$> (getUnderlying >>= getRoute)
And then created a Context with constant field:
fontCtx = do
root <- rootPath
return $ constField "fontRoot" root
Finally, i moved #font-face clause out of a CSS file into HTML one and used my field there:
<style type="text/css">
#font-face {
...
src: url("$fontRoot$/data/bla.ttf") format("truetype");
}
</style>
That context field turned out to be quite useful in other places, like path strings in Javascript code, which I also use.

Related

How to make `eval` to use specific path to resolve `include` in Julia?

This expression eval(Meta.parse("begin $(code)\nend")) would eval Julia code with include resolved relative to the file eval... is defined in.
How to change it so that it would use another directory? Something like
eval(Meta.parse("begin $(code)\nend"), resolve_include_relative_to=somepath)
Or, if that's not possible - relative to current directory (like REPL)?
UPDATE
Possible solution - replacing relative paths with absolute
function fix_include(code::String, relative_path::String)::String
code = replace(code, r"include\(\"\./(.*?)\"\)" => s"include(\"__relative_path__/\1\")")
code = replace(code, r"__relative_path__" => relative_path)
code
end
eval(Meta.parse("begin $(fix_include(code, relative_path))\nend")
Use case:
I'm evaluating snippets of string code, sometimes they contain include statement with relative paths and they resolved against wrong path. I want to explicitly specify tell it what path should be used for resolution. Or at the very least always use the current directory '.', not the directory where the file with the eval(xxx) line defined ./lib/runner.jl.
This function should do the trick (include is relative to the path in task-local storage, as kinda indicated by the docstring):
function eval_at(code; path = "none", mod = Main)
tls = task_local_storage()
hassource = haskey(tls, :SOURCE_PATH)
hassource && (path′ = tls[:SOURCE_PATH])
# setting this is enough for `include` to be correct
tls[:SOURCE_PATH] = path
try
# let's use the three-arg `include_string` here to make sure `#__FILE__`
# etc resolve correctly
return include_string(mod, code, path)
finally
hassource ?
(tls[:SOURCE_PATH] = path′) :
delete!(tls, :SOURCE_PATH)
end
end
Example usage:
julia> pwd()
"/home/pfitzseb/Documents"
julia> isfile("test.jl")
false
julia> include("test.jl")
ERROR: could not open file /home/pfitzseb/Documents/test.jl
julia> eval_at("""include("test.jl")""", path = "/home/pfitzseb/foo.jl")
Main.LogT
julia> eval_at("""#__FILE__""", path = "/home/pfitzseb/foo.jl")
"/home/pfitzseb/foo.jl"
It is not clear what exactly you want to do but for "do something in a folder" situations usually cd() do ... end syntax works great.
code = quote
cd("c:/temp") do
println("do something in $(pwd())")
#do something more
end
end
And now use it
julia> eval(code)
do something in c:\temp
Depending in your scenario you might consider using macros to manipulate code blocks that do something in a directory. Below is a simple example not offering more functionality than cd ... do ... end statement but of course it can be extended:
macro doinfolder(folder, what)
isa(what, Expr) || error("what needs to be some expression")
quote
cd($folder) do
$what
#other useful code injections can occur here...
end
end
end
And now use it
julia> #doinfolder "C:\\temp" pwd()
"C:\\temp"
Will also work with more complex code structures
julia> #doinfolder "C:\\temp" begin
pwd()
end
"C:\\temp"

How to replace a path in AST with just parsed javascript(string)?

https://astexplorer.net/#/gist/70df1bc56b9ee73d19fc949d2ef829ed/7e14217fd8510f0bf83f3372bf08454b7617bce1
I've found now I'm trying to replace an expression and I don't care whats in it.
in this example I've found the this.state.showMenu && this.handleMouseDown portion in
<a
onMouseDown={this.state.showMenu && this.handleMouseDown}
>
I need to convert to:
<a
onMouseDown={this.state.showMenu ? this.handleMouseDown : undefined}
>
how can I do so without explicitly reconstructing the tree? I just want to do something like
path.replaceText("this.state.showMenu ? this.handleMouseDown : undefined")
Here's a transformer that does what you describe:
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source)
root
.find(j.JSXExpressionContainer)
.replaceWith(path => {
return j.jsxExpressionContainer(
j.conditionalExpression(
j.identifier(j(path.value.expression.left).toSource()),
j.identifier(j(path.value.expression.right).toSource()),
j.identifier('undefined')
)
)
})
return root.toSource()
}
See it in action here.
You can also just put arbitrary text in the JSXExpressionContainer node:
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source)
root
.find(j.JSXExpressionContainer)
.replaceWith(path => {
return j.jsxExpressionContainer(
j.identifier('whatever you want')
)
})
return root.toSource()
}
See this example.
Finally, you don't even need to return a JSXExpressionContainer.
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source)
root
.find(j.JSXExpressionContainer)
.replaceWith(path => {
return j.identifier("this isn't valid JS, but works just fine")
})
return root.toSource()
}
See the result here.
You can do this with our DMS Software Reengineering Toolkit.
DMS treats HTML pages as native HTML text with embedded scripting sublanguage, which might be ECMAScript, or VBScript, or something else.
So the process of building a complete HTML "AST" requires that one first
build the pure HTML part, then find all the "onXXXXX" tags and convert them to ASTs in the chosen scripting language. DMS can distinguish AST nodes from different langauges so there's no chance of confusion in understanding the compound AST.
So, first we need to parse the HTML document of interest (code edited for pedagogical reasons):
(local (;; [my_HTML_AST AST:Node]
(includeunique `DMS/Domains/HTML/Component/ParserComponent.par')
);;
(= working_graph (AST:CreateForest))
(= my_HTML_AST (Parser:ParseFile parser working_graph input_file_full_path))
Then we need to walk over the HTML tree, find the JavaScript text fragments, parse them and splice the parsed ECMASCript tree in to replace the text fragment:
(local (;; (includeunique `DMS/Domains/ECMAScript/Components/ParserComponent.par') );;
(ScanNodes my_HTML_AST
(lambda (function boolean AST:Node)
(ifthenelse (!! (~= (AST:GetNodeType ?) GrammarConstants:Rule:Attribute) ; not an HTML attribute
(~= (Strings:Prefix (AST:GetLiteralString (AST:GetFirstChild ?)) `on')) ; not at action attribute
)&&
~t ; scan deeper into tree
(value (local (;; [my_ECMAScript_AST AST:Node]
[ECMASCript_text_stream streams:buffer]
);;
(= ECMAScript_text_stream (InputStream:MakeBufferStream (AST:StringLiteral (AST:GetSecondChild ?))=
(= my_ECMAScript_AST (Parser:ParseStream parser working_graph ECMAScript_text_stream))
(= AST:ReplaceNode ? my_ECMAScript_AST)
(= InputStream:Close my_ECMAScript_text_stream)
~f) ; no need to scan deeper here
)ifthenelse
)lambda
) ; at this point, we have a mixed HTML/ECMAScript tree
)local
If the scripting language can be something else, then this code has to change. If your pages are all HTML + ECMAScript, you can wrap the above stuff into a black box and call it "(ParseHTML)" which is what the other answer assumed happened.
Now for the actual work. OP want to replace a pattern found in his HTML with another. Here DMS shines because you can write those patterns, using the syntax of the targeted language, directly as a DMS Rewrite Rule (see this link for details).
source domain ECMAScript;
target domain ECMAScript;
rule OP_special_rewrite()=expression -> expression
"this.state.showMenu && this.handleMouseDown"
-> "this.state.showMenu ? this.handleMouseDown : undefined "
Now you need to apply this rewrite:
(RSL:Apply my_HTML_AST `OP_special_rewrite') ; applies this rule to every node in AST
; only those that match get modified
And finally regenerate text from the AST:
(PrettyPrinter:PrintStream my_ECMAScript_AST input_file_full_path)
OP's example is pretty simply because he is matching against what amounts to a constant pattern. DMS's rules can be written using all kinds of pattern variables; see above link, and can have arbitrary conditions over the matched pattern and other state information to control whether the rule applies.

Understanding Elm's Type Signature return types

I am trying to understand elm's type signatures. What does this function return exactly? It appears to be a function that accepts no arguments and returns ...
route : Parser (Page -> a) a
As a learning exercise for myself I'm going to try to answer this. Others will chip in if I get something wrong.
I'm sure you are used to something like
type Person = Adult String | Child String Age
Child is a type that takes two parameters. Parser is the same. But it's definition is pretty formidable
type Parser a b =
Parser (State a -> List (State b))
type alias State value =
{ visited : List String
, unvisited : List String
, params : Dict String String
, value : value
}
That said, you see how Parser is ultimately a wrapper around a function from a State to a list of States. Ultimately it is going to be passed a List of 'unvisited' strings or params; it will progressively 'visit' each one and the result will be combined into the final 'value'.
Next, note that while Parser takes two type parameters - a, b - parseHash is defined
parseHash : Parser (a -> a) a -> Location -> Maybe a
So, your original
route : Parser (Page -> a) a
is going to have to be
route : Parser (Page -> Page) Page
to type check.
To return to your original question, therefore, route is a Parser (which is a very general object) that encapsulates instructions on how to go from one Page to another, and can be used - via parseHash - to tell you what Page to go to next, and that is of course what you would expect from a router.
Hope this gets you started

Pulling out a column in all records in a Sqlite table into a concatenated string in Haskell with Persist

I'm trying to learn Haskell, specifically Snap, Blaze HTML5 and Persist. I would like to take every row in a table, select a single column from it, and then concatenate the values into a single string.
I've previously worked with C#'s LINQ quite extensively and under Entity Framework I could do it like this:
String.Join(", ", dbContext.People.Select(p => p.Name));
This would compile down to SELECT Name FROM People, with C# then concatenating those rows into a string with ", " in between.
To try and get the concatenation part right, I put this together, which seems to work:
intercalate ", " $ map show [1..10]
(it counts 1-9, concatenates with ", " in between the items)
However, I can't get this to work with Database.Persist.Sqlite. I'm not sure I quite understand the syntax here in Haskell. To contact the DB and retrieve the rows, I have to call: (as far as I understand)
runSqlite "TestDB" $ selectList ([] :: [Filter Person]) [] 0 0
The problem is that I'm not sure how to get the list out of runSqlite. runSqlite doesn't return the type I'm after, so I can't use the return value of runSqlite. How would I do this?
Thank you for reading.
To clarify:
Snap requires that I define a function to return the HTML I wish to send back to the client making the HTTP request. This means that:
page = runSqlite "TestDB" $ do
{pull data from the DB)
Is no-go as I can't return the data via the runSqlite call, and as far as I know I can't have a variable in the page function which is set within the runSqlite do block. All examples I can find just write to IO in the runSqlite do block, which is not what needs to be done here.
The type of runSqlite is:
runSqlite :: (MonadBaseControl IO m, MonadIO m) => Text -> SqlPersistT (NoLoggingT (ResourceT m)) a -> m a
And the type of selectList is:
[Filter val] -> [SelectOpt val] -> m [Entity val]
So, you can actually, use the nice do notation of Monad, to extract it:
runSqlite "TestDB" $ do
myData <- selectList ([] :: [Filter Person]) [] 0 0
-- Now do stuff with myData
The <- thing gets the list out of the monad. I would suggest you to go through this chapter to get an idea of how Persistent is used. Note that the chapters in the book assume a basic Haskell understanding.
The issue is that I want to use the selectList outside of runSqlite as
I need to pass the concatenated string to a Blaze HTML5 tag builder:
body $ do p (concatenated list...)
For this case, just define a function that does your intended task:
myLogic :: [SqlColumnData] -> String -- Note that SqlColumnData is hypothetical
myLogic xs = undefined
And then just call them appropriately in your main function:
main = runSqlite "TestDB" $ do
myData <- selectList ([] :: [Filter Person]) [] 0 0
let string = myLogic myData
-- do any other remaining stuff
It hadn't clicked that if I didn't use a do block with runSqlite, the result of the last call in the statement was the return value of the statement - this makes total sense.
https://gist.github.com/egonSchiele/5400694
In this example (not mine) the readPosts function does exactly what I'm after and cleared up some Haskell syntax confusion.
Thank you for your help #Sibi.

Collecting the output of an external command using OCaml

What is the right way to call an external command and collect its output in OCaml?
In Python, I can do something like this:
os.popen('cmd').read()
How I can get all of an external program's output in OCaml? Or, better, OCaml with Lwt?
Thanks.
You want Unix.open_process_in, which is described on page 388 of the OCaml system manual, version 3.10.
For Lwt,
val pread : ?env:string array -> command -> string Lwt.t
seems to be a good contender. Documentation here: http://ocsigen.org/docu/1.3.0/Lwt_process.html
let process_output_to_list2 = fun command ->
let chan = Unix.open_process_in command in
let res = ref ([] : string list) in
let rec process_otl_aux () =
let e = input_line chan in
res := e::!res;
process_otl_aux() in
try process_otl_aux ()
with End_of_file ->
let stat = Unix.close_process_in chan in (List.rev !res,stat)
let cmd_to_list command =
let (l,_) = process_output_to_list2 command in l
There are lots of examples on PLEAC.
You can use the third party library Rashell which uses Lwt to define some high-level primitives to read output from processes. These primitives, defined in the module Rashell_Command, are:
exec_utility to read the output of a process as a string;
exec_test to only read the exit status of a process;
exec_query to read the output of a process line by line as a string Lwt_stream.t
exec_filter to use an external program as a string Lwt_stream.t -> string Lwt_stream.t transformation.
The command function is used to create command contexts on which the previous primitives can be applied, it has the signature:
val command : ?workdir:string -> ?env:string array -> string * (string array) -> t
(** [command (program, argv)] prepare a command description with the
given [program] and argument vector [argv]. *)
So for instance
Rashell_Command.(exec_utility ~chomp:true (command("", [| "uname" |])))
is a string Lwt.t which returns the “chomped” string (new line removed) of the “uname” command. As a second example
Rashell_Command.(exec_query (command("", [| "find"; "/home/user"; "-type"; "f"; "-name"; "*.orig" |])))
is a string Lwt_stream.t whose elements are the paths of the file found by the command
find /home/user -type f -name '*.orig'
The Rashell library defines also interfaces to some commonly used commands, and a nice interface to the find command is defined in Rashell_Posix – which by the way guarantees POSIX portability.

Resources