Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.0-dev

- Refactor the package to strictly follow the `Converter<S, T>` interface established in `dart:convert` and clean up the API as a result.

## 0.1.0

- Initial version.
68 changes: 27 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ with the prefix `xn--`.

* **RFC 3492 Compliant:** Implements the Punycode encoding and decoding
algorithms as specified in the standard.
* **IDNA Helpers:** Provides convenient methods (`toAscii`, `toUnicode`) for
converting full domain names or email addresses, automatically handling
the `xn--` prefix and processing only the necessary parts of the string.
* **Core Codec:** Offers direct access to the raw Punycode `encode` and `decode`
operations via `PunycodeCodec` for advanced use cases.
* **IDNA Friendly:** Provides convenient ways of converting full domain names or
email addresses, automatically handling the `xn--` prefix and processing
only the necessary parts of the string.
* **Idiomatic Dart:** This package implements the `Converter<S, T>` interface
defined in `dart:convert` to make working with Punycode easy and familiar.
* **Efficient and Tested:** Based on a port of the well-regarded Punycode.js
library, including tests based on official RFC examples.

Expand All @@ -54,41 +54,27 @@ The easiest way to handle domain names or emails is using the singleton
import 'package:punycoder/punycoder.dart';

void main() {
// Use the singleton encoder/decoder for common IDNA tasks
const encoder = punycodeEncoder;
const decoder = punycodeDecoder;

// --- Convert domains/emails TO ASCII (ACE format) ---
final domainsToEncode = ['bücher.example', 'example.com', '你好.test'];
print('Encoding to ASCII:');
for (final domain in domainsToEncode) {
final asciiVersion = encoder.toAscii(domain);
// toAscii adds 'xn--' prefix only if encoding actually happens
print('"$domain" -> "$asciiVersion"');
// Output:
// "bücher.example" -> "xn--bcher-kva.example"
// "example.com" -> "example.com"
// "你好.test" -> "xn--6qq79v.test"
}

// --- Convert domains/emails FROM ASCII (ACE format) ---
final domainsToDecode = ['xn--bcher-kva.example', 'example.com', 'xn--6qq79v.test'];
print('Decoding from ASCII:');
for (final domain in domainsToDecode) {
final unicodeVersion = decoder.toUnicode(domain);
// toUnicode decodes labels starting with 'xn--'
print('"$domain" -> "$unicodeVersion"');
// Output:
// "xn--bcher-kva.example" -> "bücher.example"
// "example.com" -> "example.com"
// "xn--6qq79v.test" -> "你好.test"
}

// --- Raw Encoding/Decoding (Advanced) ---
// For direct encoding/decoding without automatic prefix handling or domain splitting:
final codec = PunycodeCodec();
final rawEncoded = codec.encode('bücher'); // -> 'bcher-kva' (no prefix)
final rawDecoded = codec.decode('bcher-kva'); // -> 'bücher'
print('\nRaw codec example: "$rawDecoded"');
// Designed to be used with domains and emails which have special rules
const domainCodec = PunycodeCodec();
// Designed to work with simple strings
const simpleCodec = PunycodeCodec.simple();

final encodedString = simpleCodec.encode('münchen');
final encodedDomain = domainCodec.encode('münchen.com');
final encodedEmail = domainCodec.encode('münchen@münchen.com');

stdout.writeln(encodedString); // Output: mnchen-3ya
// Uses the correct prefix for the domain
stdout.writeln(encodedDomain); // Output: xn--mnchen-3ya.com
// Only the domain should be encoded
stdout.writeln(encodedEmail); // Output: münchen@xn--mnchen-3ya.com

final decodedString = simpleCodec.decode('mnchen-3ya');
final decodecDomain = domainCodec.decode('xn--mnchen-3ya.com');
final decodedEmail = domainCodec.decode('münchen@xn--mnchen-3ya.com');

stdout.writeln(decodedString); // Output: münchen
stdout.writeln(decodecDomain); // Output: münchen.com
stdout.writeln(decodedEmail); // Output: münchen@münchen.com
}
```
2 changes: 1 addition & 1 deletion doc/api/__404error.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ <h5><span class="package-name">punycoder</span> <span class="package-kind">packa
<footer>
<span class="no-break">
punycoder
0.1.0-dev
0.1.0
</span>

</footer>
Expand Down
73 changes: 30 additions & 43 deletions doc/api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@

<section class="desc markdown">
<p><a href="https://pub.dev/packages/punycoder"><img src="https://img.shields.io/pub/v/punycoder.svg" alt="pub package"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT"></a></p>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT"></a>
<a href="https://studio.firebase.google.com/import?url=https%3A%2F%2Fgithub.com%2Fdropbear-software%2Fpunycoder"><img src="https://cdn.firebasestudio.dev/btn/open_light_20.svg" alt="Open in Firebase Studio"></a></p>
<h1 id="punycoder">Punycoder</h1>
<p>A Dart implementation of the Punycode (RFC 3492) encoding algorithm used for
Internationalized Domain Names in Applications (IDNA).</p>
Expand All @@ -63,11 +64,11 @@ <h2 id="features">Features</h2>
<ul>
<li><strong>RFC 3492 Compliant:</strong> Implements the Punycode encoding and decoding
algorithms as specified in the standard.</li>
<li><strong>IDNA Helpers:</strong> Provides convenient methods (<code>toAscii</code>, <code>toUnicode</code>) for
converting full domain names or email addresses, automatically handling
the <code>xn--</code> prefix and processing only the necessary parts of the string.</li>
<li><strong>Core Codec:</strong> Offers direct access to the raw Punycode <code>encode</code> and <code>decode</code>
operations via <code>PunycodeCodec</code> for advanced use cases.</li>
<li><strong>IDNA Friendly:</strong> Provides convenient ways of converting full domain names or
email addresses, automatically handling the <code>xn--</code> prefix and processing
only the necessary parts of the string.</li>
<li><strong>Idiomatic Dart:</strong> This package implements the <code>Converter&lt;S, T&gt;</code> interface
defined in <code>dart:convert</code> to make working with Punycode easy and familiar.</li>
<li><strong>Efficient and Tested:</strong> Based on a port of the well-regarded Punycode.js
library, including tests based on official RFC examples.</li>
</ul>
Expand All @@ -85,42 +86,28 @@ <h2 id="usage">Usage</h2>
<pre class="language-dart"><code class="language-dart">import 'package:punycoder/punycoder.dart';

void main() {
// Use the singleton encoder/decoder for common IDNA tasks
const encoder = punycodeEncoder;
const decoder = punycodeDecoder;

// --- Convert domains/emails TO ASCII (ACE format) ---
final domainsToEncode = ['bücher.example', 'example.com', '你好.test'];
print('Encoding to ASCII:');
for (final domain in domainsToEncode) {
final asciiVersion = encoder.toAscii(domain);
// toAscii adds 'xn--' prefix only if encoding actually happens
print('"$domain" -&gt; "$asciiVersion"');
// Output:
// "bücher.example" -&gt; "xn--bcher-kva.example"
// "example.com" -&gt; "example.com"
// "你好.test" -&gt; "xn--6qq79v.test"
}

// --- Convert domains/emails FROM ASCII (ACE format) ---
final domainsToDecode = ['xn--bcher-kva.example', 'example.com', 'xn--6qq79v.test'];
print('Decoding from ASCII:');
for (final domain in domainsToDecode) {
final unicodeVersion = decoder.toUnicode(domain);
// toUnicode decodes labels starting with 'xn--'
print('"$domain" -&gt; "$unicodeVersion"');
// Output:
// "xn--bcher-kva.example" -&gt; "bücher.example"
// "example.com" -&gt; "example.com"
// "xn--6qq79v.test" -&gt; "你好.test"
}

// --- Raw Encoding/Decoding (Advanced) ---
// For direct encoding/decoding without automatic prefix handling or domain splitting:
final codec = PunycodeCodec();
final rawEncoded = codec.encode('bücher'); // -&gt; 'bcher-kva' (no prefix)
final rawDecoded = codec.decode('bcher-kva'); // -&gt; 'bücher'
print('\nRaw codec example: "$rawDecoded"');
// Designed to be used with domains and emails which have special rules
const domainCodec = PunycodeCodec();
// Designed to work with simple strings
const simpleCodec = PunycodeCodec.simple();

final encodedString = simpleCodec.encode('münchen');
final encodedDomain = domainCodec.encode('münchen.com');
final encodedEmail = domainCodec.encode('münchen@münchen.com');

stdout.writeln(encodedString); // Output: mnchen-3ya
// Uses the correct prefix for the domain
stdout.writeln(encodedDomain); // Output: xn--mnchen-3ya.com
// Only the domain should be encoded
stdout.writeln(encodedEmail); // Output: münchen@xn--mnchen-3ya.com

final decodedString = simpleCodec.decode('mnchen-3ya');
final decodecDomain = domainCodec.decode('xn--mnchen-3ya.com');
final decodedEmail = domainCodec.decode('münchen@xn--mnchen-3ya.com');

stdout.writeln(decodedString); // Output: münchen
stdout.writeln(decodecDomain); // Output: münchen.com
stdout.writeln(decodedEmail); // Output: münchen@münchen.com
}
</code></pre>
</section>
Expand Down Expand Up @@ -164,7 +151,7 @@ <h5 class="hidden-xs"><span class="package-name">punycoder</span> <span class="p
<footer>
<span class="no-break">
punycoder
0.1.0-dev
0.1.0
</span>

</footer>
Expand Down
2 changes: 1 addition & 1 deletion doc/api/index.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"name":"punycoder","qualifiedName":"punycoder","href":"punycoder/","kind":9,"packageRank":0,"desc":"Provides a Dart implementation of the Punycode encoding algorithm\nspecified in RFC 3492."},{"name":"PunycodeCodec","qualifiedName":"punycoder.PunycodeCodec","href":"punycoder/PunycodeCodec-class.html","kind":3,"packageRank":0,"desc":"A codec for encoding and decoding strings using the Punycode algorithm.","enclosedBy":{"name":"punycoder","kind":9,"href":"punycoder/"}},{"name":"PunycodeCodec.new","qualifiedName":"punycoder.PunycodeCodec.PunycodeCodec.new","href":"punycoder/PunycodeCodec/PunycodeCodec.html","kind":2,"packageRank":0,"desc":"Creates a new instance of the Punycode codec.","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}},{"name":"decoder","qualifiedName":"punycoder.PunycodeCodec.decoder","href":"punycoder/PunycodeCodec/decoder.html","kind":16,"overriddenDepth":0,"packageRank":0,"desc":"Returns the decoder of this, converting from T to S.","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}},{"name":"encoder","qualifiedName":"punycoder.PunycodeCodec.encoder","href":"punycoder/PunycodeCodec/encoder.html","kind":16,"overriddenDepth":0,"packageRank":0,"desc":"Returns the encoder from S to T.","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}},{"name":"PunycodeDecoder","qualifiedName":"punycoder.PunycodeDecoder","href":"punycoder/PunycodeDecoder-class.html","kind":3,"packageRank":0,"desc":"Converts a Punycode string of ASCII-only symbols to a string of\nUnicode symbols.","enclosedBy":{"name":"punycoder","kind":9,"href":"punycoder/"}},{"name":"convert","qualifiedName":"punycoder.PunycodeDecoder.convert","href":"punycoder/PunycodeDecoder/convert.html","kind":10,"overriddenDepth":1,"packageRank":0,"desc":"Converts input and returns the result of the conversion.","enclosedBy":{"name":"PunycodeDecoder","kind":3,"href":"punycoder/PunycodeDecoder-class.html"}},{"name":"toUnicode","qualifiedName":"punycoder.PunycodeDecoder.toUnicode","href":"punycoder/PunycodeDecoder/toUnicode.html","kind":10,"overriddenDepth":0,"packageRank":0,"desc":"Converts a Punycode string representing a domain name or an email address\nto Unicode. Only the Punycoded parts of the input will be converted, i.e.\nit doesn't matter if you call it on a string that has already been\nconverted to Unicode.","enclosedBy":{"name":"PunycodeDecoder","kind":3,"href":"punycoder/PunycodeDecoder-class.html"}},{"name":"PunycodeEncoder","qualifiedName":"punycoder.PunycodeEncoder","href":"punycoder/PunycodeEncoder-class.html","kind":3,"packageRank":0,"desc":"Converts a string of Unicode symbols (e.g. a domain name label) to a\nPunycode string of ASCII-only symbols.","enclosedBy":{"name":"punycoder","kind":9,"href":"punycoder/"}},{"name":"convert","qualifiedName":"punycoder.PunycodeEncoder.convert","href":"punycoder/PunycodeEncoder/convert.html","kind":10,"overriddenDepth":1,"packageRank":0,"desc":"Converts input and returns the result of the conversion.","enclosedBy":{"name":"PunycodeEncoder","kind":3,"href":"punycoder/PunycodeEncoder-class.html"}},{"name":"toAscii","qualifiedName":"punycoder.PunycodeEncoder.toAscii","href":"punycoder/PunycodeEncoder/toAscii.html","kind":10,"overriddenDepth":0,"packageRank":0,"desc":"Converts a Unicode string representing a domain name or an email address\nto Punycode. Only the non-ASCII parts of the domain name will be\nconverted, i.e. it doesn't matter if you call it with a domain that's\nalready in ASCII","enclosedBy":{"name":"PunycodeEncoder","kind":3,"href":"punycoder/PunycodeEncoder-class.html"}},{"name":"punycodeDecoder","qualifiedName":"punycoder.punycodeDecoder","href":"punycoder/punycodeDecoder-constant.html","kind":19,"packageRank":0,"desc":"The canonical version of the Punycode Decoder","enclosedBy":{"name":"punycoder","kind":9,"href":"punycoder/"}},{"name":"punycodeEncoder","qualifiedName":"punycoder.punycodeEncoder","href":"punycoder/punycodeEncoder-constant.html","kind":19,"packageRank":0,"desc":"The canonical version of the Punycode Encoder","enclosedBy":{"name":"punycoder","kind":9,"href":"punycoder/"}}]
[{"name":"punycoder","qualifiedName":"punycoder","href":"punycoder/","kind":9,"packageRank":0,"desc":"Provides a Dart implementation of the Punycode encoding algorithm\nspecified in RFC 3492."},{"name":"PunycodeCodec","qualifiedName":"punycoder.PunycodeCodec","href":"punycoder/PunycodeCodec-class.html","kind":3,"packageRank":0,"desc":"A codec for encoding and decoding strings using the Punycode algorithm.","enclosedBy":{"name":"punycoder","kind":9,"href":"punycoder/"}},{"name":"PunycodeCodec.new","qualifiedName":"punycoder.PunycodeCodec.PunycodeCodec.new","href":"punycoder/PunycodeCodec/PunycodeCodec.html","kind":2,"packageRank":0,"desc":"Creates a new instance of the Punycode codec designed\nfor working with domains and email addresses where\nadditional rules apply about how it is converted","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}},{"name":"PunycodeCodec.simple","qualifiedName":"punycoder.PunycodeCodec.PunycodeCodec.simple","href":"punycoder/PunycodeCodec/PunycodeCodec.simple.html","kind":2,"packageRank":0,"desc":"Creates a new instance of the Punycode codec just designed\nfor working with simple strings","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}},{"name":"decoder","qualifiedName":"punycoder.PunycodeCodec.decoder","href":"punycoder/PunycodeCodec/decoder.html","kind":16,"overriddenDepth":0,"packageRank":0,"desc":"Returns the decoder of this, converting from T to S.","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}},{"name":"encoder","qualifiedName":"punycoder.PunycodeCodec.encoder","href":"punycoder/PunycodeCodec/encoder.html","kind":16,"overriddenDepth":0,"packageRank":0,"desc":"Returns the encoder from S to T.","enclosedBy":{"name":"PunycodeCodec","kind":3,"href":"punycoder/PunycodeCodec-class.html"}}]
1 change: 1 addition & 0 deletions doc/api/punycoder/PunycodeCodec-class-sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<li class="section-title"><a href="punycoder/PunycodeCodec-class.html#constructors">Constructors</a></li>
<li><a href="punycoder/PunycodeCodec/PunycodeCodec.html">new</a></li>
<li><a href="punycoder/PunycodeCodec/PunycodeCodec.simple.html">simple</a></li>



Expand Down
41 changes: 34 additions & 7 deletions doc/api/punycoder/PunycodeCodec-class.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,28 @@ <h1><span class="kind-class">PunycodeCodec</span> class
<pre class="language-dart"><code class="language-dart">import 'package:punycoder/punycoder.dart';

void main() {
const codec = PunycodeCodec();
final encoded = codec.encoder.convert('münchen');
print(encoded); // Output: mnchen-3ya
final decoded = codec.decoder.convert('mnchen-3ya');
print(decoded); // Output: münchen
// Designed to be used with domains and emails which have special rules
const domainCodec = PunycodeCodec();
// Designed to work with simple strings
const simpleCodec = PunycodeCodec.simple();

final encodedString = simpleCodec.encode('münchen');
final encodedDomain = domainCodec.encode('münchen.com');
final encodedEmail = domainCodec.encode('münchen@münchen.com');

print(encodedString); // Output: mnchen-3ya
// Uses the correct prefix for the domain
print(encodedDomain); // Output: xn--mnchen-3ya.com
// Only the domain should be encoded
print(encodedEmail); // Output: münchen@xn--mnchen-3ya.com

final decodedString = simpleCodec.decode('mnchen-3ya');
final decodecDomain = domainCodec.decode('xn--mnchen-3ya.com');
final decodedEmail = domainCodec.decode('münchen@xn--mnchen-3ya.com');

print(decodedString); // Output: münchen
print(decodecDomain); // Output: münchen.com
print(decodedEmail); // Output: münchen@münchen.com
}
</code></pre>
</section>
Expand Down Expand Up @@ -105,7 +122,17 @@ <h2>Constructors</h2>
<span class="name"><a href="../punycoder/PunycodeCodec/PunycodeCodec.html">PunycodeCodec.new</a></span><span class="signature">()</span>
</dt>
<dd>
Creates a new instance of the Punycode codec.
Creates a new instance of the Punycode codec designed
for working with domains and email addresses where
additional rules apply about how it is converted
<div class="constructor-modifier features">const</div>
</dd>
<dt id="PunycodeCodec.simple" class="callable">
<span class="name"><a href="../punycoder/PunycodeCodec/PunycodeCodec.simple.html">PunycodeCodec.simple</a></span><span class="signature">()</span>
</dt>
<dd>
Creates a new instance of the Punycode codec just designed
for working with simple strings
<div class="constructor-modifier features">const</div>
</dd>
</dl>
Expand Down Expand Up @@ -300,7 +327,7 @@ <h5>punycoder library</h5>
<footer>
<span class="no-break">
punycoder
0.1.0-dev
0.1.0
</span>

</footer>
Expand Down
10 changes: 7 additions & 3 deletions doc/api/punycoder/PunycodeCodec/PunycodeCodec.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@ <h1><span class="kind-constructor">PunycodeCodec</span> constructor
</section>

<section class="desc markdown">
<p>Creates a new instance of the Punycode codec.</p>
<p>Creates a new instance of the Punycode codec designed
for working with domains and email addresses where
additional rules apply about how it is converted</p>
</section>



<section class="summary source-code" id="source">
<h2><span>Implementation</span></h2>
<pre class="language-dart"><code class="language-dart">const PunycodeCodec();</code></pre>
<pre class="language-dart"><code class="language-dart">const PunycodeCodec()
: _encoder = const PunycodeEncoder(),
_decoder = const PunycodeDecoder();</code></pre>
</section>


Expand Down Expand Up @@ -93,7 +97,7 @@ <h5>PunycodeCodec class</h5>
<footer>
<span class="no-break">
punycoder
0.1.0-dev
0.1.0
</span>

</footer>
Expand Down
Loading