From f561180ce5a064497860034ef275a15d3f053cee Mon Sep 17 00:00:00 2001 From: Iain Dunning Date: Sun, 1 Jul 2018 13:44:55 -0400 Subject: [PATCH 1/6] Minimal fix to get tests passing. --- src/Humanize.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Humanize.jl b/src/Humanize.jl index 0248950..9717052 100644 --- a/src/Humanize.jl +++ b/src/Humanize.jl @@ -5,7 +5,7 @@ # All original code is (c) Iain Dunning and MIT licensed. #---------------------------------------------------------------------- -isdefined(Base, :__precompile__) && __precompile__() +__precompile__() module Humanize @@ -86,8 +86,8 @@ end # Assume nothing about magnitudes of inputs, so cast to seconds first timedelta{T<:Integer}(years::T,months::T,days::T,hours::T,mins::T,secs::T) = timedelta(((((years*12+months)*30+days)*24+hours)*60+mins)*60+secs) -timedelta(dt_diff::Dates.Millisecond) = timedelta(div(Int(dt_diff),1000)) -timedelta(d_diff::Dates.Day) = timedelta(Int(d_diff)*24*3600) +timedelta(dt_diff::Dates.Millisecond) = timedelta(div(dt_diff.value,1000)) +timedelta(d_diff::Dates.Day) = timedelta(d_diff.value*24*3600) #--------------------------------------------------------------------- From 36d56b863a5b5cf88d3ebd46e537ee3c4ab1dd6f Mon Sep 17 00:00:00 2001 From: Iain Dunning Date: Sun, 1 Jul 2018 13:45:37 -0400 Subject: [PATCH 2/6] Clean up of non-code stuff. --- .travis.yml | 9 +-------- LICENSE.md | 2 +- README.md | 5 ----- REQUIRE | 2 +- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 483397b..b9dbfe6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,10 @@ language: julia os: - linux - - osx julia: - - 0.4 - - 0.5 - - 0.6 + - 0.7 - nightly notifications: email: false -sudo: false -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.test("Humanize"; coverage=true)' after_success: - julia -e 'cd(Pkg.dir("Humanize")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' diff --git a/LICENSE.md b/LICENSE.md index b3f03a7..f973541 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,7 +2,7 @@ This package is a derived work of the MIT-licensed humanize Python library: https://github.com/jmoiron/humanize/ All original work in this package is also licensed under the MIT license: -Copyright (c) 2014 Iain Dunning, Julian Gehring +Copyright (c) 2018 Iain Dunning, Julian Gehring. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 02b15d2..6e8f42e 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ Humanize.jl [![Build Status](https://travis-ci.org/IainNZ/Humanize.jl.svg?branch=master)](https://travis-ci.org/IainNZ/Humanize.jl) [![codecov](https://codecov.io/gh/IainNZ/Humanize.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/IainNZ/Humanize.jl) -[![Humanize](http://pkg.julialang.org/badges/Humanize_0.4.svg)](http://pkg.julialang.org/?pkg=Humanize) -[![Humanize](http://pkg.julialang.org/badges/Humanize_0.5.svg)](http://pkg.julialang.org/?pkg=Humanize) - Humanize numbers, including * data sizes (`3e6 -> 3.0 MB or 2.9 MiB`). * Date/datetime differences (`Date(2014,2,3) - Date(2013,3,7) -> 1 year, 1 month`) @@ -14,8 +11,6 @@ Humanize numbers, including This package is MIT licensed, and is based on [jmoiron's humanize Python library](https://github.com/jmoiron/humanize/). -**Installation:** `Pkg.add("Humanize")` - ## Documentation All functions are also documented using Julia's in-built help system, e.g. `?datasize`. diff --git a/REQUIRE b/REQUIRE index d5d6467..c5a584c 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1 +1 @@ -julia 0.4 +julia 0.7-beta From d110cad691ef43d109d80c7a925f702bf0129f10 Mon Sep 17 00:00:00 2001 From: Iain Dunning Date: Sun, 1 Jul 2018 13:51:30 -0400 Subject: [PATCH 3/6] Fix deprecations. --- src/Humanize.jl | 14 ++++++++++---- test/runtests.jl | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Humanize.jl b/src/Humanize.jl index 9717052..b9bbdd8 100644 --- a/src/Humanize.jl +++ b/src/Humanize.jl @@ -9,6 +9,9 @@ __precompile__() module Humanize +import Dates +import Printf: @sprintf + export datasize, timedelta, digitsep #--------------------------------------------------------------------- @@ -30,13 +33,14 @@ function datasize(value::Number; style=:dec, format="%.1f") bytes = float(value) format = "$format%s" unit = base - s = suffix[1] + biggest_suffix = suffix[1] for (i,s) in enumerate(suffix) unit = base ^ (i) + biggest_suffix = suffix[i] bytes < unit && break end v = base * bytes / unit - return @eval @sprintf($format, $v, $s) + return @eval @sprintf($format, $v, $biggest_suffix) end #--------------------------------------------------------------------- @@ -84,8 +88,10 @@ function timedelta(secs::Integer) end end # Assume nothing about magnitudes of inputs, so cast to seconds first -timedelta{T<:Integer}(years::T,months::T,days::T,hours::T,mins::T,secs::T) = - timedelta(((((years*12+months)*30+days)*24+hours)*60+mins)*60+secs) +function timedelta(years::T, months::T, days::T, hours::T, mins::T, + secs::T) where T <: Integer + return timedelta(((((years*12+months)*30+days)*24+hours)*60+mins)*60+secs) +end timedelta(dt_diff::Dates.Millisecond) = timedelta(div(dt_diff.value,1000)) timedelta(d_diff::Dates.Day) = timedelta(d_diff.value*24*3600) diff --git a/test/runtests.jl b/test/runtests.jl index 2a413d1..cc5b42f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,6 @@ +import Dates +using Test using Humanize -using Base.Test function test_datasize() println("test_datasize") From 32cbc907c1dcb061eb76c77f2bf0d511418ba684 Mon Sep 17 00:00:00 2001 From: Iain Dunning Date: Sun, 1 Jul 2018 14:34:03 -0400 Subject: [PATCH 4/6] Modernize tests --- test/runtests.jl | 144 ++++++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 82 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index cc5b42f..a660f1a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,102 +1,82 @@ import Dates using Test -using Humanize +import Humanize -function test_datasize() - println("test_datasize") +@testset "Humanize" begin - tests = [300, 3000, 3000000, 3000000000, 3000000000000, 1e26, 3141592] - results = Dict( :dec => ["450.000 B", "4.500 kB", "4.500 MB", "4.500 GB", "4.500 TB", "150.000 YB", "4.712 MB"], - :bin => ["630.000 B", "6.152 KiB", "6.008 MiB", "5.867 GiB", "5.730 TiB", "173.708 YiB", "6.292 MiB"], - :gnu => ["480.000", "4.688K", "4.578M", "4.470G", "4.366T", "132.349Y", "4.794M"]) - for (style,mult) in [(:dec,1.5), (:bin,2.1), (:gnu, 1.6)] - println(" $style") - for i in 1:length(tests) - size = tests[i] * mult - @test datasize(size,style=style,format="%.3f") == results[style][i] +@testset "datasize" begin + VALUES = [300, 3000, 3000000, 3000000000, 3000000000000, 1e26, 3141592] + MULTIPLIERS = Dict(:dec => 1.5, :bin => 2.1, :gnu => 1.6) + RESULTS = Dict( + :dec => ["450.000 B", "4.500 kB", "4.500 MB", "4.500 GB", "4.500 TB", "150.000 YB", "4.712 MB"], + :bin => ["630.000 B", "6.152 KiB", "6.008 MiB", "5.867 GiB", "5.730 TiB", "173.708 YiB", "6.292 MiB"], + :gnu => ["480.000", "4.688K", "4.578M", "4.470G", "4.366T", "132.349Y", "4.794M"] + ) + @testset "$style" for style in (:dec, :bin, :gnu) + for (value, result) in zip(VALUES, RESULTS[style]) + size = value * MULTIPLIERS[style] + @test Humanize.datasize(size, style=style, format="%.3f") == result end end -end +end # testset datasize. - -function test_timedelta() - println("test_timedelta") - - test = [(0,0,0,0,0,0), "a moment", - (0,0,0,0,0,1), "a second", - (0,0,0,0,0,30), "30 seconds", - (0,0,0,0,1,30), "a minute", - (0,0,0,0,2,0), "2 minutes", - (0,0,0,1,30,30), "an hour", - (0,0,0,23,50,50), "23 hours", - (0,0,1,0,0,0), "a day", - (0,0,500,0,0,0), "1 year, 4 months", - (0,0,365*2+35,0,0,0), "2 years", - (0,0,10000,0,0,0), "27 years", - (0,0,365+30,0,0,0), "1 year, 1 month", - (0,0,365+4,0,0,0), "a year", - (0,0,35,0,0,0), "a month", - (0,0,65,0,0,0), "2 months", - (0,0,9,0,0,0), "9 days", - (0,0,365,0,0,0), "a year"] - n_test = div(length(test),2) - - #timedelta(years::Int,months::Int,days::Int,hours::Int,mins::Int,secs::Int) - println(" direct") - for i in 1:n_test - @test timedelta(test[2i-1]...) == test[2i] +@testset "timedelta" begin + # Years, months, days, hours, minutes, seconds, result. + DATA = [((0, 0, 0, 0, 0, 0), "a moment"), + ((0, 0, 0, 0, 0, 1), "a second"), + ((0, 0, 0, 0, 0, 30), "30 seconds"), + ((0, 0, 0, 0, 1, 30), "a minute"), + ((0, 0, 0, 0, 2, 0), "2 minutes"), + ((0, 0, 0, 1, 30, 30), "an hour"), + ((0, 0, 0, 23, 50, 50), "23 hours"), + ((0, 0, 1, 0, 0, 0), "a day"), + ((0, 0, 500, 0, 0, 0), "1 year, 4 months"), + ((0, 0, 365*2 + 35, 0, 0, 0), "2 years"), + ((0, 0, 10000, 0, 0, 0), "27 years"), + ((0, 0, 365 + 30, 0, 0, 0), "1 year, 1 month"), + ((0, 0, 365 + 4, 0, 0, 0), "a year"), + ((0, 0, 35, 0, 0, 0), "a month"), + ((0, 0, 65, 0, 0, 0), "2 months"), + ((0, 0, 9, 0, 0, 0), "9 days"), + ((0, 0, 365, 0, 0, 0), "a year")] + + @testset "full signature $output" for (inputs, output) in DATA + @test Humanize.timedelta(inputs...) == output end - #timedelta(dt_diff::Dates.Millisecond) - println(" DateTime diff") - for i in 1:n_test - offset = test[2i-1] + + @testset "datetime diff $output" for (inputs, output) in DATA base_datetime = Dates.DateTime(2014,1,1,0,0,0) - new_datetime = Dates.Year(offset[1]) + base_datetime - new_datetime += Dates.Month(offset[2]) - new_datetime += Dates.Day(offset[3]) - new_datetime += Dates.Hour(offset[4]) - new_datetime += Dates.Minute(offset[5]) - new_datetime += Dates.Second(offset[6]) - @test timedelta(new_datetime-base_datetime) == test[2i] - end - #timedelta(d_diff::Dates.Day) - println(" Date diff") - for i in 1:n_test - offset = test[2i-1] - sum(offset[1:3]) == 0 && continue - base_date = Dates.Date(2014,1,1) - new_date = Dates.Year(offset[1]) + base_date - new_date += Dates.Month(offset[2]) - new_date += Dates.Day(offset[3]) - @test timedelta(new_date-base_date) == test[2i] + new_datetime = Dates.Year(inputs[1]) + base_datetime + new_datetime += Dates.Month(inputs[2]) + new_datetime += Dates.Day(inputs[3]) + new_datetime += Dates.Hour(inputs[4]) + new_datetime += Dates.Minute(inputs[5]) + new_datetime += Dates.Second(inputs[6]) + @test Humanize.timedelta(new_datetime - base_datetime) == output end -end + @testset "date diff $output" for (inputs, output) in DATA + sum(inputs[1:3]) == 0 && continue # Hour-scale or less. + base_date = Dates.Date(2014, 1, 1) + new_date = Dates.Year(inputs[1]) + base_date + new_date += Dates.Month(inputs[2]) + new_date += Dates.Day(inputs[3]) + @test Humanize.timedelta(new_date - base_date) == output + end +end # testset timedelta. -function test_digitsep() - println("test_digitsep") - - test = ( - (1, "1"), +@testset "digitsep" begin + DATA = ((1, "1"), (12, "12"), (123, "123"), (1234, "1,234"), (12345, "12,345"), (123456, "123,456"), (1234567, "1,234,567"), - (12345678, "12,345,678") - ) - - n_test = length(test) - - #digitsep(value::Integer) - println(" direct") - for t in test - @test digitsep(t[1]) == t[2] + (12345678, "12,345,678")) + @testset "digitsep $output" for (input, output) in DATA + @test Humanize.digitsep(input) == output end - -end +end # testset digitsep. -test_datasize() -test_timedelta() -test_digitsep() +end # testset Humanize. \ No newline at end of file From fb961774795b14f8a84a9e4aa0b0c956bea44e0e Mon Sep 17 00:00:00 2001 From: Iain Dunning Date: Sun, 1 Jul 2018 15:15:17 -0400 Subject: [PATCH 5/6] Modernize source, remove questionable method for timedelta. --- REQUIRE | 1 + src/Humanize.jl | 120 +++++++++++++++++++++-------------------------- test/runtests.jl | 4 -- 3 files changed, 55 insertions(+), 70 deletions(-) diff --git a/REQUIRE b/REQUIRE index c5a584c..1b734fd 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1 +1,2 @@ julia 0.7-beta +Dates \ No newline at end of file diff --git a/src/Humanize.jl b/src/Humanize.jl index b9bbdd8..4cf3fc3 100644 --- a/src/Humanize.jl +++ b/src/Humanize.jl @@ -1,9 +1,8 @@ -#---------------------------------------------------------------------- -# Humanize.jl https://github.com/IainNZ/Humanize.jl +# Humanize.jl +# https://github.com/IainNZ/Humanize.jl # Based on jmoiron's humanize Python library (MIT licensed): # https://github.com/jmoiron/humanize/ # All original code is (c) Iain Dunning and MIT licensed. -#---------------------------------------------------------------------- __precompile__() @@ -12,58 +11,55 @@ module Humanize import Dates import Printf: @sprintf -export datasize, timedelta, digitsep +const SUFFIXES = Dict( + :dec => [" B", " kB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"], + :bin => [" B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB", " YiB"], + :gnu => ["", "K", "M", "G", "T", "P", "E", "Z", "Y"] +) +const BASES = Dict(:dec => 1000, :bin => 1024, :gnu => 1024) -#--------------------------------------------------------------------- -const dec_suf = [" B", " kB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"] -const bin_suf = [" B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB", " YiB"] -const gnu_suf = ["", "K", "M", "G", "T", "P", "E", "Z", "Y"] """ -datasize(value::Number; style=:dec, format="%.1f") + datasize(value::Number; style=:dec, format="%.1f") Format a number of bytes in a human-friendly format (eg. 10 kB). -style=:dec - default, decimal suffixes (kB, MB), base 10^3 -style=:bin - binary suffixes (KiB, MiB), base 2^10 -style=:gnu - GNU-style (ls -sh style, K, M), base 2^10 + style=:dec - default, decimal suffixes (kB, MB), base 10^3 + style=:bin - binary suffixes (KiB, MiB), base 2^10 + style=:gnu - GNU-style (ls -sh style, K, M), base 2^10 """ -function datasize(value::Number; style=:dec, format="%.1f") - suffix = style == :gnu ? gnu_suf : (style == :bin ? bin_suf : dec_suf) - base = style == :dec ? 1000.0 : 1024.0 - bytes = float(value) - format = "$format%s" - unit = base +function datasize(value::Number; style=:dec, format="%.1f")::String + suffix = SUFFIXES[style] + base = float(BASES[style]) + bytes = float(value) + unit = base biggest_suffix = suffix[1] - for (i,s) in enumerate(suffix) - unit = base ^ (i) - biggest_suffix = suffix[i] + for power in 1:length(suffix) + unit = base ^ power + biggest_suffix = suffix[power] bytes < unit && break end - v = base * bytes / unit - return @eval @sprintf($format, $v, $biggest_suffix) + value = base * bytes / unit + format = "$format%s" + return @eval @sprintf($format, $value, $biggest_suffix) end -#--------------------------------------------------------------------- + """ -timedelta(secs::Integer) -timedelta{T<:Integer}(years::T,months::T,days::T,hours::T,mins::T,secs::T) -timedelta(dt_diff::Dates.Millisecond) -timedelta(d_diff::Dates.Day) + timedelta(secs::Integer) + timedelta(seconds::Dates.Second) + timedelta(Δdt::Dates.Millisecond) + timedelta(Δdate::Dates.Day) Format a time length in a human friendly format. -timedelta(secs::Integer) - e.g. 3699 -> 'An hour' -timedelta{T<:Integer}(years::T,months::T,days::T,hours::T,mins::T,secs::T) - e.g. days=1,hours=4,... -> 'A day' - hours=4,mins=2,... -> '4 hours' - years=1,months=2,... -> '1 year, 2 months' -timedelta(dt_diff::Dates.Millisecond) - e.g. DateTime(2014,2,3) - DateTime(2013,3,7) -> '11 months' -timedelta(d_diff::Dates.Day) - e.g. Date(2014,3,7) - Date(2013,2,4) -> '1 year, 1 month' + timedelta(seconds::Integer) # 3699 -> "An hour". + timedelta(Δdt::Dates.Millisecond) + e.g. DateTime(2014,2,3) - DateTime(2013,3,7) -> "11 months". + timedelta(Δdate::Dates.Day) + e.g. Date(2014,3,7) - Date(2013,2,4) -> "1 year, 1 month". """ -function timedelta(secs::Integer) +function timedelta(seconds::Integer) + secs = seconds mins = div( secs, 60); secs -= 60*mins hours = div( mins, 60); mins -= 60*hours days = div( hours, 24); hours -= 24*days @@ -87,36 +83,28 @@ function timedelta(secs::Integer) return "$years years" end end -# Assume nothing about magnitudes of inputs, so cast to seconds first -function timedelta(years::T, months::T, days::T, hours::T, mins::T, - secs::T) where T <: Integer - return timedelta(((((years*12+months)*30+days)*24+hours)*60+mins)*60+secs) -end -timedelta(dt_diff::Dates.Millisecond) = timedelta(div(dt_diff.value,1000)) -timedelta(d_diff::Dates.Day) = timedelta(d_diff.value*24*3600) +timedelta(seconds::Dates.Second) = timedelta(seconds.value) +timedelta(Δdt::Dates.Millisecond) = timedelta(convert(Dates.Second, Δdt)) +timedelta(Δdate::Dates.Day) = timedelta(convert(Dates.Second, Δdate)) -#--------------------------------------------------------------------- """ -digitsep(value::Integer, sep = ",", k = 3) - -Convert an integer to a string, separating each 'k' digits by 'sep'. 'k' -defaults to 3, separating by thousands. The default "," for 'sep' matches the -commonly used digit separator in the US. - -digitsep(value::Integer) - e.g. 12345678 -> "12,345,678" -digitsep(value::Integer, sep = "'") - e.g. 12345678 -> "12'345'678" -digitsep(value::Integer, sep = "'", k = 4) - e.g. 12345678 -> "1234'5678" + digitsep(value::Integer, separator=",", per_separator=3) + +Convert an integer to a string, separating each `per_separator` digits by +`separator`. + + digitsep(value) # 12345678 -> "12,345,678". + digitsep(value, separator="'") # 12345678 -> "12'345'678". + digitsep(value, separator="/", per_separator=4) # 12345678 -> "1234/5678". """ -function digitsep(value::Integer, sep = ",", k = 3) - value = string(value) - n = length(value) - starts = reverse(collect(n:-k:1)) - groups = [value[max(x-k+1, 1):x] for x in starts] - return join(groups, sep) +function digitsep(value::Integer, seperator=",", per_separator=3) + value = string(value) # Stringify, no seperators. + # Figure out last character index of each group of digits. + group_ends = reverse(collect(length(value):-per_separator:1)) + groups = [value[max(end_index - per_separator + 1, 1):end_index] + for end_index in group_ends] + return join(groups, seperator) end -end +end # module Humanize. diff --git a/test/runtests.jl b/test/runtests.jl index a660f1a..8db6924 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,10 +39,6 @@ end # testset datasize. ((0, 0, 65, 0, 0, 0), "2 months"), ((0, 0, 9, 0, 0, 0), "9 days"), ((0, 0, 365, 0, 0, 0), "a year")] - - @testset "full signature $output" for (inputs, output) in DATA - @test Humanize.timedelta(inputs...) == output - end @testset "datetime diff $output" for (inputs, output) in DATA base_datetime = Dates.DateTime(2014,1,1,0,0,0) From 19e4933379529814ca603b00bff722866f40cfee Mon Sep 17 00:00:00 2001 From: Iain Dunning Date: Mon, 2 Jul 2018 11:26:49 -0400 Subject: [PATCH 6/6] Fix digitsep for negative integers, close #8. Incorporates the changes in PR #8, which had become stale. --- src/Humanize.jl | 5 +++-- test/runtests.jl | 10 +++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Humanize.jl b/src/Humanize.jl index 4cf3fc3..cbb77e1 100644 --- a/src/Humanize.jl +++ b/src/Humanize.jl @@ -99,12 +99,13 @@ Convert an integer to a string, separating each `per_separator` digits by digitsep(value, separator="/", per_separator=4) # 12345678 -> "1234/5678". """ function digitsep(value::Integer, seperator=",", per_separator=3) - value = string(value) # Stringify, no seperators. + isnegative = value < zero(value) + value = string(abs(value)) # Stringify, no seperators. # Figure out last character index of each group of digits. group_ends = reverse(collect(length(value):-per_separator:1)) groups = [value[max(end_index - per_separator + 1, 1):end_index] for end_index in group_ends] - return join(groups, seperator) + return (isnegative ? "-" : "") * join(groups, seperator) end end # module Humanize. diff --git a/test/runtests.jl b/test/runtests.jl index 8db6924..80a9dae 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -69,7 +69,15 @@ end # testset timedelta. (12345, "12,345"), (123456, "123,456"), (1234567, "1,234,567"), - (12345678, "12,345,678")) + (12345678, "12,345,678"), + (-1, "-1"), + (-12, "-12"), + (-123, "-123"), + (-1234, "-1,234"), + (-12345, "-12,345"), + (-123456, "-123,456"), + (-1234567, "-1,234,567"), + (-12345678, "-12,345,678")) @testset "digitsep $output" for (input, output) in DATA @test Humanize.digitsep(input) == output end