Is there a way to make fsi print the documentation for a function?
I can retrieve the type by just evaluating the function as a value but I would like the know what the xml docs say if they exist.
Without the use case I have 2 suggestions. If this is just about help then use and FSX file.
If you are actually wanting to print out the docs in FSI you could use the following code to print out the docs for a member. Since the xml comments are not stored in the dll, this code checks if the xml doc exists in the location of the dll and loads that in if so.
#r "packages/Newtonsoft.Json/lib/net45/Newtonsoft.Json.dll"
open System.IO
open System.Xml
open System.Reflection
let loadXml (path:string) =
if (File.Exists(path)) then
let xml = new XmlDocument()
xml.Load(path)
Some xml
else None
let xmlForMember (maybeXml:XmlDocument option) (mi:MemberInfo) =
let path = sprintf "M:%s.%s" mi.DeclaringType.FullName mi.Name
match maybeXml with
| None -> None
| Some xml -> xml.SelectSingleNode("//member[starts-with(#name, '" + path + "')]") |> Some
let docFrom (node:XmlNode option) =
match node with
| None -> "No docs available"
| Some n -> n.InnerXml
Usage would be something like this but you could neaten and package this up for your needs:
let t = typedefof<Newtonsoft.Json.JsonSerializer>
let assembly = t.Assembly
let dllPath = assembly.Location
printfn "Assembly location: %s" dllPath
let expectedXmlPath = Path.ChangeExtension(dllPath, ".xml")
printfn "Expected xml: %s" expectedXmlPath
let xmlDoc = expectedXmlPath |> loadXml
let mi = t.GetMember("Create").[0]
let docNode = mi |> xmlForMember xmlDoc
docNode |> docFrom |> printfn "%s"
Hope this can get you started.
Related
I've got an application that I've built in SAFE-Stack using websockets, more or less following the approach here: https://github.com/CompositionalIT/safe-sockets
It works fine but the Elmish debugger doesn't like the type of WsSender in this example:
type ConnectionState =
| DisconnectedFromServer
| ConnectedToServer of WsSender
| Connecting
member this.IsConnected =
match this with
| ConnectedToServer _ -> true
| DisconnectedFromServer | Connecting -> false
and WsSender = Msg -> Unit
giving the following error message in the Browser Console:
Can anyone tell me how to go about fixing this issue? (Assuming it's fixable and that I've diagnosed the problem correctly.) Thanks.
you see this error because of Elmish.Debugger using Thoth.Json to serialize your Msg/Model to a JSON format.
The type WsSender can't be represented in a JSON format because it is a function. So Thoth.Json is asking you to explain how it should encode this type.
You can do that by creating what is called an extraCoder like that:
In your case, you will have to create a fake encoder/decoder "just" to make the Debugger happy.
module CustomEncoders =
let wsSenderEncoder (_ : WsSender) = Encode.string "WsSender function"
let wsSenderDecoder = Decode.fail "Decoding is not supported for WsSender type"
let myExtraCoders =
Extra.empty
|> Extra.withCustom wsSenderEncoder wsSenderDecoder
let modelEncoder = Encode.Auto.generateEncoder(extra = myExtraCoders)
let modelDecoder = Decode.Auto.generateDecoder(extra = myExtraCoders)
In your Program creation, you should replace Program.withDebugger by Program.withDebuggerCoders and give it the encoder and decoder you created.
Program.withDebuggerCoders CustomEncoders.modelEncoder CustomEncoders.modelDecoder
I had a bit of a play around to try and come up with something that would make it easier to have multiple extra coders if required. This seems to work - thought it might be helpful to others.
module CustomEncoders =
let inline addDummyCoder<'b> extrasIn =
let typeName = string typeof<'b>
let simpleEncoder(_ : 'b) = Encode.string (sprintf "%s function" typeName)
let simpleDecoder = Decode.fail (sprintf "Decoding is not supported for %s type" typeName)
extrasIn |> Extra.withCustom simpleEncoder simpleDecoder
let inline buildExtras<'a> extraCoders =
let myEncoder:Encoder<'a> = Encode.Auto.generateEncoder(extra = extraCoders)
let myDecoder:Decoder<'a> = Decode.Auto.generateDecoder(extra = extraCoders)
(myEncoder, myDecoder)
type TestType = Msg -> Unit
type TestType2 = string -> Unit
let extras = Extra.empty
|> CustomEncoders.addDummyCoder<TestType>
|> CustomEncoders.addDummyCoder<TestType2>
|> CustomEncoders.buildExtras<Model.Model>
#if DEBUG
open Elmish.Debug
open Elmish.HMR
#endif
Program.mkProgram Model.init Model.update View.render
|> Program.withSubscription subs
#if DEBUG
|> Program.withConsoleTrace
#endif
|> Program.withReactBatched "elmish-app"
#if DEBUG
|> Program.withDebuggerCoders (fst extras) (snd extras)
#endif
|> Program.run
If anyone has a better idea of how to do it, I'd be happy to update this answer with their suggestions. Also, the apostrophe in the generic type seems to upset the code prettifier above - do I need to do something to fix that?
I'm trying to combine two XmlDocument.xmls for WebAPI 2.0 documentation. But when I do the below code it returns < /doc> with no errors. It looks like I'm doing everything right, so I'm a bit confused. Where the printf statement is, that is where it is failing with the result value. Before and after the code seems to work flawlessly.
#r "../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"
#r "System.Xml.Linq.dll"
open FSharp.Data
open System.IO
// Define your library scripting code here
let seedPath = __SOURCE_DIRECTORY__ + "../../FieldOps/bin"
let outFile = __SOURCE_DIRECTORY__ + "../../FieldOps/App_Data/XmlDocument.xml"
type XmlDocument = XmlProvider<"""<?xml version="1.0"?>
<doc>
<assembly><name>lala</name></assembly>
<members>
<member name="">
<summary>lala</summary>
<param name="">lala</param>
<param name="">lala</param>
<returns>lala</returns>
</member>
<member name=""></member>
</members>
</doc>
""">
let xmlPaths =
[
"../bin/Release"
]
let seed = XmlDocument.Load(Path.Combine(seedPath, "XmlDocument.xml"))
let addFile path = Path.Combine(path, "XmlDocument.XML")
let loadXmlDoc (path: string) = XmlDocument.Load(path)
let joinXmlDocuments (acc: XmlDocument.Doc) path =
let doc = (addFile >> loadXmlDoc) path
let members = Array.append acc.Members doc.Members
let result = new XmlDocument.Doc(acc.Assembly, members)
printf "result ------------------- %A --------------------" result
result
let joinedDocs =
xmlPaths
|> List.fold (joinXmlDocuments) seed
let xml = joinedDocs.XElement.ToString()
File.WriteAllText(outFile, xml)
Update → I think I know why it has the problem. Probably because they are immutable so you need to create a new one. But, how to easily do that, I don't know.
It appears that the XmlProvider doesn't recognize it's own types. Not sure what is going on exactly. But when I recreate the types from scratch it doesn't come back with the empty < /doc> value that I was seeing.
Here's what I ended up doing:
let assembly = new XmlDocument.Assembly(acc.Assembly.Name)
let members =
Array.append acc.Members doc.Members
|> Array.map
(
fun x ->
let parameters =
x.Params
|> Array.map (fun x -> new XmlDocument.Param(x.Name, x.Value))
new XmlDocument.Member(x.Name, x.Summary, parameters, x.Returns)
)
let result = XmlDocument.Doc(assembly, members)
This is fixed in F# Data 2.3.0-beta1
I wrote this script from some resources I found. It's working but I some files I have problem. I am new in F# so how can I change line with FileHelpersException to get exact line where is the problem? Thanks
// Learn more about F# at http://fsharp.net
// See the 'F# Tutorial' project for more help.
open FileHelpers
open System
[<DelimitedRecord(",")>]
type CsvRecord =
class
val field1 : string
val field2 : string
val field3 : int
new () = {
field1 = ""
field2 = ""
field3 = 0
}
end
[<EntryPoint>]
let main argv =
use file = System.IO.File.CreateText("result.txt")
let engine = new FileHelperEngine<CsvRecord>()
engine.Encoding <- new Text.UTF8Encoding()
let res =
try
engine.ReadFile("test.csv")
with
| :? FileHelpersException -> Array.empty<CsvRecord>
for record in res do
fprintfn file "%s" record.field1
printf "DONE!"
let s = Console.ReadLine()
0 // return an integer exit code
I suggest that you use CsvTypeProvider instead. When there's a mismatch the error message states the line which has the error
open FSharp.Data
[<EntryPoint>]
let main argv =
use file = System.IO.File.CreateText("result.txt")
let csv = new CsvProvider<"test.csv">()
for record in csv.Data do
fprintfn file "%s" record.field1
If you want to ignore the lines with errors, just pass IgnoreErrors=true as an extra parameter to CsvProvider
This question is about the FileHelpers library you are using, not F#, so looking at the docs for that might help. In this case you can check for ConvertException instead of FileHelpersException, which contains members that give you more details about the member.
try
engine.ReadFile("test.csv")
with
| :? ConvertException as ex ->
printfn "ERROR: Line %d Col %d" ex.LineNumber ex.ColumnNumber
Array.empty<CsvRecord>
I agree with Gustavo though, you might find it easier to use the CsvTypeProvider.
i am getting this error in the interactive window on http://www.tryfsharp.org. It works fine in visual studio and im not sure how to tackle it.Any help would be appreciated
let randomNumberGenerator count =
let rnd = System.Random()
List.init count (fun numList -> rnd.Next(0, 100))
let rec sortFunction = function
| [] -> []
| l -> let minNum = List.min l in
let rest = List.filter (fun i -> i <> minNum) l in
let sortedList = sortFunction rest in
minNum :: sortedList
let List = randomNumberGenerator 10
let sortList = sortFunction List
printfn "Randomly Generated numbers in a NON-SORTED LIST\n"
printfn "%A" List
printfn "\nSORTED LIST \n"
printfn "%A \n" sortList
error FS0039: The field, constructor or member 'init' is not defined
Aprreciate your help
You should be getting the error only when you run the code for the second time and it shoul behave the same in the TryF# console as well as locally in Visual Studio.
The problem is that you're declaring a value named List:
let List = randomNumberGenerator 10
which hides the standard module List. After you declare the value List.init tries to access a member of this List value instead of accessing a function in the standard List module.
There is a good reason for naming conventions, such as using lowercase for local variable names :-)
I have an XML file, which I open in F# like this:
let Bookmarks(xmlFile:string) =
let xml = XDocument.Load(xmlFile)
Once I have the XDocument I need to navigate it using LINQ to XML and extract all specific tags. Part of my solution is:
let xname (tag:string) = XName.Get(tag)
let tagUrl (tag:XElement) = let attribute = tag.Attribute(xname "href")
attribute.Value
let Bookmarks(xmlFile:string) =
let xml = XDocument.Load(xmlFile)
xml.Elements <| xname "A" |> Seq.map(tagUrl)
How can I extract the specific tags from the XML file?
#light
open System
open System.Xml.Linq
let xname s = XName.Get(s)
let bookmarks (xmlFile : string) =
let xd = XDocument.Load xmlFile
xd.Descendants <| xname "bookmark"
This will find all the descendant elements of "bookmark". If you only want direct descendants, use the Elements method (xd.Root.Elements <| xname "whatever").
Caveat: I've never done linq-to-xml before, but looking through other posts on the topic, this snippet has some F# code that compiles and does something, and thus it may help you get started:
open System.IO
open System.Xml
open System.Xml.Linq
let xmlStr = #"<?xml version='1.0' encoding='UTF-8'?>
<doc>
<blah>Blah</blah>
<a href='urn:foo' />
<yadda>
<blah>Blah</blah>
<a href='urn:bar' />
</yadda>
</doc>"
let xns = XNamespace.op_Implicit ""
let a = xns + "a"
let reader = new StringReader(xmlStr)
let xdoc = XDocument.Load(reader)
let aElements = [for x in xdoc.Root.Elements() do
if x.Name = a then
yield x]
let href = xns + "href"
aElements |> List.iter (fun e -> printfn "%A" (e.Attribute(href)))