Fake: What is the difference between fileset selectors: !!, ++ and !+ - f#-fake

There are different fileset selectors in FAKE. What is the difference between these: !!, ++, !+?
I found these examples:
Example 1:
// Filesets
let appReferences =
!! "src/app/**/*.csproj"
++ "src/app/**/*.fsproj"
Example 2:
// files
let appReferences =
!+ "./src/**/*.fsproj"
|> Scan
Example 3:
let filesToZip =
!+ (buildDir + "/**/*.*")
-- "*.zip"
|> Scan

!+ "./src/**/*.fsproj"
|> Scan
is obsolete and replaced by !!
-- "*.zip"
excludes files from the fileset above.

Related

How do you iterate a List (Maybe a)

I have the following graphQL result:
[Just { details = Just "Engine failure at 33 seconds and loss of
vehicle", launch_year = Just "2006", links = Just { article_link =
Just
"https://www.space.com/2196-spacex-inaugural-falcon-1-rocket-lost-launch.html"
}, mission_name = Just "FalconSat" }]
Based on the following types:
type alias Launch =
{ mission_name : Maybe String
, details : Maybe String
, launch_year : Maybe String
, links : Maybe LaunchLinks
}
type alias Launches =
Maybe (List (Maybe Launch))
type alias LaunchLinks =
{ article_link : Maybe String
}
I want to List.map through and display the results in unordered list. I started with this:
renderLaunch : Launches -> Html Msg
renderLaunch launches =
div [] <|
case launches of
Nothing ->
[ text "Nothing here" ]
Just launch ->
launch
|> List.map (\x -> x)
|> ul []
But I keep getting this error:
This function cannot handle the argument sent through the (|>) pipe:
141| launch 142| |> List.map (\x
-> x) 143| |> ul []
^^^^^ The argument is:
List (Maybe Launch)
But (|>) is piping it a function that expects:
List (Html msg)
The problem is that the Just launch case needs to result in a List (Html msg) but the code results in a different type being returned.
When you are using List.map (\x -> x), it is essentially a no-op. You are iterating over a List (Maybe Launch) and returning the same thing. I'd recommend creating another function that takes a Maybe Launch value and use that as your mapping function. For example:
displayLaunch : Maybe Launch -> Html Msg
displayLaunch launch =
case launch of
Nothing -> text "No launch"
Just l -> text (Debug.toString l)
Now you can plug that into your mapping function:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
But, whoops! Now you get a new error indicating:
The 2nd branch is:
Html Msg
But all the previous branches result in:
List (Html msg)
The problem here is that we are now returning a ul from the Just launch branch and we need to return a list of html. You can use List.singleton to create a list with just one item:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
|> List.singleton

Elixir - Manipulating a 2 dimensional list

Hope everybody is having a beautiful 2019 even though we're just a day in.
I am currently working on a small Phoenix app where I'm manipulating PDF files (in the context of this question I'm splitting them) and then uploading them to S3. Later on I have to delete the temporary files created by pdftk ( a pdf tool ) I use to split them up and also show the s3 links in the response body since this is an API request.
The way I have structured this is as following:
Inside my Split module where the core business logic is:
filenames = []
s3_links = []
Enum.map(pages, fn(item) ->
split_filename = item
|> split(filename)
link = split_filename
|> FileHelper.result_file_bytes()
|> ManageS3.upload()
|> FileHelper.save_file(work_group_id, pass)
[filenames ++ split_filename, s3_links ++ link]
end)
|> transform()
{filenames, s3_links}
The important things are split_filename and link
This is what I'm getting when I call an IO.inspect in the transform() method:
[
["87cdcd73-5b27-4757-a472-78aaf6cc6864.pdf",
"Some_S3_LINK00"],
["0ab460ca-5019-4864-b0ff-343966c7d72a.pdf",
"Some_S3_LINK01"]
]
The structuring is [[filename, s3_link], [filename, s3_link]] whereas the desired outcome would be that of [ [list of all filenames], [list of s3 links].
If anybody can lend a hand I would be super grateful. Thanks in advance!
Sidenotes:
Assigning filenames = []; s3_links = [] in the very beginning makes zero sense. Enum.map already maps the input. What you need is probably Enum.reduce/3.
Don’t use the pipe |> operator when the pipe consists of the only call, it is considered an anti-pattern by Elixir core team.
Always start pipes with a term.
Solution:
Reduce the input into the result using Enum.reduce/3 directly to what you need.
pages
|> Enum.reduce([[], []], fn item, [files, links] ->
split_filename = split(item, filename)
link =
split_filename
|> FileHelper.result_file_bytes()
|> ManageS3.upload()
|> FileHelper.save_file(work_group_id, pass)
[[split_filename | files], [link | links]]
end)
|> Enum.map(&Enum.reverse/1)
|> IO.inspect(label: "Before transform")
|> transform()
You did not provide the input to test it, but I believe it should work.
Instead of working on lists of lists, you may want to consider using tuples with lists. Something like the following should work for you.
List.foldl(pages, {[], []}, fn(item, {filenames, links}) ->
filename = split(item, filename)
link =
file_name
|> FileHelper.result_file_bytes()
|> ManagerS3.upload()
|> FileHelper.save_file(work_group_id, pass)
{[filename | filenames], [link | links]}
end)
This will return a value that looks like
{
["87cdcd73-5b27-4757-a472-78aaf6cc6864.pdf",
"0ab460ca-5019-4864-b0ff-343966c7d72a.pdf"],
["Some_S3_LINK00",
"Some_S3_LINK01"]
}
Though, depending on how you are using these values, maybe a list of tuples would be more appropriate. Something like
Enum.map(pages, fn(item) ->
filename = split(item, filename)
link =
filename
|> FileHelper.result_file_bytes()
|> ManageS3.upload()
|> FileHelper.save_file(work_group_id, pass)
{filename, link}
end)
would return
[
{"87cdcd73-5b27-4757-a472-78aaf6cc6864.pdf", "Some_S3_LINK00"},
{"0ab460ca-5019-4864-b0ff-343966c7d72a.pdf", "Some_S3_LINK01"}
]

elixir dynamic module call

How can I call function func() in a module called App.Reporting.Name
based on the string "name" which is not known until runtime
using String.to_atom or to_existing_atom does not work :
alias App.Reporting.Name
module = "name" |> String.capitalise |> String.to_atom
apply(module, :func, [])
Without the alias, this does not work either
module = "App.Reporting.Name" |> String.to_atom
apply(module, :func, [])
I get an (UndefinedFunctionError) and (module :"App.Reporting.Name" is not available)
thanks
Your second approach is almost correct, you just need to prefix Elixir. because App.Reporting.Name is equal to :"Elixir.App.Reporting.Name", not :"App.Reporting.Name" since Elixir prefixes all module names (names starting with an uppercase letter) with Elixir. before turning it into an atom:
iex(1)> App.Reporting.Name == :"App.Reporting.Name"
false
iex(2)> App.Reporting.Name == :"Elixir.App.Reporting.Name"
true
So, this code should work:
module = "Elixir.App.Reporting.Name" |> String.to_atom
apply(module, :func, [])
and so should this:
module = Module.concat(App.Reporting, "name" |> String.capitalize |> String.to_atom)
apply(module, :func, [])
The reason yours isn't working is because the String.to_atom does just that, turns a string into an atom. Because there is no module called "App.Reporting.Name" it's most likely App.Reporting.Name it errors.
Not sure if this is the best way to do this, just one that sprang to mind. But you could do something like this:
iex(2)> module = "Casing"
"Casing"
iex(3)> Module.concat(String, "#{module}") |> apply(:upcase, ["test sentence"])
"TEST SENTENCE"
Another solution could be to create a macro that automatically does this process, however that is not something I am that great at so you will have to go through the docs here for that one.

How do you specify Fake Target inputs and output?

In the build systems that I'm familiar with (make and msbuild) there's a way to specify the inputs and outputs for a target. If the time stamps on the input files are earlier than those on the outputs the task is skipped. I can't find something similar in FAKE.
For example if I wanted to translate this Makefile to Fake
a.exe: a.fs
fsharpc a.fs -o a.exe
it might look like:
Target "a.exe" (fun _ -> ["a.fs"] |> FscHelper.compile [...])
However, when I run the build command it will always execute the compiler and produce a new a.exe regardless the modification time on a.fs. Is there a simple way to get the same behavior as the makefile?
You could use =?>and provide a function that returns true or false if the task should run.
let fileModified f1 f2 =
FileInfo(f1).LastWriteTime > FileInfo(f2).LastWriteTime
and then in target dependencies
=?> ("a.exe", fileModified "a.fs" "a.exe")
A more complete code example to flesh out Lazydevs answer:
#r "packages/FAKE/tools/FakeLib.dll"
open Fake
open System.IO
Target "build" (fun _ ->
trace "built"
)
let needsUpdate f1 f2 =
let lastWrite files =
files
|> Seq.map (fun f -> FileInfo(f).LastWriteTime)
|> Seq.max
let t1 = lastWrite f1
let t2 = lastWrite f2
t1 > t2
let BuildTarget name infiles outfiles fn =
Target name (fn infiles)
name =?> ("build", needsUpdate infiles outfiles)
BuildTarget "compile" ["Test2.fs"; "Test1.fs"] ["Test2.dll"] (fun files _ ->
files
|> FscHelper.compile [
FscHelper.Target FscHelper.TargetType.Library
]
|> function 0 -> () | c -> failwithf "compile error"
)
RunTargetOrDefault "build"

How to pass projects in order in FAKE

I need to build vb6 projects in order. How should I pass them to Vb6Make?
let projs = [
"a.vbp"
"b.vbp"
"c.vbp"
]
Target "VB6" (fun _ ->
!! projs // <- ?
|> Vb6Make
)
!! and ++ can be used for single files files as well. And FullName is important for directories.
let vb6dir = FullName "./bin"
let tempdir = FullName "./temp"
let projs =
!! "src\a\a.vbp"
++ "src\b\b.vbp"
++ "src\c\c.vbp"
Target "BuildVb6" (fun _ ->
projs
|> Fake.Vb6Helper.Vb6Make(fun c ->
{ c with
Logdir = tempdir
Outdir = vb6dir })
)