├── test ├── test_helper.exs └── size_test.exs ├── .travis.yml ├── .gitignore ├── config └── config.exs ├── mix.exs ├── mix.lock ├── README.md ├── LICENSE └── lib └── size.ex /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.4.5 4 | otp_release: 5 | - 20.0 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :size, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:size, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Size.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :size, 7 | version: "0.1.1", 8 | elixir: "~> 1.4", 9 | build_embedded: Mix.env == :prod, 10 | start_permanent: Mix.env == :prod, 11 | description: description(), 12 | package: package(), 13 | deps: deps(), 14 | name: "Size", 15 | source_url: "https://github.com/jfcalvo/size" 16 | ] 17 | end 18 | 19 | # Configuration for the OTP application 20 | # 21 | # Type "mix help compile.app" for more information 22 | def application do 23 | # Specify extra applications you'll use from Erlang/Elixir 24 | [extra_applications: [:logger]] 25 | end 26 | 27 | # Dependencies can be Hex packages: 28 | # 29 | # {:my_dep, "~> 0.3.0"} 30 | # 31 | # Or git/path repositories: 32 | # 33 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 34 | # 35 | # Type "mix help deps" for more examples and options 36 | defp deps do 37 | [ 38 | {:ex_doc, "~> 0.22.1", only: :dev, runtime: false}, 39 | {:credo, "~> 0.8", only: [:dev, :test], runtime: false}, 40 | {:dialyxir, "~> 0.5", only: [:dev], runtime: false} 41 | ] 42 | end 43 | 44 | defp description do 45 | """ 46 | Size provides a set of functions to facilitate working with file sizes. 47 | """ 48 | end 49 | 50 | defp package do 51 | [ 52 | name: :size, 53 | files: ["lib", "mix.exs", "README*", "LICENSE*"], 54 | maintainers: ["José Francisco Calvo"], 55 | licenses: ["Apache-2.0"], 56 | links: %{"GitHub" => "https://github.com/jfcalvo/size"} 57 | ] 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, 3 | "credo": {:hex, :credo, "0.8.1", "137efcc99b4bc507c958ba9b5dff70149e971250813cbe7d4537ec7e36997402", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm", "27b5502c571a6877a9e8ed0bef599cd266b1ac5a2c5151c1534ad1235fc7a84a"}, 4 | "dialyxir": {:hex, :dialyxir, "0.5.0", "5bc543f9c28ecd51b99cc1a685a3c2a1a93216990347f259406a910cf048d1d7", [:mix], [], "hexpm", "7c5c6c1eceb93e26a06c36148cb6f8021ae6f4f9a07bb1ae95f588e0a01ea8e1"}, 5 | "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"}, 6 | "earmark_parser": {:hex, :earmark_parser, "1.4.9", "819bda2049e6ee1365424e4ced1ba65806eacf0d2867415f19f3f80047f8037b", [:mix], [], "hexpm", "8bf54fddabf2d7e137a0c22660e71b49d5a0a82d1fb05b5af62f2761cd6485c4"}, 7 | "ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"}, 8 | "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, 9 | "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, 10 | "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jfcalvo/size.svg?branch=master)](https://travis-ci.org/jfcalvo/size) 2 | 3 | # Size 4 | Size is a Elixir library that helps you working with file sizes. 5 | 6 | ## Installation 7 | 8 | Add `size` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [{:size, "~> 0.1.0"}] 13 | end 14 | ``` 15 | 16 | And run: 17 | 18 | ```elixir 19 | $ mix deps.get 20 | ``` 21 | 22 | ## What can you do with Size? 23 | 24 | ### Specify sizes 25 | 26 | `Size` defines a set of macros to improve specification of sizes. 27 | 28 | Instead of define a magic number that nobody understand like: 29 | 30 | ```elixir 31 | > disk_space = 12884901888 32 | ``` 33 | 34 | You can better do the following: 35 | 36 | ```elixir 37 | > import Size, only: :macros 38 | > disk_space = gigabytes(12) 39 | ``` 40 | 41 | A lot better, right?. 42 | 43 | You can also work using bits instead of bytes: 44 | 45 | ```elixir 46 | > import Size, only: :macros 47 | > network_speed = megabits(3) 48 | ``` 49 | 50 | The list of macros available are: 51 | 52 | For bytes (output always in bytes): 53 | 54 | ```elixir 55 | bytes(bytes) 56 | kilobytes(kilobytes) 57 | megabytes(megabytes) 58 | gigabytes(gigabytes) 59 | terabytes(terabytes) 60 | petabytes(petabytes) 61 | exabytes(exabytes) 62 | zettabytes(zettabytes) 63 | yottabytes(yottabytes) 64 | ``` 65 | 66 | For bits (output always in bits): 67 | 68 | ```elixir 69 | bits(bits) 70 | kilobits(kilobits) 71 | megabits(megabits) 72 | gigabits(gigabits) 73 | terabits(terabits) 74 | petabits(petabits) 75 | exabits(exabits) 76 | zettabits(zettabits) 77 | yottabits(yottabits) 78 | ``` 79 | 80 | #### Examples 81 | 82 | ```elixir 83 | > bytes(2.1) # Input in bytes, output in bytes 84 | 3 85 | 86 | > kilobytes(1) # Input in kilobytes, output in bytes 87 | 1024 88 | 89 | > megabytes(1) # Input in megabytes, output in bytes 90 | 1048576 91 | 92 | > megabytes(2) # Input in megabytes, output in bytes 93 | 2097152 94 | 95 | > megabytes(2.1) # Input in megabytes, output in bytes 96 | 2202010 97 | 98 | > gigabytes(12) # Input in gigabytes, output in bytes 99 | 12884901888 100 | 101 | > bits(2.1) # Input in bits, output in bits 102 | 3 103 | 104 | > kilobits(1) # Input in kilobits, output in bits 105 | 1000 106 | 107 | > megabits(100) # Input in megabits, output in bits 108 | 100000000 109 | 110 | > gigabits(12.5) # Input in gigabits, output in bits 111 | 12500000000 112 | ``` 113 | 114 | ### Humanize file sizes 115 | 116 | `Size` provide `humanize(size, options)` and `humanize!(size, options)` functions to generate human-readable file sizes. 117 | 118 | ```elixir 119 | > Size.humanize(1024) 120 | {:ok, "1 KB"} 121 | 122 | > Size.humanize!(1024) 123 | "1 KB" 124 | ``` 125 | 126 | `humanize` returns `{:ok, string}`, where `string` is the humanized version of the `size` parameter or `{:error, reason}` if an error occurs. 127 | 128 | `humanize!` returns a string with the humanized version of the `size` parameter or an exception is raised if an error occurs. 129 | 130 | `size` input parameter should always be a integer quantity of bytes. Not floats or strings are supported. 131 | 132 | #### Options 133 | 134 | `humanize` allows a set of options to customize the formatting/output of the given size. 135 | 136 | * `:bits` specifies whether the output will use bits instead of bytes (default: `false`). 137 | 138 | ```elixir 139 | > Size.humanize(1000, bits: true) 140 | {:ok, "8 Kb"} 141 | ``` 142 | 143 | * `:round` specifies using an integer the round to be done to the result value (default: `2`). 144 | 145 | ```elixir 146 | > Size.humanize(1234322, round: 1) 147 | {:ok, "1.2 MB"} 148 | 149 | > Size.humanize(1234322, round: 0) 150 | {:ok, "1 MB"} 151 | ``` 152 | 153 | * `:output` specifies the output type to be used, the possible values are `:tuple`, `:map` and `:string` (default: `:string`). 154 | 155 | ```elixir 156 | > Size.humanize(1024, output: :tuple) 157 | {:ok, {1, "KB"}} 158 | 159 | > Size.humanize(1024, output: :map) 160 | {:ok, %{symbol: "KB", value: 1}} 161 | 162 | > Size.humanize(1024, output: :string) 163 | {:ok, "1 KB"} 164 | 165 | > Size.humanize!(1024, output: :tuple) 166 | {1, "KB"} 167 | 168 | > Size.humanize!(1024, output: :map) 169 | %{symbol: "KB", value: 1} 170 | 171 | > Size.humanize!(1024, output: :string) 172 | "1 KB" 173 | ``` 174 | 175 | * `:spacer` specifies using a string the spacer to use between the value and the symbol (default: `" "`). 176 | 177 | ```elixir 178 | > Size.humanize(1024, spacer: "~") 179 | {:ok, "1~KB"} 180 | 181 | > Size.humanize(1024, spacer: "") 182 | {:ok, "1KB"} 183 | ``` 184 | 185 | * `:symbols` specifies a list of 9 string symbols to be used instead of the default one. 186 | 187 | ```elixir 188 | > alternative_symbols = ~w(bytes kilobytes megabytes gigabytes terabytes petabytes exabytes zettabytes yottabytes) 189 | > Size.humanize(2048, symbols: alternative_symbols) 190 | {:ok, "2 kilobytes"} 191 | ```` 192 | 193 | ## Author 194 | * **José Francisco Calvo** 195 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/size_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SizeTest do 2 | use ExUnit.Case, async: true 3 | doctest Size 4 | 5 | test "macros" do 6 | assert Size.bytes(1) == 1 7 | assert Size.kilobytes(1) == 1024 8 | assert Size.megabytes(1) == 1048576 9 | assert Size.gigabytes(1) == 1073741824 10 | assert Size.terabytes(1) == 1099511627776 11 | assert Size.petabytes(1) == 1125899906842624 12 | assert Size.exabytes(1) == 1152921504606846976 13 | assert Size.zettabytes(1) == 1180591620717411303424 14 | assert Size.yottabytes(1) == 1208925819614629174706176 15 | 16 | assert Size.bytes(2) == 2 17 | assert Size.bytes(3.2) == 4 18 | assert Size.kilobytes(2) == 2048 19 | assert Size.kilobytes(2.3) == 2356 20 | assert Size.megabytes(4) == 4194304 21 | assert Size.megabytes(1.6) == 1677722 22 | assert Size.gigabytes(3) == 3221225472 23 | assert Size.gigabytes(4.2) == 4509715661 24 | assert Size.terabytes(3) == 3298534883328 25 | assert Size.terabytes(12.1) == 13304090696090 26 | assert Size.petabytes(4) == 4503599627370496 27 | assert Size.petabytes(3.6) == 4053239664633447 28 | assert Size.exabytes(6) == 6917529027641081856 29 | assert Size.exabytes(3.9) == 4496393867966703104 30 | assert Size.zettabytes(2) == 2361183241434822606848 31 | assert Size.zettabytes(3.1) == 3659834024223975145472 32 | 33 | assert Size.bits(1) == 1 34 | assert Size.kilobits(1) == 1000 35 | assert Size.megabits(1) == 1000000 36 | assert Size.gigabits(1) == 1000000000 37 | assert Size.terabits(1) == 1000000000000 38 | assert Size.petabits(1) == 1000000000000000 39 | assert Size.exabits(1) == 1000000000000000000 40 | assert Size.zettabits(1) == 1000000000000000000000 41 | assert Size.yottabits(1) == 1000000000000000000000000 42 | 43 | assert Size.bits(4) == 4 44 | assert Size.bits(2.3) == 3 45 | assert Size.kilobits(3) == 3000 46 | assert Size.kilobits(3.1234) == 3124 47 | assert Size.megabits(2) == 2000000 48 | assert Size.megabits(2.3333333) == 2333334 49 | assert Size.gigabits(3) == 3000000000 50 | assert Size.gigabits(3.2234567893) == 3223456790 51 | assert Size.terabits(2) == 2000000000000 52 | assert Size.terabits(2.523478365) == 2523478365000 53 | assert Size.petabits(3) == 3000000000000000 54 | assert Size.petabits(3.432548343281542) == 3432548343281543 55 | assert Size.exabits(4) == 4000000000000000000 56 | assert Size.exabits(4.532) == 4532000000000000000 57 | assert Size.zettabits(2) == 2000000000000000000000 58 | assert Size.zettabits(2.54) == 2540000000000000000000 59 | assert Size.yottabits(4) == 4000000000000000000000000 60 | assert Size.yottabits(4.32) == 4319999999999999798673408 61 | end 62 | 63 | test "bytes" do 64 | assert Size.humanize!(0) == "0 B" 65 | assert Size.humanize!(1) == "1 B" 66 | assert Size.humanize!(100) == "100 B" 67 | assert Size.humanize!(500) == "500 B" 68 | assert Size.humanize!(1023) == "1023 B" 69 | 70 | assert Size.humanize(0) == {:ok, "0 B"} 71 | assert Size.humanize(1) == {:ok, "1 B"} 72 | assert Size.humanize(100) == {:ok, "100 B"} 73 | assert Size.humanize(500) == {:ok, "500 B"} 74 | assert Size.humanize(1023) == {:ok, "1023 B"} 75 | end 76 | 77 | test "kilobytes" do 78 | assert Size.humanize!(1024) == "1 KB" 79 | assert Size.humanize!(2000) == "1.95 KB" 80 | assert Size.humanize!(2048) == "2 KB" 81 | assert Size.humanize!(2348) == "2.29 KB" 82 | assert Size.humanize!(265318) == "259.1 KB" 83 | 84 | assert Size.humanize(1024) == {:ok, "1 KB"} 85 | assert Size.humanize(2000) == {:ok, "1.95 KB"} 86 | assert Size.humanize(2048) == {:ok, "2 KB"} 87 | assert Size.humanize(2348) == {:ok, "2.29 KB"} 88 | assert Size.humanize(265318) == {:ok, "259.1 KB"} 89 | end 90 | 91 | test "megabytes" do 92 | assert Size.humanize!(1048576) == "1 MB" 93 | assert Size.humanize!(1468006) == "1.4 MB" 94 | assert Size.humanize!(1509949) == "1.44 MB" 95 | assert Size.humanize!(2621440) == "2.5 MB" 96 | assert Size.humanize!(2673868) == "2.55 MB" 97 | 98 | assert Size.humanize(1048576) == {:ok, "1 MB"} 99 | assert Size.humanize(1468006) == {:ok, "1.4 MB"} 100 | assert Size.humanize(1509949) == {:ok, "1.44 MB"} 101 | assert Size.humanize(2621440) == {:ok, "2.5 MB"} 102 | assert Size.humanize(2673868) == {:ok, "2.55 MB"} 103 | end 104 | 105 | test "gigabytes" do 106 | assert Size.humanize!(1073741824) == "1 GB" 107 | assert Size.humanize!(1273741824) == "1.19 GB" 108 | assert Size.humanize!(2147483648) == "2 GB" 109 | assert Size.humanize!(2565464654) == "2.39 GB" 110 | assert Size.humanize!(2684354560) == "2.5 GB" 111 | 112 | assert Size.humanize(1073741824) == {:ok, "1 GB"} 113 | assert Size.humanize(1273741824) == {:ok, "1.19 GB"} 114 | assert Size.humanize(2147483648) == {:ok, "2 GB"} 115 | assert Size.humanize(2565464654) == {:ok, "2.39 GB"} 116 | assert Size.humanize(2684354560) == {:ok, "2.5 GB"} 117 | end 118 | 119 | test "terabytes" do 120 | assert Size.humanize!(1099511627776) == "1 TB" 121 | assert Size.humanize!(2199023255552) == "2 TB" 122 | assert Size.humanize!(2748779069440) == "2.5 TB" 123 | assert Size.humanize!(2803754650828) == "2.55 TB" 124 | assert Size.humanize!(4145158836715) == "3.77 TB" 125 | 126 | assert Size.humanize(1099511627776) == {:ok, "1 TB"} 127 | assert Size.humanize(2199023255552) == {:ok, "2 TB"} 128 | assert Size.humanize(2748779069440) == {:ok, "2.5 TB"} 129 | assert Size.humanize(2803754650828) == {:ok, "2.55 TB"} 130 | assert Size.humanize(4145158836715) == {:ok, "3.77 TB"} 131 | end 132 | 133 | test "petabytes" do 134 | assert Size.humanize!(1125899906842624) == "1 PB" 135 | assert Size.humanize!(2251799813685248) == "2 PB" 136 | assert Size.humanize!(2871044762448691) == "2.55 PB" 137 | assert Size.humanize!(2690900777353871) == "2.39 PB" 138 | assert Size.humanize!(3002399751505270) == "2.67 PB" 139 | 140 | assert Size.humanize(1125899906842624) == {:ok, "1 PB"} 141 | assert Size.humanize(2251799813685248) == {:ok, "2 PB"} 142 | assert Size.humanize(2871044762448691) == {:ok, "2.55 PB"} 143 | assert Size.humanize(2690900777353871) == {:ok, "2.39 PB"} 144 | assert Size.humanize(3002399751505270) == {:ok, "2.67 PB"} 145 | end 146 | 147 | test "exabytes" do 148 | assert Size.humanize!(1152921504606846976) == "1 EB" 149 | assert Size.humanize!(2305843009213694000) == "2 EB" 150 | assert Size.humanize!(2818893078763740700) == "2.44 EB" 151 | assert Size.humanize!(2939949836747459600) == "2.55 EB" 152 | assert Size.humanize!(3712407244834047500) == "3.22 EB" 153 | 154 | assert Size.humanize(1152921504606846976) == {:ok, "1 EB"} 155 | assert Size.humanize(2305843009213694000) == {:ok, "2 EB"} 156 | assert Size.humanize(2818893078763740700) == {:ok, "2.44 EB"} 157 | assert Size.humanize(2939949836747459600) == {:ok, "2.55 EB"} 158 | assert Size.humanize(3712407244834047500) == {:ok, "3.22 EB"} 159 | end 160 | 161 | test "zettabytes" do 162 | assert Size.humanize!(1180591620717411303424) == "1 ZB" 163 | assert Size.humanize!(2361183241434822606848) == "2 ZB" 164 | assert Size.humanize!(3361183241434822606848) == "2.85 ZB" 165 | assert Size.humanize!(3541774862152233910272) == "3 ZB" 166 | assert Size.humanize!(3641774862152233910272) == "3.08 ZB" 167 | 168 | assert Size.humanize(1180591620717411303424) == {:ok, "1 ZB"} 169 | assert Size.humanize(2361183241434822606848) == {:ok, "2 ZB"} 170 | assert Size.humanize(3361183241434822606848) == {:ok, "2.85 ZB"} 171 | assert Size.humanize(3541774862152233910272) == {:ok, "3 ZB"} 172 | assert Size.humanize(3641774862152233910272) == {:ok, "3.08 ZB"} 173 | end 174 | 175 | test "yottabytes" do 176 | assert Size.humanize!(1208925819614629174706176) == "1 YB" 177 | assert Size.humanize!(2208925819614629174706176) == "1.83 YB" 178 | assert Size.humanize!(6208925819614629174706176) == "5.14 YB" 179 | assert Size.humanize!(9208925819614629174706176) == "7.62 YB" 180 | assert Size.humanize!(9608925819614629174706176) == "7.95 YB" 181 | 182 | assert Size.humanize(1208925819614629174706176) == {:ok, "1 YB"} 183 | assert Size.humanize(2208925819614629174706176) == {:ok, "1.83 YB"} 184 | assert Size.humanize(6208925819614629174706176) == {:ok, "5.14 YB"} 185 | assert Size.humanize(9208925819614629174706176) == {:ok, "7.62 YB"} 186 | assert Size.humanize(9608925819614629174706176) == {:ok, "7.95 YB"} 187 | end 188 | 189 | test "size too big" do 190 | assert_raise Size.SizeTooBigError, fn -> 191 | Size.humanize!(1237940039285380274899124224) 192 | end 193 | 194 | assert Size.humanize(1237940039285380274899124224) == {:error, :size_too_big} 195 | end 196 | 197 | test "negative sizes" do 198 | assert Size.humanize!(-0) == "0 B" 199 | assert Size.humanize!(-500) == "-500 B" 200 | assert Size.humanize!(-1024) == "-1 KB" 201 | assert Size.humanize!(-1048576) == "-1 MB" 202 | assert Size.humanize!(-1468006) == "-1.4 MB" 203 | assert Size.humanize!(-1073741824) == "-1 GB" 204 | assert Size.humanize!(-1273741824) == "-1.19 GB" 205 | assert Size.humanize!(-1099511627776) == "-1 TB" 206 | assert Size.humanize!(-2199023255552) == "-2 TB" 207 | assert Size.humanize!(-2748779069440) == "-2.5 TB" 208 | assert Size.humanize!(-1125899906842624) == "-1 PB" 209 | assert Size.humanize!(-1152921504606846976) == "-1 EB" 210 | assert Size.humanize!(-1180591620717411303424) == "-1 ZB" 211 | assert Size.humanize!(-1208925819614629174706176) == "-1 YB" 212 | end 213 | 214 | test "using multiple options" do 215 | assert Size.humanize!(500) == "500 B" 216 | assert Size.humanize!(500, round: 1) == "500 B" 217 | assert Size.humanize!(500, round: 1, spacer: "") == "500B" 218 | assert Size.humanize!(500, bits: true) == "4 Kb" 219 | assert Size.humanize!(500, round: 1, bits: true) == "4 Kb" 220 | assert Size.humanize!(500, round: 1, bits: true, spacer: "") == "4Kb" 221 | assert Size.humanize!(500, round: 1, bits: true, spacer: "", output: :string) == "4Kb" 222 | assert Size.humanize!(500, round: 1, bits: true, spacer: "", output: :tuple) == {4, "Kb"} 223 | assert Size.humanize!(500, round: 1, bits: true, spacer: "", output: :map) == %{value: 4, symbol: "Kb"} 224 | 225 | assert Size.humanize!(1023) == "1023 B" 226 | assert Size.humanize!(1023, round: 1) == "1023 B" 227 | assert Size.humanize!(1023, round: 1, spacer: "") == "1023B" 228 | assert Size.humanize!(1023, bits: true) == "8.18 Kb" 229 | assert Size.humanize!(1023, round: 1, bits: true) == "8.2 Kb" 230 | assert Size.humanize!(1023, round: 1, bits: true, spacer: "") == "8.2Kb" 231 | assert Size.humanize!(1023, round: 1, bits: true, spacer: "", output: :string) == "8.2Kb" 232 | assert Size.humanize!(1023, round: 1, bits: true, spacer: "", output: :tuple) == {8.2, "Kb"} 233 | assert Size.humanize!(1023, round: 1, bits: true, spacer: "", output: :map) == %{value: 8.2, symbol: "Kb"} 234 | 235 | assert Size.humanize!(1024) == "1 KB" 236 | assert Size.humanize!(1024, round: 1) == "1 KB" 237 | assert Size.humanize!(1024, round: 1, spacer: "") == "1KB" 238 | assert Size.humanize!(1024, bits: true) == "8.19 Kb" 239 | assert Size.humanize!(1024, round: 1, bits: true) == "8.2 Kb" 240 | assert Size.humanize!(1024, round: 1, bits: true, spacer: "") == "8.2Kb" 241 | assert Size.humanize!(1024, round: 1, bits: true, spacer: "", output: :string) == "8.2Kb" 242 | assert Size.humanize!(1024, round: 1, bits: true, spacer: "", output: :tuple) == {8.2, "Kb"} 243 | assert Size.humanize!(1024, round: 1, bits: true, spacer: "", output: :map) == %{value: 8.2, symbol: "Kb"} 244 | 245 | assert Size.humanize!(-1024) == "-1 KB" 246 | assert Size.humanize!(-1024, round: 1) == "-1 KB" 247 | assert Size.humanize!(-1024, round: 1, spacer: "") == "-1KB" 248 | assert Size.humanize!(-1024, bits: true) == "-8.19 Kb" 249 | assert Size.humanize!(-1024, round: 1, bits: true) == "-8.2 Kb" 250 | assert Size.humanize!(-1024, round: 1, bits: true, spacer: "") == "-8.2Kb" 251 | assert Size.humanize!(-1024, round: 1, bits: true, spacer: "", output: :string) == "-8.2Kb" 252 | assert Size.humanize!(-1024, round: 1, bits: true, spacer: "", output: :tuple) == {-8.2, "Kb"} 253 | assert Size.humanize!(-1024, round: 1, bits: true, spacer: "", output: :map) == %{value: -8.2, symbol: "Kb"} 254 | 255 | assert Size.humanize!(1) == "1 B" 256 | assert Size.humanize!(1, round: 1) == "1 B" 257 | assert Size.humanize!(1, round: 1, spacer: "") == "1B" 258 | assert Size.humanize!(1, bits: true) == "8 b" 259 | assert Size.humanize!(1, round: 1, bits: true) == "8 b" 260 | assert Size.humanize!(1, round: 1, bits: true, spacer: "") == "8b" 261 | assert Size.humanize!(1, round: 1, bits: true, spacer: "", output: :string) == "8b" 262 | assert Size.humanize!(1, round: 1, bits: true, spacer: "", output: :tuple) == {8, "b"} 263 | assert Size.humanize!(1, round: 1, bits: true, spacer: "", output: :map) == %{value: 8, symbol: "b"} 264 | 265 | assert Size.humanize!(0) == "0 B" 266 | assert Size.humanize!(0, round: 1) == "0 B" 267 | assert Size.humanize!(0, round: 1, spacer: "") == "0B" 268 | assert Size.humanize!(0, bits: true) == "0 b" 269 | assert Size.humanize!(0, round: 1, bits: true) == "0 b" 270 | assert Size.humanize!(0, round: 1, bits: true, spacer: "") == "0b" 271 | assert Size.humanize!(0, round: 1, bits: true, spacer: "", output: :string) == "0b" 272 | assert Size.humanize!(0, round: 1, bits: true, spacer: "", output: :tuple) == {0, "b"} 273 | assert Size.humanize!(0, round: 1, bits: true, spacer: "", output: :map) == %{value: 0, symbol: "b"} 274 | end 275 | 276 | test "bits options" do 277 | assert Size.humanize!(125, bits: true) == "1 Kb" 278 | assert Size.humanize!(250, bits: true) == "2 Kb" 279 | assert Size.humanize!(500, bits: true) == "4 Kb" 280 | assert Size.humanize!(555, bits: true) == "4.44 Kb" 281 | assert Size.humanize!(125000, bits: true) == "1 Mb" 282 | assert Size.humanize!(250000, bits: true) == "2 Mb" 283 | assert Size.humanize!(292500, bits: true) == "2.34 Mb" 284 | end 285 | 286 | test "round option" do 287 | assert Size.humanize!(2000, round: 0) == "2 KB" 288 | assert Size.humanize!(2000, round: 1) == "2 KB" 289 | assert Size.humanize!(2000, round: 2) == "1.95 KB" 290 | assert Size.humanize!(2000, round: 3) == "1.953 KB" 291 | assert Size.humanize!(265318, round: 0) == "259 KB" 292 | assert Size.humanize!(265318, round: 1) == "259.1 KB" 293 | assert Size.humanize!(265318, round: 2) == "259.1 KB" 294 | assert Size.humanize!(265318, round: 3) == "259.1 KB" 295 | assert Size.humanize!(1509949, round: 1) == "1.4 MB" 296 | assert Size.humanize!(1509949, round: 2) == "1.44 MB" 297 | assert Size.humanize!(2684354560, round: 0) == "3 GB" 298 | assert Size.humanize!(2684354560, round: 1) == "2.5 GB" 299 | assert Size.humanize!(2684354560, round: 2) == "2.5 GB" 300 | end 301 | 302 | test "symbols option" do 303 | symbols = ~w(bytes kilobytes megabytes gigabytes terabytes petabytes exabytes zettabytes yottabytes) 304 | 305 | assert Size.humanize!(0, symbols: symbols) == "0 bytes" 306 | assert Size.humanize!(100, symbols: symbols) == "100 bytes" 307 | assert Size.humanize!(1024, symbols: symbols) == "1 kilobytes" 308 | assert Size.humanize!(1048576, symbols: symbols) == "1 megabytes" 309 | assert Size.humanize!(1073741824, symbols: symbols) == "1 gigabytes" 310 | assert Size.humanize!(1099511627776, symbols: symbols) == "1 terabytes" 311 | assert Size.humanize!(1125899906842624, symbols: symbols) == "1 petabytes" 312 | assert Size.humanize!(1152921504606846976, symbols: symbols) == "1 exabytes" 313 | assert Size.humanize!(1180591620717411303424, symbols: symbols) == "1 zettabytes" 314 | assert Size.humanize!(1208925819614629174706176, symbols: symbols) == "1 yottabytes" 315 | end 316 | 317 | test "spacer option" do 318 | assert Size.humanize!(1024, spacer: " ~ ") == "1 ~ KB" 319 | assert Size.humanize!(2000, spacer: "-") == "1.95-KB" 320 | assert Size.humanize!(100, spacer: "") == "100B" 321 | end 322 | 323 | test "string output option" do 324 | assert Size.humanize!(1024, output: :string) == "1 KB" 325 | assert Size.humanize!(2000, output: :string) == "1.95 KB" 326 | end 327 | 328 | test "tuple output option" do 329 | assert Size.humanize!(1024, output: :tuple) == {1, "KB"} 330 | assert Size.humanize!(2000, output: :tuple) == {1.95, "KB"} 331 | end 332 | 333 | test "map output option" do 334 | assert Size.humanize!(1024, output: :map) == %{value: 1, symbol: "KB"} 335 | assert Size.humanize!(2000, output: :map) == %{value: 1.95, symbol: "KB"} 336 | end 337 | 338 | test "invalid output option" do 339 | assert_raise Size.InvalidOutputError, fn -> 340 | Size.humanize!(1, output: :invalid_output) 341 | end 342 | 343 | assert Size.humanize(1, output: :invalid_output) == {:error, :invalid_output_option} 344 | end 345 | end 346 | -------------------------------------------------------------------------------- /lib/size.ex: -------------------------------------------------------------------------------- 1 | defmodule Size do 2 | @moduledoc """ 3 | Size provides a set of functions to facilitate working with file sizes. 4 | 5 | ## Specifying file sizes 6 | 7 | iex> Size.kilobytes(1) 8 | 1024 9 | 10 | iex> Size.megabytes(2.3) 11 | 2411725 12 | 13 | iex> Size.gigabytes(1) 14 | 1073741824 15 | 16 | ## Humanizing file sizes 17 | 18 | iex> Size.humanize(1073741824) 19 | {:ok, "1 GB"} 20 | 21 | iex> Size.humanize(1024, spacer: "") 22 | {:ok, "1KB"} 23 | 24 | iex> Size.humanize!(1073741824) 25 | "1 GB" 26 | 27 | iex> Size.humanize!(500, bits: true) 28 | "4 Kb" 29 | 30 | """ 31 | 32 | @doc """ 33 | Returns the value in bytes. 34 | 35 | ## Examples 36 | 37 | iex> Size.bytes(2) 38 | 2 39 | 40 | iex> Size.bytes(2.1) 41 | 3 42 | 43 | iex> Size.bytes(2.8) 44 | 3 45 | 46 | """ 47 | @spec bytes(number) :: integer 48 | defmacro bytes(bytes) when is_float(bytes) do 49 | round Float.ceil(bytes) 50 | end 51 | 52 | defmacro bytes(bytes) when is_integer(bytes) do 53 | bytes 54 | end 55 | 56 | @doc """ 57 | Returns an input value in kilobytes to bytes. 58 | 59 | ## Examples 60 | 61 | iex> Size.kilobytes(1) 62 | 1024 63 | 64 | iex> Size.kilobytes(2) 65 | 2048 66 | 67 | iex> Size.kilobytes(2.3) 68 | 2356 69 | 70 | """ 71 | @spec kilobytes(number) :: integer 72 | defmacro kilobytes(kilobytes) when is_float(kilobytes) do 73 | round Float.ceil(kilobytes * 1024) 74 | end 75 | 76 | defmacro kilobytes(kilobytes) when is_integer(kilobytes) do 77 | kilobytes * 1024 78 | end 79 | 80 | @doc """ 81 | Returns an input value in megabytes to bytes. 82 | 83 | ## Examples 84 | 85 | iex> Size.megabytes(1) 86 | 1048576 87 | 88 | iex> Size.megabytes(2) 89 | 2097152 90 | 91 | iex> Size.megabytes(2.1) 92 | 2202010 93 | 94 | """ 95 | @spec megabytes(number) :: integer 96 | defmacro megabytes(megabytes) when is_float(megabytes) do 97 | round Float.ceil(megabytes * 1024 * 1024) 98 | end 99 | 100 | defmacro megabytes(megabytes) when is_integer(megabytes) do 101 | megabytes * 1024 * 1024 102 | end 103 | 104 | @doc """ 105 | Returns an input value in gigabytes to bytes. 106 | 107 | ## Examples 108 | 109 | iex> Size.gigabytes(1) 110 | 1073741824 111 | 112 | iex> Size.gigabytes(2) 113 | 2147483648 114 | 115 | iex> Size.gigabytes(2.3) 116 | 2469606196 117 | 118 | """ 119 | @spec gigabytes(number) :: integer 120 | defmacro gigabytes(gigabytes) when is_float(gigabytes) do 121 | round Float.ceil(gigabytes * 1024 * 1024 * 1024) 122 | end 123 | 124 | defmacro gigabytes(gigabytes) when is_integer(gigabytes) do 125 | gigabytes * 1024 * 1024 * 1024 126 | end 127 | 128 | @doc """ 129 | Returns an input value in terabytes to bytes. 130 | 131 | ## Examples 132 | 133 | iex> Size.terabytes(1) 134 | 1099511627776 135 | 136 | iex> Size.terabytes(2) 137 | 2199023255552 138 | 139 | iex> Size.terabytes(2.1) 140 | 2308974418330 141 | 142 | """ 143 | @spec terabytes(number) :: integer 144 | defmacro terabytes(terabytes) when is_float(terabytes) do 145 | round Float.ceil(terabytes * 1024 * 1024 * 1024 * 1024) 146 | end 147 | 148 | defmacro terabytes(terabytes) when is_integer(terabytes) do 149 | terabytes * 1024 * 1024 * 1024 * 1024 150 | end 151 | 152 | @doc """ 153 | Returns an input value in petabytes to bytes. 154 | 155 | ## Examples 156 | 157 | iex> Size.petabytes(1) 158 | 1125899906842624 159 | 160 | iex> Size.petabytes(2) 161 | 2251799813685248 162 | 163 | iex> Size.petabytes(2.1) 164 | 2364389804369511 165 | 166 | """ 167 | @spec petabytes(number) :: integer 168 | defmacro petabytes(petabytes) when is_float(petabytes) do 169 | round Float.ceil(petabytes * 1024 * 1024 * 1024 * 1024 * 1024) 170 | end 171 | 172 | defmacro petabytes(petabytes) when is_integer(petabytes) do 173 | petabytes * 1024 * 1024 * 1024 * 1024 * 1024 174 | end 175 | 176 | @doc """ 177 | Returns an input value in exabytes to bytes. 178 | 179 | ## Examples 180 | 181 | iex> Size.exabytes(1) 182 | 1152921504606846976 183 | 184 | iex> Size.exabytes(2) 185 | 2305843009213693952 186 | 187 | iex> Size.exabytes(2.1) 188 | 2421135159674378752 189 | 190 | """ 191 | @spec exabytes(number) :: integer 192 | defmacro exabytes(exabytes) when is_float(exabytes) do 193 | round Float.ceil(exabytes * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 194 | end 195 | 196 | defmacro exabytes(exabytes) when is_integer(exabytes) do 197 | exabytes * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 198 | end 199 | 200 | @doc """ 201 | Returns an input value in zettabytes to bytes. 202 | 203 | ## Examples 204 | 205 | iex> Size.zettabytes(1) 206 | 1180591620717411303424 207 | 208 | iex> Size.zettabytes(2) 209 | 2361183241434822606848 210 | 211 | iex> Size.zettabytes(2.1) 212 | 2479242403506563842048 213 | 214 | """ 215 | @spec zettabytes(number) :: integer 216 | defmacro zettabytes(zettabytes) when is_float(zettabytes) do 217 | round Float.ceil(zettabytes * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 218 | end 219 | 220 | defmacro zettabytes(zettabytes) when is_integer(zettabytes) do 221 | zettabytes * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 222 | end 223 | 224 | @doc """ 225 | Returns an input value in yottabytes to bytes. 226 | 227 | ## Examples 228 | 229 | iex> Size.yottabytes(1) 230 | 1208925819614629174706176 231 | 232 | iex> Size.yottabytes(2) 233 | 2417851639229258349412352 234 | 235 | iex> Size.yottabytes(2.1) 236 | 2538744221190721374257152 237 | 238 | """ 239 | @spec yottabytes(number) :: integer 240 | defmacro yottabytes(yottabytes) when is_float(yottabytes) do 241 | round Float.ceil(yottabytes * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 242 | end 243 | 244 | defmacro yottabytes(yottabytes) when is_integer(yottabytes) do 245 | yottabytes * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 246 | end 247 | 248 | @doc """ 249 | Returns the value in bits. 250 | 251 | iex> Size.bits(1) 252 | 1 253 | 254 | iex> Size.bits(2.1) 255 | 3 256 | 257 | iex> Size.bits(2.8) 258 | 3 259 | 260 | """ 261 | @spec bits(number) :: integer 262 | defmacro bits(bits) when is_float(bits) do 263 | round Float.ceil(bits) 264 | end 265 | 266 | defmacro bits(bits) when is_integer(bits) do 267 | bits 268 | end 269 | 270 | @doc """ 271 | Returns an input value in kilobits to bits. 272 | 273 | iex> Size.kilobits(1) 274 | 1000 275 | 276 | iex> Size.kilobits(2) 277 | 2000 278 | 279 | iex> Size.kilobits(2.1) 280 | 2100 281 | 282 | """ 283 | @spec kilobits(number) :: integer 284 | defmacro kilobits(kilobits) when is_float(kilobits) do 285 | round Float.ceil(kilobits * 1000) 286 | end 287 | 288 | defmacro kilobits(kilobits) when is_integer(kilobits) do 289 | kilobits * 1000 290 | end 291 | 292 | @doc """ 293 | Returns an input value in megabits to bits. 294 | 295 | iex> Size.megabits(1) 296 | 1000000 297 | 298 | iex> Size.megabits(2) 299 | 2000000 300 | 301 | iex> Size.megabits(2.1) 302 | 2100000 303 | 304 | """ 305 | @spec megabits(number) :: integer 306 | defmacro megabits(megabits) when is_float(megabits) do 307 | round Float.ceil(megabits * 1000 * 1000) 308 | end 309 | 310 | defmacro megabits(megabits) when is_integer(megabits) do 311 | megabits * 1000 * 1000 312 | end 313 | 314 | @doc """ 315 | Returns an input value in gigabits to bits. 316 | 317 | iex> Size.gigabits(1) 318 | 1000000000 319 | 320 | iex> Size.gigabits(2) 321 | 2000000000 322 | 323 | iex> Size.gigabits(2.1) 324 | 2100000000 325 | 326 | """ 327 | @spec gigabits(number) :: integer 328 | defmacro gigabits(gigabits) when is_float(gigabits) do 329 | round Float.ceil(gigabits * 1000 * 1000 * 1000) 330 | end 331 | 332 | defmacro gigabits(gigabits) when is_integer(gigabits) do 333 | gigabits * 1000 * 1000 * 1000 334 | end 335 | 336 | @doc """ 337 | Returns an input value in terabits to bits. 338 | 339 | iex> Size.terabits(1) 340 | 1000000000000 341 | 342 | iex> Size.terabits(2) 343 | 2000000000000 344 | 345 | iex> Size.terabits(2.1) 346 | 2100000000000 347 | 348 | """ 349 | @spec terabits(number) :: integer 350 | defmacro terabits(terabits) when is_float(terabits) do 351 | round Float.ceil(terabits * 1000 * 1000 * 1000 * 1000) 352 | end 353 | 354 | defmacro terabits(terabits) when is_integer(terabits) do 355 | terabits * 1000 * 1000 * 1000 * 1000 356 | end 357 | 358 | @doc """ 359 | Returns an input value in petabits to bits. 360 | 361 | iex> Size.petabits(1) 362 | 1000000000000000 363 | 364 | iex> Size.petabits(2) 365 | 2000000000000000 366 | 367 | iex> Size.petabits(2.1) 368 | 2100000000000000 369 | 370 | """ 371 | @spec petabits(number) :: integer 372 | defmacro petabits(petabits) when is_float(petabits) do 373 | round Float.ceil(petabits * 1000 * 1000 * 1000 * 1000 * 1000) 374 | end 375 | 376 | defmacro petabits(petabits) when is_integer(petabits) do 377 | petabits * 1000 * 1000 * 1000 * 1000 * 1000 378 | end 379 | 380 | @doc """ 381 | Returns an input value in exabits to bits. 382 | 383 | iex> Size.exabits(1) 384 | 1000000000000000000 385 | 386 | iex> Size.exabits(2) 387 | 2000000000000000000 388 | 389 | iex> Size.exabits(2.1) 390 | 2100000000000000000 391 | 392 | """ 393 | @spec exabits(number) :: integer 394 | defmacro exabits(exabits) when is_float(exabits) do 395 | round Float.ceil(exabits * 1000 * 1000 * 1000 * 1000 * 1000 * 1000) 396 | end 397 | 398 | defmacro exabits(exabits) when is_integer(exabits) do 399 | exabits * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 400 | end 401 | 402 | @doc """ 403 | Returns an input value in zettabits to bits. 404 | 405 | iex> Size.zettabits(1) 406 | 1000000000000000000000 407 | 408 | iex> Size.zettabits(2) 409 | 2000000000000000000000 410 | 411 | iex> Size.zettabits(2.1) 412 | 2100000000000000000000 413 | 414 | """ 415 | @spec zettabits(number) :: integer 416 | defmacro zettabits(zettabits) when is_float(zettabits) do 417 | round Float.ceil(zettabits * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000) 418 | end 419 | 420 | defmacro zettabits(zettabits) when is_integer(zettabits) do 421 | zettabits * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 422 | end 423 | 424 | @doc """ 425 | Returns an input value in yottabits to bits. 426 | 427 | iex> Size.yottabits(1) 428 | 1000000000000000000000000 429 | 430 | iex> Size.yottabits(2) 431 | 2000000000000000000000000 432 | 433 | iex> Size.yottabits(2.1) 434 | 2100000000000000125829120 435 | 436 | """ 437 | @spec yottabits(number) :: integer 438 | defmacro yottabits(yottabits) when is_float(yottabits) do 439 | round Float.ceil(yottabits * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000) 440 | end 441 | 442 | defmacro yottabits(yottabits) when is_integer(yottabits) do 443 | yottabits * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 444 | end 445 | 446 | @type humanize_output :: {number, String.t} | %{value: number, symbol: String.t} | String.t 447 | 448 | @default_bits false 449 | @default_round 2 450 | @default_output :string 451 | @default_spacer " " 452 | @default_symbols %{ 453 | bits: ~w(b Kb Mb Gb Tb Pb Eb Zb Yb), 454 | bytes: ~w(B KB MB GB TB PB EB ZB YB) 455 | } 456 | 457 | @ceil %{bits: 1000, bytes: 1024} 458 | @exponent_limit length(@default_symbols[:bits]) - 1 459 | 460 | defmodule SizeTooBigError do 461 | defexception message: "size too big to be calculated" 462 | end 463 | 464 | defmodule InvalidOutputError do 465 | defexception message: "invalid output argument" 466 | end 467 | 468 | @doc """ 469 | Returns `{:ok, string}`, where `string` is the humanized version of the `size` parameter or 470 | `{:error, reason}` if an error occurs. 471 | 472 | `size` must be an integer indicating a number of bytes to be humanized. 473 | 474 | Error reasons: 475 | 476 | * `:size_too_big` - the number specified by the `size` parameter is too big to be humanized. 477 | * `:invalid_output_option` - the value specified on the optional parameter `:output` is not valid. 478 | 479 | ## Options 480 | 481 | The accepted options are: 482 | 483 | * `:bits` - specifies whether the output will use bits instead of bytes (default: `false`) 484 | * `:round` - specifies using an integer the round to be done to the result value (default: 2) 485 | * `:output` - specifies the ouput type to be used (default: `:string`) 486 | * `:spacer` - specifies using a string the spacer to use between the value and the symbol (default: `" "`) 487 | * `:symbols` - specifies a list of 9 string symbols to be used instead of the default one 488 | 489 | The values for `:bits` can be: 490 | 491 | * `true` - the output will use bits instead of bytes 492 | * `false` - the output will use bytes (default) 493 | 494 | The values for `:output` can be: 495 | 496 | * `:tuple` - the output will include a tuple like `{1024, KB}` 497 | * `:map` - the output will include a map like `{value: 1024, symbol: "KB"}` 498 | * `:string` - the output will include a string like `"1024 KB"` (default) 499 | 500 | ## Examples 501 | 502 | iex> Size.humanize(500) 503 | {:ok, "500 B"} 504 | 505 | iex> Size.humanize(1024) 506 | {:ok, "1 KB"} 507 | 508 | iex> Size.humanize(1024, spacer: "") 509 | {:ok, "1KB"} 510 | 511 | iex> Size.humanize(1024, output: :tuple) 512 | {:ok, {1, "KB"}} 513 | 514 | iex> Size.humanize(1024, output: :map) 515 | {:ok, %{value: 1, symbol: "KB"}} 516 | 517 | ## Examples that will return an error 518 | 519 | iex> Size.humanize(9999999999999999999999999999) 520 | {:error, :size_too_big} 521 | 522 | iex> Size.humanize(500, output: :not_valid_output) 523 | {:error, :invalid_output_option} 524 | 525 | """ 526 | @spec humanize(integer, keyword) :: {:ok, humanize_output} | {:error, atom} 527 | def humanize(size, options \\ []) when is_integer(size) do 528 | try do 529 | {:ok, humanize!(size, options)} 530 | rescue 531 | SizeTooBigError -> {:error, :size_too_big} 532 | InvalidOutputError -> {:error, :invalid_output_option} 533 | end 534 | end 535 | 536 | @doc """ 537 | Returns a string with the humanized version of the `size` parameter or an exception is raised 538 | if an error occurs. 539 | 540 | `size` must be an integer indicating a number of bytes to be humanized. 541 | 542 | Exceptions: 543 | 544 | * `SizeTooBigError` - the number specified by the `size` parameter is too big to be humanized 545 | * `InvalidOutputError` - the value specified on the optional parameter `:output` is not valid 546 | 547 | ## Options 548 | 549 | The accepted options are: 550 | 551 | * `:bits` - specifies whether the output will use bits instead of bytes (default: `false`) 552 | * `:round` - specifies using an integer the round to be done to the result value (default: 2) 553 | * `:output` - specifies the ouput type to be used (default: `:string`) 554 | * `:spacer` - specifies using a string the spacer to use between the value and the symbol (default: `" "`) 555 | * `:symbols` - specifies a list of 9 string symbols to be used instead of the default one 556 | 557 | The values for `:bits` can be: 558 | 559 | * `true` - the output will use bits instead of bytes 560 | * `false` - the output will use bytes (default) 561 | 562 | The values for `:output` can be: 563 | 564 | * `:tuple` - the output will include a tuple like `{1024, KB}` 565 | * `:map` - the output will include a map like `{value: 1024, symbol: "KB"}` 566 | * `:string` - the output will include a string like `"1024 KB"` (default) 567 | 568 | ## Examples 569 | 570 | iex> Size.humanize!(500) 571 | "500 B" 572 | 573 | iex> Size.humanize!(1024) 574 | "1 KB" 575 | 576 | iex> Size.humanize!(1024, spacer: "") 577 | "1KB" 578 | 579 | iex> Size.humanize!(1024, output: :tuple) 580 | {1, "KB"} 581 | 582 | iex> Size.humanize!(1024, output: :map) 583 | %{value: 1, symbol: "KB"} 584 | 585 | ## Examples that will return an exception 586 | 587 | iex> Size.humanize!(9999999999999999999999999999) 588 | ** (Size.SizeTooBigError) `9999999999999999999999999999` size argument is out of the limits to be humanized 589 | 590 | iex> Size.humanize!(500, output: :not_valid_output) 591 | ** (Size.InvalidOutputError) `not_valid_output` is not a valid output type 592 | 593 | """ 594 | @spec humanize!(integer, keyword) :: humanize_output 595 | def humanize!(size, options \\ []) when is_integer(size) do 596 | bits = Keyword.get(options, :bits, @default_bits) 597 | round = Keyword.get(options, :round, @default_round) 598 | output = Keyword.get(options, :output, @default_output) 599 | spacer = Keyword.get(options, :spacer, @default_spacer) 600 | symbols = Keyword.get_lazy options, :symbols, fn -> 601 | if bits, do: @default_symbols[:bits], else: @default_symbols[:bytes] 602 | end 603 | 604 | size = if bits, do: size * 8, else: size 605 | ceil = if bits, do: @ceil[:bits], else: @ceil[:bytes] 606 | exponent = exponent(size, ceil) 607 | result = result(size, exponent, ceil, round) 608 | symbol = Enum.at(symbols, exponent) 609 | 610 | case output do 611 | :tuple -> 612 | {result, symbol} 613 | :map -> 614 | %{value: result, symbol: symbol} 615 | :string -> 616 | "#{result}#{spacer}#{symbol}" 617 | _ -> 618 | raise InvalidOutputError, message: "`#{output}` is not a valid output type" 619 | end 620 | end 621 | 622 | defp exponent(0, _ceil), do: 0 623 | defp exponent(size, ceil) do 624 | exponent = :math.log(abs(size)) / :math.log(ceil) |> Float.floor |> round 625 | 626 | if exponent > @exponent_limit do 627 | raise SizeTooBigError, message: "`#{size}` size argument is out of the limits to be humanized" 628 | else 629 | exponent 630 | end 631 | end 632 | 633 | defp result(size, exponent, ceil, round) do 634 | result = size / :math.pow(ceil, exponent) |> Float.round(round) 635 | 636 | if round(result) == result, do: round(result), else: result 637 | end 638 | end 639 | --------------------------------------------------------------------------------