text:
module SalesForceBulkApi

# pre requirements
## Other packages
using HTTP, LightXML, CSV, ProgressMeter, DataFrames, Distributed
import JSON

export login, sf_bulkapi_query, all_object_fields, fields_description, object_list, multiquery

# login
## login function and session token gathering
function login_post(username, password, version)
    xml = """<?xml version="1.0" encoding="utf-8" ?>
    <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
        <env:Body>
            <n1:login xmlns:n1="urn:partner.soap.sforce.com">
                <username>$(username)</username>
                <password>$(password)</password>
            </n1:login>
        </env:Body>
    </env:Envelope>"""
    HTTP.request("POST", "https://login.salesforce.com/services/Soap/u/$(version)",
            ["Content-Type" => "text/xml",
            "SOAPAction" => "login"],
            xml, retries=10)
end

function login_base(username::String, password::String, version::String = "35.0")
    session_info=login_post(username, password, version)
    status = session_info.status;
    http_status_exception_hand(status)
    body = String(session_info.body)
    if status == 200
        return child_elem(body)
    else
        return status
    end
end

function login(username::String, password::String, version::String = "35.0", tries::Int = 10)
    current = 1
    while current <= tries
        try
            session = login_base(username, password, version);
            return  session
        catch
            current += 1
            @warn "Not successfull. Starting try $current of $tries"
        end
    end
end

# Bulk api functions
## create work
function jobcreater(session, object, queryall = false)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    if queryall == true
        xml = """<?xml version="1.0" encoding="utf-8" ?>
        <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
            <operation>queryAll</operation>
            <object>$(object)</object>
            <concurrencyMode>Parallel</concurrencyMode>
            <contentType>CSV</contentType>
        </jobInfo>"""
    else
        xml = """<?xml version="1.0" encoding="utf-8" ?>
        <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
            <operation>query</operation>
            <object>$(object)</object>
            <concurrencyMode>Parallel</concurrencyMode>
            <contentType>CSV</contentType>
        </jobInfo>"""
    end
    job = HTTP.request("POST", url1 * "/services/async/" * apiVersion * "/job",
                ["Content-Type" => "text/plain",
                "X-SFDC-Session" => session["sessionId"]],
                xml, retries=10)

    status = job.status;
    http_status_exception_hand(status)
    body = String(job.body)
    job = child_elem(body)
    println("Job: " * job["id"])
    println("Status: " * job["state"])
    return job
end

## create query
function queryposter(session, job, query)
    jobid = job["id"]
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    ret = HTTP.request("POST", url1 * "/services/async/" * apiVersion * "/job/" * jobid * "/batch",
                ["Content-Type" => "text/csv",
                "X-SFDC-Session" => session["sessionId"]],
                query, retries=10)
    status = ret.status;
    http_status_exception_hand(status)
    body = String(ret.body)
    query = child_elem(body)
    println("Job: " * query["id"])
    println("Status: " * query["state"])
    return query
end

## check status
function batchstatus(session, query; printing = true, tries = 10)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    jobid = query["jobId"]
    batchid = query["id"]
    ret = []
    while tries > 0
        try
            ret = HTTP.request("GET", url1 * "/services/async/" * apiVersion * "/job/" * jobid * "/batch/" * batchid,
                ["Content-Type" => "text/plain",
                "X-SFDC-Session" => session["sessionId"]], retries=10)
            tries = 0
        catch
            tries -= 1
            @warn "Batchstatus: Not successfull. Tries left $tries"
        end
    end
    status = ret.status;
    http_status_exception_hand(status)
    body = String(ret.body)
    batch = child_elem(body)
    if batch["state"] == "Failed"
        println("Batch: " * batch["id"])
        error("Status: " * batch["stateMessage"])
    elseif printing == true
        println("Batch: " * batch["id"])
        println("Status: " * batch["state"])
    end
    return batch
end

## fetch results
function resultsid(session, batch)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    jobid = batch["jobId"]
    batchid = batch["id"]

    ret = HTTP.request("GET", url1 * "/services/async/" * apiVersion * "/job/" * jobid * "/batch/" * batchid * "/result",
                ["Content-Type" => "text/plain",
                "X-SFDC-Session" => session["sessionId"]], retries=10)
    status = ret.status;
    http_status_exception_hand(status)
    body = String(ret.body)
    results = Dict{String,String}()
    for (i,x) in enumerate(child_elements(LightXML.root(parse_string(body))))
        name_v, value = split(string(x), r"<|>")[2:3]
        merge!(results, Dict([name_v*string(i) => value]))
    end
    return results
end

function results(session, batch)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    jobid = batch["jobId"]
    batchid = batch["id"]
    resultids = collect(values(resultsid(session,batch)))
    body = DataFrame()
    for resultid in resultids
        ret = HTTP.request("GET", url1 * "/services/async/" * apiVersion * "/job/" * jobid * "/batch/" * batchid * "/result/" * resultid,
                ["Content-Type" => "text/plain",
                "X-SFDC-Session" => session["sessionId"]], retries=10)
        status = ret.status;
        http_status_exception_hand(status)
        if size(body) == (0, 0)
            body = mapcols(x -> replace(x, "" => missing), CSV.read(IOBuffer(String(ret.body)), missingstring = ""))
        else
            body = vcat(body, mapcols(x -> replace(x, "" => missing), CSV.read(IOBuffer(String(ret.body)), missingstring = "")))
        end
    end
    return body
end

## close worker
function jobcloser(session, job)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    jobid = job["id"]

    xml = """<?xml version="1.0" encoding="utf-8" ?>
    <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
        <state>Closed</state>
    </jobInfo>"""
    ret = HTTP.request("POST", url1 * "/services/async/" * apiVersion * "/job/" * jobid,
                ["Content-Type" => "text/plain",
                "X-SFDC-Session" => session["sessionId"]],
                xml, retries=10)

    status = ret.status;
    http_status_exception_hand(status)
    body = String(ret.body)
    job = child_elem(body)
    println("Job: " * job["id"])
    println("Status: " * job["state"])
    return job
end

# Wrapper
# wrapper function for single task
function lowercase_query(query::String)
    org = collect(eachmatch(r"([\"'])(?:(?=(\\?))\2.)*?\1", query))
    query = lowercase(query)
    new = collect(eachmatch(r"([\"'])(?:(?=(\\?))\2.)*?\1",query))
    [query = replace(query, x) for x in Dict(zip([String(y.match) for y in new], [String(y.match) for y in org]))];
    return query
end

function sf_bulkapi_query(session, query::String, queryall::Bool = false)
    query = lowercase_query(query)
    objects = [x.match for x in eachmatch(r"(?<=from\s)(\w+)",query)]
    length(objects) > 1 ? error("Query string include multiple objects. Should only have 1 FROM * statement") : nothing
    objects = objects[1]
    job = jobcreater(session, objects, queryall);
    try
        query = queryposter(session, job, query);
        batch = batchstatus(session, query, printing=true);
        if batch["state"] == "Failed"
            error("Status: " * batch["stateMessage"])
        else
            while batch["state"] != "Completed"
                sleep(3)
                batch = batchstatus(session, query, printing=false);
            end
            if batch["state"] == "Completed"
                res = results(session, batch)
            end
            return res
        end
    catch
        return DataFrame()
    finally
        jobcloser(session, job)
    end
end

# Functions for multiple queries
function startworker(session, joblist::RemoteChannel{Channel{String}}, res::RemoteChannel{Channel{Dict}}, queries, queryall = false)
    function do_worker(session, joblist::RemoteChannel{Channel{String}}, res::RemoteChannel{Channel{Dict}}, queryall = false)
        running = true
        while running
            try
                query = take!(joblist)
                result = sf_bulkapi_query(session, query, queryall)
                put!(res, Dict([(query,result)]))
            catch berror
                if isa(berror, InvalidStateException)
                    running = false
                elseif isa(berror, RemoteException)
                    running = false
                else
                    running = false
                    println(berror)
                end
            end
        end
    end
    for p in workers()
        remote_do(do_worker, p, session, joblist, res, queryall)
    end
end;

function startworker(session, joblist::Channel{String}, res::Channel{Dict}, queries, queryall = false)
    function create_worker(session, res::Channel{Dict}, queryall = false)
        query = take!(joblist)
        println("Job taken")
        put!(res, Dict([(query,sf_bulkapi_query(session, query, queryall))]))
    end
    for i in queries
        @async create_worker(session, res, queryall)
    end
end;

function create_joblist(queries::Array{String}, joblist)
    for i in queries
        put!(joblist, i)
    end
    close(joblist)
end;

function result_collector(queries, res)
    totalres=Dict()
    while isopen(res)
        resa = take!(res)
        merge!(totalres, resa)
        if length(totalres) >= size(queries, 1)
            close(res)
        end
    end
    return totalres
end;

function multiquery(session, queries, queryall = false)
    if length(workers()) > 1
        res = RemoteChannel(()->Channel{Dict}(size(queries,1)));
        joblist = RemoteChannel(()->Channel{String}(size(queries,1)));
    else
        res = Channel{Dict}(size(queries,1));
        joblist = Channel{String}(size(queries,1));
    end
    println("Setup done")
    println("Create Jobs")
    create_joblist(queries, joblist)
    println("Start worker")
    @async startworker(session, joblist, res, queries, queryall)
    ret = result_collector(queries, res)
    println("Results collected")
    return ret
end

# Helper functions
## All Tables
function object_list(session)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    ret = HTTP.request("GET", url1 * "/services/data/v" * apiVersion * "/sobjects",
                ["Content-Type" => "text/plain",
                "Authorization" => "Bearer " * session["sessionId"],
                "Accept" => "application/json"], retries=10)
    body = JSON.parse(String(ret.body));
    objects = [x["name"] for x in body["sobjects"]]
    return objects
end
## Field per Table
function fields_description(session, object::String)
    apiVersion = match(r"/[0-9\.]{2,}/", session["serverUrl"]).match[2:end-1]
    url1 = match(r".{0,}\.com", session["serverUrl"]).match
    ret = HTTP.request("GET", url1 * "/services/data/v" * apiVersion * "/sobjects/" * object * "/describe",
                ["Content-Type" => "text/plain",
                "Authorization" => "Bearer " * session["sessionId"],
                "Accept" => "application/json"], retries=10)

    body = JSON.parse(String(ret.body));
    ret = field_extractor(body["fields"], object)
    return ret
end

## All Field in a Table
function fields_description(session, object::Array)
    p = Progress(size(object,1), dt=1, barglyphs=BarGlyphs("[=> ]"), barlen=10, color=:green)
    ret = fields_description(session, object[1])
    next!(p)
    for x in object[2:end]
        append!(ret, fields_description(session,x))
        next!(p)
    end
    return ret
end

## All fields + all tables
function all_object_fields(session)
    objects = object_list(session)
    ret = fields_description(session, objects)
    return ret
end

## XML functions
function child_elem(x)
    x = LightXML.root(parse_string(x))
    res = Dict{String,String}()
    child_elem(x, res)
end

function child_elem(x, res)
    if size(collect(child_elements(x)),1) > 0
        for x in child_elements(x)
            name_v, value = split(string(x), r"<|>")[2:3]
            merge!(res, Dict([name_v => value]))
            child_elem(x, res)
        end
    end
    return(res)
end

## Return stat behaviour
function http_status_exception_hand(x)
    if x >= 300 | x < 200
        @error "HTTP code $x"
    end
end

#Extracts all fields from a dict into columns of a DataFrame and appends the object name for reference
function field_extractor(x, object::String)
    ret = []
    for (i, x) in enumerate(x)
        if i == 1
            ret = DataFrame(reshape([x for x in values(x)],1,:), Symbol.(keys(x)))
        else
            append!(ret,DataFrame(reshape([x for x in values(x)],1,:), Symbol.(keys(x))))
        end
    end
    ret[!,:object] .= object
    return ret
end

end


text:
export discretize,timedirichlet

include("KroneckerOperator.jl")

## PDE

lap(d::Space) = Laplacian(d)
lap(d::Domain) = Laplacian(d)
lap(f::Fun) = Laplacian()*f

function Laplacian(d::BivariateSpace,k::Integer)
    Dx2=Derivative(d,[2,0])
    Dy2=Derivative(d,[0,2])
    if k==1
        LaplacianWrapper(Dx2+Dy2,k)
    else
        @assert k > 0
        Δ=Laplacian(d,1)
        LaplacianWrapper(TimesOperator(Laplacian(rangespace(Δ),k-1),Δ),k)
    end
end

Laplacian(d::Domain2d, k::Integer) = Laplacian(Space(d),k)
grad(d::ProductDomain) = [Derivative(d,[1,0]),Derivative(d,[0,1])]

function tensor_Dirichlet(d::Union{ProductDomain,TensorSpace},k)
    @assert nfactors(d)==2

    DirichletWrapper(
        if isempty(∂(factor(d,1)))
            I ⊗ Dirichlet(factor(d,2),k)
        elseif isempty(∂(factor(d,2)))
            Dirichlet(factor(d,1),k) ⊗ I
        else
            [Dirichlet(factor(d,1),k) ⊗ I;I ⊗ Dirichlet(factor(d,2),k)]
        end, k)
end

Dirichlet(d::Union{ProductDomain,TensorSpace},k) = tensor_Dirichlet(d,k)

function timedirichlet(d::Union{ProductDomain,TensorSpace})
    @assert nfactors(d)==2
    Bx=Dirichlet(factor(d,1))
    Bt=ldirichlet(factor(d,2))
    [I⊗Bt;Bx⊗I]
end

function *(B::Operator,f::ProductFun)
    if isafunctional(B)
        Fun(factor(space(f),2),map(c->Number(B*c),f.coefficients))
    else
        ProductFun(space(f),map(c->B*c,f.coefficients))
    end
end

*(f::ProductFun,B::Operator) = transpose(B*(transpose(f)))

ext: jl


text:
using Rogue
using Test

@testset "Rogue.jl" begin
    # Write your own tests here.
end


text:
module VTK

using ...TopOptProblems: StiffnessTopOptProblem, JuAFEM
using WriteVTK

export  save_mesh

function save_mesh(filename, problem::StiffnessTopOptProblem)
    topology = ones(getncells(TopOptProblems.getdh(problem).grid))
    vtkfile = WriteVTK.vtk_grid(filename, problem, topology)
    outfiles = WriteVTK.vtk_save(vtkfile)
end
function WriteVTK.vtk_grid(filename::AbstractString, problem::StiffnessTopOptProblem{dim, T}, topology::AbstractVector) where {dim, T}
    grid = problem.ch.dh.grid
    celltype = JuAFEM.cell_to_vtkcell(JuAFEM.getcelltype(grid))
    cls = JuAFEM.MeshCell[]
    for (i, cell) in enumerate(JuAFEM.CellIterator(grid))
        if topology[i] >= 0.5
            push!(cls, JuAFEM.MeshCell(celltype, copy(JuAFEM.getnodes(cell))))
        end
    end
    coords = reshape(reinterpret(T, JuAFEM.getnodes(grid)), (dim, JuAFEM.getnnodes(grid)))
    return vtk_grid(filename, coords, cls)
end

function save_mesh(filename, problem, solver)
    save_mesh(filename, problem, solver.vars)
end
function save_mesh(filename, alg)
    problem = alg.obj.problem
    vars = alg.optimizer.obj.solver.vars
    save_mesh(filename, problem, vars)
end
function save_mesh(filename, problem, vars::AbstractVector)
    vtkfile = WriteVTK.vtk_grid(filename, problem, vars)
    outfiles = WriteVTK.vtk_save(vtkfile)
end
function WriteVTK.vtk_grid(filename::AbstractString, problem::StiffnessTopOptProblem{dim, T}, vars::AbstractVector{T}) where {dim, T}
    varind = problem.varind
    black = problem.black
    white = problem.white
    grid = problem.ch.dh.grid

    celltype = JuAFEM.cell_to_vtkcell(JuAFEM.getcelltype(grid))
    cls = JuAFEM.MeshCell[]
    for (i, cell) in enumerate(JuAFEM.CellIterator(grid))
        if black[i]
            push!(cls, JuAFEM.MeshCell(celltype, copy(JuAFEM.getnodes(cell))))
        elseif !white[i]
            if vars[varind[i]] >= 0.5
                push!(cls, JuAFEM.MeshCell(celltype, copy(JuAFEM.getnodes(cell))))
            end
        end
    end
    coords = reshape(reinterpret(T, JuAFEM.getnodes(grid)), (dim, JuAFEM.getnnodes(grid)))
    return vtk_grid(filename, coords, cls)
end

end


text:
# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module KamchatkaNumbers

using Base
export Kamchatka

"""

General description of the module goes here.

* All exported functions are listed here.
"""
const ModuleKamchatka = ""

"""

Return the ``n``-th Kamchatka number.
"""
Kamchatka(n) = 0

#START-TEST-########################################################

using Test

function test()
    @testset "Kamchatka" begin
        @test Kamchatka(0) == 0
    end
end

function demo()
end

function perf()
end

function main()
    test()
    demo()
    perf()
end

main()

end # module

max_stars_repo_name: OpenLibMathSeq/Sequences

max_issues_repo_name: OpenLibMathSeq/Sequences


text:
using UUIDs
is_ch_type(::Val{:UUID})  = true
result_type(::Val{:UUID})  = Vector{UUID}

function read_col_data(sock::ClickHouseSock, num_rows::VarUInt, ::Val{:UUID})
    tmp = Vector{UUID}(undef, UInt64(num_rows))
    Base.read!(sock.io, tmp)
    return tmp
end

function write_col_data(sock::ClickHouseSock,
                        data::AbstractVector{UUID}, ::Val{:UUID})

    Base.write(sock.io,
        data
    )
end


text:
### These benchmarks can be run from the top-level directory of this repo with
###
###     julia -e 'using PkgBenchmark; results=benchmarkpkg("SphericalFunctions"); export_markdown("benchmark/results.md", results)'
###
### This runs the benchmarks (possibly tuning them automatically first), and writes the results to a nice markdown file.

using BenchmarkTools
using SphericalFunctions

const SUITE = BenchmarkGroup()

SUITE["complex_powers"] = BenchmarkGroup(["recursions", "complex"])
for T in [big, Float64, Float32, Float16]
    z = exp(T(6)im/5)
    m = 10_000
    zpowers = zeros(typeof(z), m+1)
    SUITE["complex_powers"][T] = @benchmarkable complex_powers!($zpowers, $z)
end

# SUITE["trigonometry"]["hyperbolic"] = BenchmarkGroup()
# for f in (sin, cos, tan)
#     for x in (0.0, pi)
#         SUITE["trigonometry"]["hyperbolic"][string(f), x] = @benchmarkable ($f)($x)
#     end
# end


text:
function format_check(io::IOBuffer, fst::FST, s::State)
    if length(s.doc.format_skips) == 0
        print_notcode(io, fst, s)
        return
    end

    skip = s.doc.format_skips[1]
    line_range = fst.startline:fst.endline

    if s.on && skip[1] in line_range && skip[2] in line_range
        # weird corner case where off and on toggle
        # are in the same comment block
        fst.endline = skip[1]
        print_notcode(io, fst, s, fmttag = true)
        write(io, skip[3])
        fst.startline = skip[2]
        fst.endline = skip[2]
        print_notcode(io, fst, s, fmttag = true)
    elseif s.on && skip[1] in line_range
        fst.endline = skip[1]
        print_notcode(io, fst, s, fmttag = true)
        write(io, skip[3])
        s.on = false
    elseif !s.on && skip[2] in line_range
        deleteat!(s.doc.format_skips, 1)
        s.on = true
        # change the startline, otherwise lines
        # prior to in the NOTCODE node prior to
        # "format: on" will be reprinted
        fst.startline = skip[2]
        print_notcode(io, fst, s, fmttag = true)
        # previous NEWLINE node won't be printed
    else
        print_notcode(io, fst, s)
    end
end

function print_leaf(io::IOBuffer, fst::FST, s::State)
    if fst.typ === NOTCODE
        format_check(io, fst, s)
    elseif fst.typ === INLINECOMMENT
        print_inlinecomment(io, fst, s)
    else
        s.on && write(io, fst.val)
    end
    s.line_offset += length(fst)
end

function print_tree(io::IOBuffer, fst::FST, s::State)
    notcode_indent = -1
    if fst.typ === CSTParser.BinaryOpCall ||
       fst.typ === CSTParser.ConditionalOpCall ||
       fst.typ === CSTParser.ModuleH
        notcode_indent = fst.indent
    end
    print_tree(io, fst.nodes, s, fst.indent, notcode_indent = notcode_indent)
end

function print_tree(
    io::IOBuffer,
    nodes::Vector{FST},
    s::State,
    indent::Int;
    notcode_indent = -1,
)
    ws = repeat(" ", max(indent, 0))
    for (i, n) in enumerate(nodes)
        if n.typ === NOTCODE
            if notcode_indent > -1
                n.indent = notcode_indent
            elseif i + 1 < length(nodes) && is_end(nodes[i+2])
                n.indent += s.opts.indent
            elseif i + 1 < length(nodes) && (
                nodes[i+2].typ === CSTParser.Block || nodes[i+2].typ === CSTParser.Begin
            )
                n.indent = nodes[i+2].indent
            elseif i > 2 && (
                nodes[i-2].typ === CSTParser.Block || nodes[i-2].typ === CSTParser.Begin
            )
                n.indent = nodes[i-2].indent
            end
        end

        if is_leaf(n)
            print_leaf(io, n, s)
        elseif n.typ === CSTParser.StringH
            print_stringh(io, n, s)
        else
            print_tree(io, n, s)
        end

        if n.typ === NEWLINE && s.on && i < length(nodes)
            if is_closer(nodes[i+1]) ||
               nodes[i+1].typ === CSTParser.Block ||
               nodes[i+1].typ === CSTParser.Begin
                write(io, repeat(" ", max(nodes[i+1].indent, 0)))
                s.line_offset = nodes[i+1].indent
            elseif !skip_indent(nodes[i+1])
                write(io, ws)
                s.line_offset = indent
            end
        end
    end
end

function print_stringh(io::IOBuffer, fst::FST, s::State)
    # The indent of StringH is set to the the offset
    # of when the quote is first encountered in the source file.

    # This difference notes the indent change due to formatting.
    diff = s.line_offset - fst.indent

    # The new indent for the string is the index of when a character in
    # the multiline string is FIRST encountered in the source file plus
    # the above difference.
    fst.indent = max(fst[1].indent + diff, 0)
    print_tree(io, fst, s)
end

function print_notcode(io::IOBuffer, fst::FST, s::State; fmttag = false)
    s.on || return
    for l = fst.startline:fst.endline
        ws, v = get(s.doc.comments, l, (0, "\n"))
        !fmttag && (ws = fst.indent)

        # If the current newline is followed by another newline
        # don't print the current newline.
        if s.opts.remove_extra_newlines
            _, vn = get(s.doc.comments, l + 1, (0, "\n"))
            vn == "\n" && v == "\n" && (v = "")
        end

        v == "" && continue
        v == "\n" && (ws = 0)

        if l == fst.endline && v[end] == '\n'
            v = v[1:prevind(v, end)]
        end

        ws > 0 && write(io, repeat(" ", ws))
        write(io, v)

        if l != fst.endline && v[end] != '\n'
            write(io, "\n")
        end
    end
end

function print_inlinecomment(io::IOBuffer, fst::FST, s::State)
    s.on || return
    ws, v = get(s.doc.comments, fst.startline, (0, ""))
    isempty(v) && return
    v = v[end] == '\n' ? v[nextind(v, 1):prevind(v, end)] : v
    ws > 0 && write(io, repeat(" ", ws))
    write(io, v)
end