base85 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f8a6088b3843d55dae49087d58126a5527c36804fbcee912db569db9950ab40
4
- data.tar.gz: 5da6748b35556b5723052547d4c10cc0e55a5c1f85ab88f29df32760e164c4ce
3
+ metadata.gz: cbae229dec12dca96d21882da5944e4370e6f4d3096e85dad9ce0183a21c18d6
4
+ data.tar.gz: 8bfb829e5a5b61d5f6169940a55d74cc1534699350a2c563d0fe3feb51373d67
5
5
  SHA512:
6
- metadata.gz: 6e63afeb1c585b407dc2bb154ecc1a363e28919b7a354a9ae1c355293c5ed7b3e91c51a98f4adc8bd88c84391f47b4ccf825a545017a96fbb263d0863e5935c9
7
- data.tar.gz: d0e243fb0f19e05733836879cb47577b0127439e5145f5e11939eb3fab34837ebcba486681061a3f2a53cb8c5d3a161bac73e11b4184a64e937a3c46b749e738
6
+ metadata.gz: 894e13e4725732b656aa9b97194c7e81ef0fc9b87d4fa7d255e23762586a88b0396da955004de092677c5423de6384f7faf23fcc6b4c093c6fd5e82a5ada6cdd
7
+ data.tar.gz: f10eb21ea204e722f99b6b2c8f08fc7147bb48737b77b91c64cd9db95b72ad8925cd61f4ca380aedba01aa0390c556a521c61de54786049564fc99cd65da1aab
data/.rubocop.yml CHANGED
@@ -10,12 +10,21 @@ AllCops:
10
10
  Layout/LineLength:
11
11
  Max: 120
12
12
 
13
+ Metrics/AbcSize:
14
+ Max: 18
15
+
16
+ Metrics/ClassLength:
17
+ Enabled: false
18
+
13
19
  Metrics/MethodLength:
14
20
  Max: 20
15
21
 
16
22
  RSpec/DescribedClass:
17
23
  Enabled: false
18
24
 
25
+ RSpec/MultipleExpectations:
26
+ Max: 2
27
+
19
28
  Style/RaiseArgs:
20
29
  Enabled: false
21
30
 
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Changelog
2
2
 
3
- ## [0.2.0] - 20025-06-25
3
+ ## [0.3.0] - 2025-06-xx
4
+
5
+ - Add Ascii85 alphabet (Adobe flavor)
6
+ - Add base85 command line tool
7
+
8
+ ## [0.2.0] - 2025-06-25
4
9
 
5
10
  - Add Z85 and RFC1924 alphabets
6
11
 
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 TODO: Write your name
3
+ Copyright (c) 2025 sd77
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
+ [![Gem Version](https://badge.fury.io/rb/base85.svg)](https://badge.fury.io/rb/base85)
2
+
1
3
  # Base85
2
4
 
3
- Base85 is a pure Ruby gem to encode/decode data using Base85 encoding.
5
+ Base85 is a pure Ruby gem to encode/decode data using Base85 encodings.
4
6
 
5
7
  It handles mulitple alphabets:
6
8
 
7
9
  * standard (original) one,
10
+ * Ascii85 (Adobe version, very similar to standard one but with start and end markers, and a special 'z' character for 4 null bytes),
8
11
  * Z85 from [ZeroMQ](https://github.com/zeromq/libzmq),
9
12
  * [RFC1924](https://www.rfc-editor.org/rfc/rfc1924).
10
13
 
@@ -20,6 +23,33 @@ If bundler is not being used to manage dependencies, install the gem by executin
20
23
 
21
24
  ## Usage
22
25
 
26
+ ### CLI
27
+
28
+ `base85` command line tool may be used to decode/encode base85 dialects on command line.
29
+
30
+ ```bash
31
+ # Encode data from STDIN to STDOUT
32
+ echo "text to encode" | base85
33
+ # Encode using given alphabet (standard (default one), z85, ascii85 or rfc1924)
34
+ echo "text to encode" | base85 -a z85
35
+ # Output data is wrapped to 72 columns by default (as do base64)
36
+ # To change this value, use -w option (0 to deactivate wrapping)
37
+ echo "text to encode" | base85 -a rfc1924 -w 0
38
+
39
+ # Encode file to STDOUT
40
+ base85 -a ascii85 -w 40 myfile
41
+
42
+ # Decode data from STDIN to STDOUT
43
+ echo "aaaaa" | base85 -d
44
+ # Encode using given alphabet (standard (default one), z85, ascii85 or rfc1924)
45
+ echo "aaaaa" | base85 -d -a z85
46
+
47
+ # Decode file to STDOUT
48
+ base85 -d -a rfc1924 myfile
49
+ ```
50
+
51
+ ### Ruby library
52
+
23
53
  To encode a string:
24
54
 
25
55
  ```ruby
@@ -29,6 +59,9 @@ require "base85"
29
59
  Base85.encode("test1") #=> "FCfN80`"
30
60
  Base85.encode("test1", alphabet: :standard) #=> "FCfN80`"
31
61
 
62
+ # Use Ascii85 alphabet
63
+ Base85.encode("test1", alphabet: :ascii85) #=> "<~FCfN80`~>"
64
+
32
65
  # Use Z85 alphabet
33
66
  Base85.encode("test1", alphabet: :z85) #=> "By/Jnf-"
34
67
 
@@ -45,6 +78,13 @@ require "base85"
45
78
  Base85.decode("ASu!rA7]9") #=> "encoded"
46
79
  Base85.decode("ASu!rA7]9", alphabet: :standard) #=> "encoded"
47
80
 
81
+ # Use Ascii85 alphabet
82
+ Base85.decode("<~ASu!rA7]9~>", alphabet: :ascii85) #=> "encoded"
83
+ # Special z character
84
+ Base85.decode("<~z~>", alphabet: :ascii85) #=> "\u0000\u0000\u0000\u0000"
85
+ # Also accept string without markers
86
+ Base85.decode("ASu!rA7]9", alphabet: :ascii85) #=> "encoded"
87
+
48
88
  # Use Z85 alphabet
49
89
  Base85.decode("wO#0@wmYo", alphabet: :z85) #=> "encoded"
50
90
 
@@ -52,12 +92,6 @@ Base85.decode("wO#0@wmYo", alphabet: :z85) #=> "encoded"
52
92
  Base85.decode("Wo~0{WMyO", alphabet: :rfc1924) #=> "encoded"
53
93
  ```
54
94
 
55
- ## Development
56
-
57
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bundle exec bin/console` for an interactive prompt that will allow you to experiment.
58
-
59
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
60
-
61
95
  ## Contributing
62
96
 
63
97
  Bug reports and pull requests are welcome on Codeberg at <https://codeberg.org/sd77/base85>.
data/Rakefile CHANGED
@@ -10,3 +10,13 @@ require "rubocop/rake_task"
10
10
  RuboCop::RakeTask.new
11
11
 
12
12
  task default: %i[spec rubocop]
13
+
14
+ begin
15
+ require "yard"
16
+
17
+ YARD::Rake::YardocTask.new do |t|
18
+ t.files = ["lib/**/*.rb", "-", "README.md", "LICENSE.txt"]
19
+ t.options = %w[--no-private]
20
+ end
21
+ rescue LoadError # rubocop:disable Lint/SuppressedException
22
+ end
data/exe/base85 ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "base85/cli"
5
+
6
+ Base85::CLI.start(ARGV)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Base85
4
+ # Ascii85 base85 encoding. This encoding uses +!+ to +u+ ASCII characters to encode data. +z+ is used to represent
5
+ # 32-bit 0 value. The encoded string is surrounded by +<~+ and +~>+ markers.
6
+ # @author sd77
7
+ module Ascii85
8
+ extend ModuleMethods
9
+
10
+ # ASCII value for character +!+
11
+ BASE_ASCII_VALUE = "!".ord
12
+ # Ascii85 defines +z+ as a special character
13
+ SPECIAL_Z = true
14
+
15
+ # ASCII85 start marker
16
+ START_MARKER = "<~"
17
+ # ASCII85 end marker
18
+ END_MARKER = "~>"
19
+
20
+ # Encode a value (0..84 integer) to a Ascii85 character
21
+ # @param [Integer] value
22
+ # @return [String]
23
+ def self.encode_value(value)
24
+ (BASE_ASCII_VALUE + value).chr
25
+ end
26
+
27
+ # Encode data using Ascii85 encoding
28
+ # @param [String] data
29
+ # @return [String] encoded data, surrounded by +<~+ and +~>+ markers
30
+ def self.encode(data)
31
+ "#{START_MARKER}#{super}#{END_MARKER}"
32
+ end
33
+
34
+ # Decode a character from Ascii85 and give its index in alphabet
35
+ # @param [String] char a acharacter from Ascii85 alphabet
36
+ # @return [Integer] index of this character in alphabet
37
+ # @raise DecodeError +char+ is not known in alphabet
38
+ def self.decode_char(char)
39
+ case char
40
+ when "!".."u" # rubocop:disable Lint/MixedCaseRange
41
+ char.ord - BASE_ASCII_VALUE
42
+ else
43
+ raise DecodeError, "unknown character '#{char}'"
44
+ end
45
+ end
46
+
47
+ # Decode data using Ascii85 encoding
48
+ # @param [String] data Encoded string, may be prefixed by +<~+ and suffixed by +~>+
49
+ # @return [String] Decoded data
50
+ # @raise [DecodeError] unknown character or invalid tuple
51
+ def self.decode(data)
52
+ start = data.start_with?(START_MARKER) ? 2 : 0
53
+ stop = data.end_with?(END_MARKER) ? -3 : -1
54
+ super(data[start..stop])
55
+ end
56
+ end
57
+ end
data/lib/base85/cli.rb ADDED
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require_relative "../base85"
5
+
6
+ module Base85
7
+ # Base85 comman line interface
8
+ # @author sd77
9
+ class CLI
10
+ # Default wrap column count
11
+ DEFAULT_WRAP = 76
12
+ # @private
13
+ # Max size to read from input at once
14
+ READ_MAX_SIZE = 4096
15
+ # Known alphabet codes for --alphabet option
16
+ ALPHABETS = Base85::SUPPORTED_ALPHABETS.map(&:to_s).freeze
17
+
18
+ # Decode (+true+) or encode (+false+)
19
+ # @return [Boolean]
20
+ attr_accessor :decode
21
+ # Alpahbet to use
22
+ # @param alphabet [:standard, :ascii85 :z85 :rfc1924]
23
+ attr_writer :alphabet
24
+ # column count after which output is wrapped
25
+ # @return [Integer]
26
+ attr_accessor :wrap
27
+
28
+ # Create a CLI object and parse arguments
29
+ # @param [Array[String]] argv
30
+ # @return [CLI]
31
+ def self.start(argv)
32
+ new.start(argv)
33
+ end
34
+
35
+ # @return [self]
36
+ def initialize
37
+ @decode = false
38
+ @alphabet = :standard
39
+ @wrap = DEFAULT_WRAP
40
+ end
41
+
42
+ # Alpahbet to use
43
+ # @return [:standard, :ascii85 :z85 :rfc1924]
44
+ def alphabet
45
+ @alphabet.to_sym
46
+ end
47
+
48
+ # Parse arguments
49
+ # @param [Array[String]] argv
50
+ # @return [self]
51
+ def start(argv)
52
+ parser = create_option_parser
53
+ parser.parse!(argv)
54
+
55
+ abort("---wrap may only be used when encoding") if decode && (wrap != DEFAULT_WRAP)
56
+
57
+ if argv.empty?
58
+ process($stdin)
59
+ elsif argv.size == 1
60
+ File.open(argv.shift) do |file|
61
+ process(file)
62
+ end
63
+ else
64
+ parser.abort("Too much operands")
65
+ end
66
+
67
+ self
68
+ end
69
+
70
+ # Process +input+
71
+ # @param [IO] input
72
+ # @return [void]
73
+ def process(input)
74
+ if decode
75
+ decode_input(input)
76
+ else
77
+ encode_input(input)
78
+ end
79
+ end
80
+
81
+ # Encode +input+ and print it to stdout
82
+ # @param [IO] input
83
+ # @return [void]
84
+ def encode_input(input)
85
+ rest = +""
86
+ current_col = handle_ascii85_start
87
+ until input.eof?
88
+ data = rest << input.read(READ_MAX_SIZE)
89
+ mod = data.size % 4
90
+ rest = mod.zero? ? +"" : data.slice!(-mod..)
91
+ current_col = print_wrapped(encode_data(data), current_col)
92
+ end
93
+
94
+ encoded_rest = handle_ascii85_end(encode_data(rest))
95
+ print_wrapped(encoded_rest, current_col)
96
+ end
97
+
98
+ # @param [String] data
99
+ # @return [String] encoded data
100
+ def encode_data(data)
101
+ encoded = Base85.encode(data, alphabet: alphabet)
102
+ ascii85_remove_markers(encoded)
103
+ end
104
+
105
+ # Decode +input+ and print it to stdout
106
+ # @param [IO] input
107
+ # @return [void]
108
+ def decode_input(input)
109
+ rest = +""
110
+ until input.eof?
111
+ data = rest << input.read(READ_MAX_SIZE)
112
+ ascii85_remove_markers(data)
113
+ mod = data.size % 5
114
+ rest = mod.zero? ? +"" : data.slice!(-mod..)
115
+ print decode_data(data)
116
+ end
117
+ print decode_data(rest)
118
+ end
119
+
120
+ # @param [String] data
121
+ # @return [String] decoded data
122
+ def decode_data(data)
123
+ Base85.decode(data, alphabet: alphabet)
124
+ end
125
+
126
+ private
127
+
128
+ def create_option_parser
129
+ OptionParser.new do |parser|
130
+ prepare_banner(parser)
131
+ parser.on("-d", "--decode", "Decode data.") { self.decode = true }
132
+ parser.on("-a", "--alphabet ALPHABET", ALPHABETS,
133
+ "Alphabet to use: #{ALPHABETS.join(", ")}\n",
134
+ "(default standard).") { |alphabet| self.alphabet = alphabet }
135
+ parser.on("-w", "--wrap COLS", Integer,
136
+ "Wrap encoded lines after WRAP characters (default #{DEFAULT_WRAP}).\n",
137
+ "Use 0 to disable wrapping.") { |cols| self.wrap = cols }
138
+ parser.on("--version", "Output version information and exit.") do
139
+ show_version
140
+ exit
141
+ end
142
+ end
143
+ end
144
+
145
+ def prepare_banner(parser)
146
+ parser.banner = "Usage: #{$PROGRAM_NAME} [options] [FILE]\n" \
147
+ "Encode or decode FILE or standard input to standard output."
148
+ parser.separator ""
149
+ parser.separator "With no FILE, or when FILE is -, read standard input."
150
+ parser.separator ""
151
+ parser.separator "Options:"
152
+ end
153
+
154
+ def show_version
155
+ puts "base85 #{Base85::VERSION}"
156
+ end
157
+
158
+ def ascii85?
159
+ alphabet == :ascii85
160
+ end
161
+
162
+ def ascii85_remove_markers(data)
163
+ return data unless ascii85?
164
+
165
+ data.delete_prefix!("<~")
166
+ data.delete_suffix!("~>")
167
+ data
168
+ end
169
+
170
+ def handle_ascii85_start
171
+ if ascii85?
172
+ print Ascii85::START_MARKER
173
+ Ascii85::START_MARKER.size
174
+ else
175
+ 0
176
+ end
177
+ end
178
+
179
+ def handle_ascii85_end(data)
180
+ data << Ascii85::END_MARKER if ascii85?
181
+ data
182
+ end
183
+
184
+ def print_wrapped(data, current_col)
185
+ if wrap.zero?
186
+ print data
187
+ return 0
188
+ end
189
+
190
+ puts data.slice!(0, wrap - current_col) if (data.size + current_col) > wrap
191
+ puts data.slice!(0, wrap) while data.size > wrap
192
+ print data
193
+ data.size
194
+ end
195
+ end
196
+ end
@@ -12,6 +12,8 @@ module Base85
12
12
  # @param [Integer] word
13
13
  # @param [Integer] byte_count byte count of word. Must be in range 1..4
14
14
  def encode_word(word, byte_count: 4)
15
+ return "z" if const_defined?(:SPECIAL_Z) && word.zero? && (byte_count == 4)
16
+
15
17
  encoded = "~" * 5
16
18
  5.times do |i|
17
19
  word, r = word.divmod(85)
@@ -20,9 +22,9 @@ module Base85
20
22
  encoded[..byte_count]
21
23
  end
22
24
 
23
- # Encode data using standard base85 encoding
24
- # @param [String] data
25
- # @return [String]
25
+ # Encode data in base85
26
+ # @param [String] data data to encode
27
+ # @return [String] base85 encoded string
26
28
  def encode(data)
27
29
  result = +""
28
30
  data.unpack("N*").each do |word|
@@ -36,17 +38,23 @@ module Base85
36
38
  result << encode_word(last_chars.unpack1("N"), byte_count: last_count)
37
39
  end
38
40
 
39
- # Decode data using standard base85 encoding
40
- # @param [String] data
41
- # @return [String]
41
+ # Decode data from base85
42
+ # @param [String] data base85 encoded data
43
+ # @return [String] decoded data
42
44
  # @raise [DecodeError] unknown character or invalid tuple
43
45
  def decode(data)
44
46
  result = +""
45
47
  word = 0
46
48
  count = 0
47
49
  data.each_char do |c|
50
+ if c == "z" && const_defined?(:SPECIAL_Z)
51
+ raise DecodeError, "'z' cannot be in the middle of a tuple" if count.positive?
52
+
53
+ result << "\0\0\0\0"
54
+ next
55
+ end
56
+
48
57
  idx = decode_char(c)
49
- p c if idx.nil?
50
58
 
51
59
  word += idx * LUT[count]
52
60
  count += 1
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Base85
4
4
  # RFC 1924 base85 encoding
5
- # # @author sd77
5
+ # @author sd77
6
6
  module Rfc1924
7
7
  extend ModuleMethods
8
8
 
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Base85
4
- # Standard base85 encoding. This encoding uses '!' to 'u' ASCII characters to encode data.
4
+ # Standard base85 encoding. This encoding uses +!+ to +u+ ASCII characters to encode data.
5
5
  # @author sd77
6
6
  module Standard
7
7
  extend ModuleMethods
8
8
 
9
- # ASCII value for character '!'
9
+ # ASCII value for character +!+
10
10
  BASE_ASCII_VALUE = "!".ord
11
11
 
12
12
  # Encode a value (0..84 integer) to a standard base85 character
@@ -17,7 +17,7 @@ module Base85
17
17
  end
18
18
 
19
19
  # Decode a character from standard base85 and give its index in alphabet
20
- # @param [String] char a acharacter from base85 alphabet
20
+ # @param [String] char a character from base85 alphabet
21
21
  # @return [Integer] index of this character in alphabet
22
22
  # @raise DecodeError +char+ is not known in alphabet
23
23
  def self.decode_char(char)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Base85
4
- VERSION = "0.2.0"
4
+ # Base85 gem version
5
+ VERSION = "0.3.0"
5
6
  end
data/lib/base85.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "base85/module_methods"
5
5
  require_relative "base85/standard"
6
6
  require_relative "base85/z85"
7
7
  require_relative "base85/rfc1924"
8
+ require_relative "base85/ascii85"
8
9
 
9
10
  # Provide methods to encode/decode Base85 formatted data
10
11
  # @author sd77
@@ -17,23 +18,31 @@ module Base85
17
18
 
18
19
  # Unknown alphabet exception
19
20
  class UnknownAlphabetError < Error
21
+ # @param [Symbol] name alphabet name
20
22
  def initialize(name)
21
23
  super
22
24
  @name = name
23
25
  end
24
26
 
27
+ # @return [String]
25
28
  def message
26
29
  "Uknown alphabet '#{@name}'"
27
30
  end
28
31
  end
29
32
 
30
33
  # Supported Base85 alphabets
31
- SUPPORTED_ALPHABETS = %i[standard z85 rfc1924].freeze
34
+ SUPPORTED_ALPHABETS = %i[standard ascii85 z85 rfc1924].freeze
32
35
 
36
+ # Encode +data+ using given +alphabet+
37
+ # @param [String] data data to encode
38
+ # @param [:standard,:ascii85,:z85,:rfc1924] alphabet alphabet to use to encode
39
+ # @return [String] encoded string
33
40
  def self.encode(data, alphabet: :standard)
34
41
  case alphabet
35
42
  when :standard
36
43
  Standard.encode(data)
44
+ when :ascii85
45
+ Ascii85.encode(data)
37
46
  when :z85
38
47
  Z85.encode(data)
39
48
  when :rfc1924
@@ -43,10 +52,16 @@ module Base85
43
52
  end
44
53
  end
45
54
 
55
+ # Decode +data+ using given +alphabet+
56
+ # @param [String] data data to decode
57
+ # @param [:standard,:ascii85,:z85,:rfc1924] alphabet alphabet to use to decode
58
+ # @return [String] decoded string
46
59
  def self.decode(data, alphabet: :standard)
47
60
  case alphabet
48
61
  when :standard
49
62
  Standard.decode(data)
63
+ when :ascii85
64
+ Ascii85.decode(data)
50
65
  when :z85
51
66
  Z85.decode(data)
52
67
  when :rfc1924
metadata CHANGED
@@ -1,20 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: base85
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sd77
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-15 00:00:00.000000000 Z
11
+ date: 2025-06-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Encode and decode base85 data. Multiple alphabets are supported (standard,
14
- Z85 or rfc1924)
13
+ description: |
14
+ Encode and decode base85 data. Multiple alphabets are supported (standard,
15
+ Ascii85, Z85 or RFC 1924).
15
16
  email:
16
17
  - sd@ld77.eu
17
- executables: []
18
+ executables:
19
+ - base85
18
20
  extensions: []
19
21
  extra_rdoc_files: []
20
22
  files:
@@ -24,7 +26,10 @@ files:
24
26
  - LICENSE.txt
25
27
  - README.md
26
28
  - Rakefile
29
+ - exe/base85
27
30
  - lib/base85.rb
31
+ - lib/base85/ascii85.rb
32
+ - lib/base85/cli.rb
28
33
  - lib/base85/module_methods.rb
29
34
  - lib/base85/rfc1924.rb
30
35
  - lib/base85/standard.rb