From d349c0f759adc60ad4ecf6487689eadc2ad920f1 Mon Sep 17 00:00:00 2001
From: hustf <hustf@users.noreply.github.com>
Date: Sun, 8 Nov 2020 17:23:07 +0100
Subject: [PATCH] modified:   README.md     Add 'parse' example, quantities
 from text files modified:   src/MechanicalUnits.jl Add 'parse' new file:  
 src/parse.jl  New file modified:   test/conversion_promotion.jl Add 'parse'
 tests

---
 README.md                    | 18 ++++++++++++
 src/MechanicalUnits.jl       |  9 +++++-
 src/parse.jl                 | 56 ++++++++++++++++++++++++++++++++++++
 test/conversion_promotion.jl | 11 +++++++
 4 files changed, 93 insertions(+), 1 deletion(-)
 create mode 100644 src/parse.jl

diff --git a/README.md b/README.md
index 88349bf..f967b4b 100644
--- a/README.md
+++ b/README.md
@@ -186,6 +186,24 @@ Unitfu.FreeUnits{(dyn,), ᴸ∙ ᴹ∙ ᵀ⁻²,nothing}
 
 julia> 1dyn |> μm
 10kg∙μm∙s⁻²
+
+julia> # When parsing text file, spaces as multipliers and brackets are allowed. Just specify the numeric type:
+julia> lin = "2 [s]\t11364.56982421875 [N]\t-44553.50244140625 [N]\t-26.586366176605225 [N]\t0.0[N mm]\t0.0[N mm]\t0.0[N mm]\t1561.00350618362 [mm]\t-6072.3729133606 [mm]\t2825.15907287598 [mm]"
+"2 [s]\t11364.56982421875 [N]\t-44553.50244140625 [N]\t-26.586366176605225 [N]\t0.0[N mm]\t0.0[N mm]\t0.0[N mm]\t1561.00350618362 [mm]\t-6072.3729133606 [mm]\t2825.15907287598 [mm]"
+
+julia> time, Fx, Fy, Fz, Mx, My, Mz, px, py, pz = parse.(Quantity{Float64}, split(lin, '\t'))
+10-element Array{Quantity{Float64,D,U} where U where D,1}:
+                 2.0s
+   11364.56982421875N
+  -44553.50244140625N
+ -26.586366176605225N
+    0.0mm∙N
+    0.0mm∙N
+    0.0mm∙N
+   1561.00350618362mm
+   -6072.3729133606mm
+   2825.15907287598mm
+
 ```
 
 ## Goals
diff --git a/src/MechanicalUnits.jl b/src/MechanicalUnits.jl
index edf592e..9612f28 100644
--- a/src/MechanicalUnits.jl
+++ b/src/MechanicalUnits.jl
@@ -5,6 +5,7 @@ export ∙
 # Import / exports for short and parseable type signatures
 import Unitfu: Time, Length, Mass, Temperature, Current, Luminosity, Amount
 import Unitfu: ᵀ , ᴸ , ᴹ , ᶿ, ᴶ , ᴺ
+import Unitfu: lookup_units
 export Time, Length, Mass, Temperature, Current, Luminosity, Amount, Level
 export ᵀ , ᴸ , ᴹ , ᶿ , ᴶ , ᴺ
 export Quantity, DimensionlessQuantity, NoUnits, NoDims
@@ -13,7 +14,7 @@ import Unitfu:
 export  FreeUnits, AffineUnits, Affine, AffineQuantity, Unitlike, Unit, Dimensions, Dimension, Units
 export  Level, Gain
 
-# For importinng from Unitfu, or defining more units
+# For importing from Unitfu, or defining more units
 export @import_expand, @unit, @u_str
 
 # Reexported functions from Unitfu
@@ -33,6 +34,10 @@ import Unitfu: Area, Acceleration, Force, Pressure, Density
 import Unitfu: Velocity
 import Unitfu:ForceFreeUnits, PressureFreeUnits, EnergyFreeUnits, AreaFreeUnits, DensityFreeUnits, VolumeFreeUnits
 export Area, Acceleration, Force, Pressure, Density, Velocity
+
+# Extend base. This could perhaps reside in Unitfu
+import Base: tryparse_internal, parse
+
 # Units are exported in 'import_export_units.jl'.
 
 include("internal_functions.jl")
@@ -49,6 +54,7 @@ eval(exponents_superscripts(:ᴺ))
 # Used for registering units with Unitfu macros during initialisation.
 const localunits = Unitfu.basefactors
 
+include("parse.jl")
 function __init__()
     # This is for evaluating Unitfu macros in the context of this package.
     merge!(Unitfu.basefactors, localunits)
@@ -60,4 +66,5 @@ function __init__()
     Sys.isapple() && push!(ENV, "UNITFUL_FANCY_EXPONENTS" => "true")
 end
 
+
 end # module
diff --git a/src/parse.jl b/src/parse.jl
new file mode 100644
index 0000000..970d153
--- /dev/null
+++ b/src/parse.jl
@@ -0,0 +1,56 @@
+parse(::Type{Quantity{T}}, s::AbstractString; kwargs...) where {T <: Real} =
+    convert(Quantity{T}, tryparse_internal(Quantity{T}, s, firstindex(s), lastindex(s), 10, true; kwargs...))
+
+function tryparse_internal(::Type{Quantity{T}}, sbuff::Union{String,SubString{String}},
+    startpos::Int, endpos::Int, base::Integer, raise::Bool) where {T<:Real}
+    if isempty(sbuff)
+        raise && throw(ArgumentError("input string is empty"))
+        return nothing
+    end
+
+    orig_start = startpos
+    orig_end   = endpos
+
+    # Ignore leading and trailing whitespace
+    while isspace(sbuff[startpos]) && startpos <= endpos
+        startpos = nextind(sbuff, startpos)
+    end
+    while isspace(sbuff[endpos]) && endpos >= startpos
+        endpos = prevind(sbuff, endpos)
+    end
+
+    # Find first character of unit specification
+    unitpos = startpos
+    while sbuff[unitpos] ∈ "+-0123456789.," && unitpos <= endpos 
+        unitpos = nextind(sbuff, unitpos)
+    end
+
+    numlen = unitpos - startpos + 1
+    unitlen = endpos - unitpos + 1
+
+    if numlen > 0 && unitlen > 0
+        num = parse(T, sbuff[startpos:unitpos - 1])
+        suni = strip(sbuff[unitpos:endpos], ['[', ']', ' '])
+        if '/' ∉ suni
+            suni = replace(suni, r"[∙· *]" => '∙')
+            sunits = split(suni, '∙')
+            uni =  lookup_units(MechanicalUnits, Symbol(sunits[1]))
+            for string_unit in sunits[2:lastindex(sunits)]
+                uni *= lookup_units(MechanicalUnits, Symbol(string_unit))
+            end
+            return num * uni
+        end
+    end
+
+    if raise
+        substr = SubString(sbuff, orig_start, orig_end) # show input string in the error to avoid confusion
+        if all(isspace, substr)
+            throw(ArgumentError("input string only contains whitespace"))
+        elseif '/' ∉ sbuff[unitpos:endpos]
+            throw(ArgumentError("input string contains '/' unit. Replace input with multiplied units: 'm/s' =>  'm∙s⁻¹'"))
+        else
+            throw(ArgumentError("invalid quantity representation: $(repr(substr))"))
+        end
+    end
+    return nothing
+end
\ No newline at end of file
diff --git a/test/conversion_promotion.jl b/test/conversion_promotion.jl
index 769b4f2..f273354 100644
--- a/test/conversion_promotion.jl
+++ b/test/conversion_promotion.jl
@@ -48,3 +48,14 @@ end
     @test (1m, 1m^2/mm) == (1000, 1000000)mm
     @test (1m, 1m^2/mm) == (1, 1000)m
 end
+
+@testset "Quantity parse" begin
+@test parse(Quantity{Float64}, "2.0kN") == 2.0kN
+@test  parse(Quantity{Int64}, "2 kN") == 2kN
+@test  parse(Quantity{Int64}, "2 [m]") == 2m
+@test  parse(Quantity{Float64}, "2 [m]") == 2.0m
+@test  parse(Quantity{Float64}, "2 [N m]") == 2.0Nm
+lin = "2 [s]\t11364.56982421875 [N]\t-44553.50244140625 [N]\t-26.586366176605225 [N]\t0.0[N mm]\t0.0[N mm]\t0.0[N mm]\t1561.00350618362 [mm]\t-6072.3729133606 [mm]\t2825.15907287598 [mm]"
+data = parse.(Quantity{Float64}, split(lin, '\t'))
+@test  data ==  [ 2.0s, 11364.56982421875N, -44553.50244140625N, -26.586366176605225N,  0.0mm∙N, 0.0mm∙N, 0.0mm∙N, 1561.00350618362mm, -6072.3729133606mm, 2825.15907287598mm]
+end