codext 1.16.0__tar.gz → 1.16.2__tar.gz

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.
Files changed (137) hide show
  1. {codext-1.16.0 → codext-1.16.2}/.github/workflows/python-package.yml +6 -1
  2. {codext-1.16.0 → codext-1.16.2}/PKG-INFO +76 -26
  3. {codext-1.16.0 → codext-1.16.2}/README.md +75 -25
  4. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/crypto.md +19 -0
  5. codext-1.16.2/src/codext/VERSION.txt +1 -0
  6. {codext-1.16.0 → codext-1.16.2}/src/codext/__common__.py +1 -1
  7. {codext-1.16.0 → codext-1.16.2}/src/codext/__init__.py +7 -7
  8. {codext-1.16.0 → codext-1.16.2}/src/codext/base/_base.py +6 -1
  9. {codext-1.16.0 → codext-1.16.2}/src/codext/base/base45.py +5 -2
  10. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/__init__.py +1 -0
  11. codext-1.16.2/src/codext/crypto/phillips.py +128 -0
  12. {codext-1.16.0 → codext-1.16.2}/src/codext.egg-info/PKG-INFO +76 -26
  13. {codext-1.16.0 → codext-1.16.2}/src/codext.egg-info/SOURCES.txt +1 -0
  14. {codext-1.16.0 → codext-1.16.2}/tests/test_base.py +22 -1
  15. codext-1.16.0/src/codext/VERSION.txt +0 -1
  16. {codext-1.16.0 → codext-1.16.2}/.coveragerc +0 -0
  17. {codext-1.16.0 → codext-1.16.2}/.github/ISSUE_TEMPLATE/add-encoding.yml +0 -0
  18. {codext-1.16.0 → codext-1.16.2}/.github/copilot-instructions.md +0 -0
  19. {codext-1.16.0 → codext-1.16.2}/.github/prompts/add_codec.prompt.md +0 -0
  20. {codext-1.16.0 → codext-1.16.2}/.github/pull_request_template.md +0 -0
  21. {codext-1.16.0 → codext-1.16.2}/.gitignore +0 -0
  22. {codext-1.16.0 → codext-1.16.2}/.readthedocs.yml +0 -0
  23. {codext-1.16.0 → codext-1.16.2}/LICENSE +0 -0
  24. {codext-1.16.0 → codext-1.16.2}/docs/coverage.svg +0 -0
  25. {codext-1.16.0 → codext-1.16.2}/docs/mkdocs.yml +0 -0
  26. {codext-1.16.0 → codext-1.16.2}/docs/pages/cli.md +0 -0
  27. {codext-1.16.0 → codext-1.16.2}/docs/pages/css/extra.css +0 -0
  28. {codext-1.16.0 → codext-1.16.2}/docs/pages/demos/using-bases.gif +0 -0
  29. {codext-1.16.0 → codext-1.16.2}/docs/pages/demos/using-codext.gif +0 -0
  30. {codext-1.16.0 → codext-1.16.2}/docs/pages/demos/using-debase.gif +0 -0
  31. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/base.md +0 -0
  32. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/binary.md +0 -0
  33. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/common.md +0 -0
  34. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/compressions.md +0 -0
  35. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/hashing.md +0 -0
  36. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/languages.md +0 -0
  37. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/others.md +0 -0
  38. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/stegano.md +0 -0
  39. {codext-1.16.0 → codext-1.16.2}/docs/pages/enc/web.md +0 -0
  40. {codext-1.16.0 → codext-1.16.2}/docs/pages/features.md +0 -0
  41. {codext-1.16.0 → codext-1.16.2}/docs/pages/guessing.md +0 -0
  42. {codext-1.16.0 → codext-1.16.2}/docs/pages/howto.md +0 -0
  43. {codext-1.16.0 → codext-1.16.2}/docs/pages/img/banner.png +0 -0
  44. {codext-1.16.0 → codext-1.16.2}/docs/pages/img/icon.png +0 -0
  45. {codext-1.16.0 → codext-1.16.2}/docs/pages/img/logo.png +0 -0
  46. {codext-1.16.0 → codext-1.16.2}/docs/pages/index.md +0 -0
  47. {codext-1.16.0 → codext-1.16.2}/docs/pages/manipulations.md +0 -0
  48. {codext-1.16.0 → codext-1.16.2}/docs/requirements.txt +0 -0
  49. {codext-1.16.0 → codext-1.16.2}/pyproject.toml +0 -0
  50. {codext-1.16.0 → codext-1.16.2}/pytest.ini +0 -0
  51. {codext-1.16.0 → codext-1.16.2}/requirements.txt +0 -0
  52. {codext-1.16.0 → codext-1.16.2}/setup.cfg +0 -0
  53. {codext-1.16.0 → codext-1.16.2}/src/codext/__info__.py +0 -0
  54. {codext-1.16.0 → codext-1.16.2}/src/codext/base/__init__.py +0 -0
  55. {codext-1.16.0 → codext-1.16.2}/src/codext/base/_base2n.py +0 -0
  56. {codext-1.16.0 → codext-1.16.2}/src/codext/base/base100.py +0 -0
  57. {codext-1.16.0 → codext-1.16.2}/src/codext/base/base122.py +0 -0
  58. {codext-1.16.0 → codext-1.16.2}/src/codext/base/base85.py +0 -0
  59. {codext-1.16.0 → codext-1.16.2}/src/codext/base/base91.py +0 -0
  60. {codext-1.16.0 → codext-1.16.2}/src/codext/base/baseN.py +0 -0
  61. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/__init__.py +0 -0
  62. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/baudot.py +0 -0
  63. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/bcd.py +0 -0
  64. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/excess3.py +0 -0
  65. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/gray.py +0 -0
  66. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/manchester.py +0 -0
  67. {codext-1.16.0 → codext-1.16.2}/src/codext/binary/rotate.py +0 -0
  68. {codext-1.16.0 → codext-1.16.2}/src/codext/checksums/__init__.py +0 -0
  69. {codext-1.16.0 → codext-1.16.2}/src/codext/checksums/adler.py +0 -0
  70. {codext-1.16.0 → codext-1.16.2}/src/codext/checksums/crc.py +0 -0
  71. {codext-1.16.0 → codext-1.16.2}/src/codext/checksums/luhn.py +0 -0
  72. {codext-1.16.0 → codext-1.16.2}/src/codext/common/__init__.py +0 -0
  73. {codext-1.16.0 → codext-1.16.2}/src/codext/common/a1z26.py +0 -0
  74. {codext-1.16.0 → codext-1.16.2}/src/codext/common/cases.py +0 -0
  75. {codext-1.16.0 → codext-1.16.2}/src/codext/common/dummy.py +0 -0
  76. {codext-1.16.0 → codext-1.16.2}/src/codext/common/octal.py +0 -0
  77. {codext-1.16.0 → codext-1.16.2}/src/codext/common/ordinal.py +0 -0
  78. {codext-1.16.0 → codext-1.16.2}/src/codext/compressions/__init__.py +0 -0
  79. {codext-1.16.0 → codext-1.16.2}/src/codext/compressions/gzipp.py +0 -0
  80. {codext-1.16.0 → codext-1.16.2}/src/codext/compressions/lz77.py +0 -0
  81. {codext-1.16.0 → codext-1.16.2}/src/codext/compressions/lz78.py +0 -0
  82. {codext-1.16.0 → codext-1.16.2}/src/codext/compressions/pkzip.py +0 -0
  83. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/affine.py +0 -0
  84. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/atbash.py +0 -0
  85. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/bacon.py +0 -0
  86. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/barbie.py +0 -0
  87. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/citrix.py +0 -0
  88. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/polybius.py +0 -0
  89. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/railfence.py +0 -0
  90. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/rot.py +0 -0
  91. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/scytale.py +0 -0
  92. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/shift.py +0 -0
  93. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/vigenere.py +0 -0
  94. {codext-1.16.0 → codext-1.16.2}/src/codext/crypto/xor.py +0 -0
  95. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/__init__.py +0 -0
  96. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/blake.py +0 -0
  97. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/crypt.py +0 -0
  98. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/md.py +0 -0
  99. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/mmh3.py +0 -0
  100. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/sha.py +0 -0
  101. {codext-1.16.0 → codext-1.16.2}/src/codext/hashing/shake.py +0 -0
  102. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/__init__.py +0 -0
  103. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/braille.py +0 -0
  104. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/galactic.py +0 -0
  105. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/ipsum.py +0 -0
  106. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/leetspeak.py +0 -0
  107. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/morse.py +0 -0
  108. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/navajo.py +0 -0
  109. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/radio.py +0 -0
  110. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/southpark.py +0 -0
  111. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/tap.py +0 -0
  112. {codext-1.16.0 → codext-1.16.2}/src/codext/languages/tomtom.py +0 -0
  113. {codext-1.16.0 → codext-1.16.2}/src/codext/macros.json +0 -0
  114. {codext-1.16.0 → codext-1.16.2}/src/codext/others/__init__.py +0 -0
  115. {codext-1.16.0 → codext-1.16.2}/src/codext/others/dna.py +0 -0
  116. {codext-1.16.0 → codext-1.16.2}/src/codext/others/kbshift.py +0 -0
  117. {codext-1.16.0 → codext-1.16.2}/src/codext/others/letters.py +0 -0
  118. {codext-1.16.0 → codext-1.16.2}/src/codext/others/markdown.py +0 -0
  119. {codext-1.16.0 → codext-1.16.2}/src/codext/others/uuencode.py +0 -0
  120. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/__init__.py +0 -0
  121. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/hexagram.py +0 -0
  122. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/klopf.py +0 -0
  123. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/resistor.py +0 -0
  124. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/rick.py +0 -0
  125. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/sms.py +0 -0
  126. {codext-1.16.0 → codext-1.16.2}/src/codext/stegano/whitespace.py +0 -0
  127. {codext-1.16.0 → codext-1.16.2}/src/codext/web/__init__.py +0 -0
  128. {codext-1.16.0 → codext-1.16.2}/src/codext/web/html.py +0 -0
  129. {codext-1.16.0 → codext-1.16.2}/src/codext/web/url.py +0 -0
  130. {codext-1.16.0 → codext-1.16.2}/src/codext.egg-info/dependency_links.txt +0 -0
  131. {codext-1.16.0 → codext-1.16.2}/src/codext.egg-info/entry_points.txt +0 -0
  132. {codext-1.16.0 → codext-1.16.2}/src/codext.egg-info/requires.txt +0 -0
  133. {codext-1.16.0 → codext-1.16.2}/src/codext.egg-info/top_level.txt +0 -0
  134. {codext-1.16.0 → codext-1.16.2}/tests/__init__.py +0 -0
  135. {codext-1.16.0 → codext-1.16.2}/tests/test_common.py +0 -0
  136. {codext-1.16.0 → codext-1.16.2}/tests/test_generated.py +0 -0
  137. {codext-1.16.0 → codext-1.16.2}/tests/test_manual.py +0 -0
@@ -18,12 +18,14 @@ jobs:
18
18
  runs-on: ubuntu-latest
19
19
  outputs:
20
20
  package: ${{ steps.pkg.outputs.package }}
21
+ pypi_url: ${{ steps.pkg.outputs.pypi_url }}
21
22
  steps:
22
23
  - name: Compute package name from the repository's
23
24
  id: pkg
24
25
  run: |
25
26
  name="${GITHUB_REPOSITORY##*/}"
26
27
  echo "package=${name#python-}" >> $GITHUB_OUTPUT
28
+ echo "pypi_url=https://pypi.org/p/${name#python-}" >> $GITHUB_OUTPUT
27
29
  build:
28
30
  needs: prepare
29
31
  runs-on: ${{ matrix.os }}
@@ -63,7 +65,7 @@ jobs:
63
65
  - uses: actions/checkout@v5
64
66
  with:
65
67
  fetch-depth: 0
66
- ref: ${{ github.head_ref || github.ref_name }}
68
+ ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.sha }}
67
69
  - name: Set up Python ${{ env.python_version }}
68
70
  uses: actions/setup-python@v6
69
71
  with:
@@ -96,6 +98,9 @@ jobs:
96
98
  git commit -m "Update coverage badge"
97
99
  git push origin coverage-badge --force
98
100
  deploy:
101
+ environment:
102
+ name: pypi
103
+ url: ${{ needs.prepare.outputs.pypi_url }}
99
104
  runs-on: ubuntu-latest
100
105
  needs: [prepare, coverage]
101
106
  steps:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codext
3
- Version: 1.16.0
3
+ Version: 1.16.2
4
4
  Summary: Native codecs extension
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -779,6 +779,8 @@ example-macro
779
779
 
780
780
  ## //img.shields.io/badge/Tweet%20(unbase)--lightgrey?logo=twitter&style=social" alt="Tweet on unbase" height="20"/></a>
781
781
 
782
+ Playing with base encodings.
783
+
782
784
  ```session
783
785
  $ echo "Test string !" | base122
784
786
  *.7!ft9�-f9Â
@@ -804,9 +806,63 @@ $ echo "Test string !" | base91 | base85 | base36 | base58-flickr | unbase -f Te
804
806
  Test string !
805
807
  ```
806
808
 
809
+ ## Usage (CLI)
810
+
811
+ Listing codecs.
812
+
813
+ ```session
814
+ $ codext list encodings
815
+ a1z26 adler32 affine alternative-rot ascii
816
+ atbash autoclave bacon barbie base
817
+ base1 base2 base3 base4 base8
818
+ <<snipped>>
819
+ ```
820
+
821
+ Finding a codec based on a name.
822
+
823
+ ```session
824
+ $ codext search bitcoin
825
+ base58
826
+ ```
827
+
828
+ Encoding a string.
829
+
830
+ ```sesssion
831
+ $ echo -en "This is a test" | codext encode polybius
832
+ 44232443 2443 11 44154344
833
+ ```
834
+
835
+ Encoding a file.
836
+
837
+ ```session
838
+ $ echo -en "this is a test" > to_be_encoded.txt
839
+ $ codext encode base64 < to_be_encoded.txt > text.b64
840
+ $ cat text.b64
841
+ dGhpcyBpcyBhIHRlc3Q=
842
+ ```
843
+
844
+ Chaining codecs.
845
+
846
+ ```session
847
+ $ echo -en "mrdvm6teie6t2cq=" | codext encode upper | codext decode base32 | codext decode base64
848
+ test
849
+ ```
850
+
851
+ Iteratively guessing decodings.
852
+
853
+ ```session
854
+ $ echo -en "test" | codext encode base64 gzip | codext guess
855
+ Codecs: gzip
856
+ dGVzdA==
857
+ $ echo -en "test" | codext encode base64 gzip | codext guess gzip -i base
858
+ Codecs: gzip, base64
859
+ test
860
+ ```
861
+
862
+
807
863
  ## Usage (Python)
808
864
 
809
- Getting the list of available codecs:
865
+ Getting the list of available codecs.
810
866
 
811
867
  ```python
812
868
  >>> import codext
@@ -814,6 +870,9 @@ Getting the list of available codecs:
814
870
  >>> codext.list()
815
871
  ['ascii85', 'base85', 'base100', 'base122', ..., 'tomtom', 'dna', 'html', 'markdown', 'url', 'resistor', 'sms', 'whitespace', 'whitespace-after-before']
816
872
 
873
+ Playing with some base encodings.
874
+
875
+ ```python
817
876
  >>> codext.encode("this is a test", "base58-bitcoin")
818
877
  'jo91waLQA1NNeBmZKUF'
819
878
 
@@ -828,7 +887,21 @@ Getting the list of available codecs:
828
887
 
829
888
  >>> codecs.decode("👫👟👠👪🐗👠👪🐗👘🐗👫👜👪👫", "base100")
830
889
  'this is a test'
890
+ ```
891
+
892
+ Playing with some cryptography-based codecs.
831
893
 
894
+ ```python
895
+ >>> codext.encode("This is a test !", "vigenere-MYSECRETKET")
896
+ 'Ffaw kj e mowm !'
897
+
898
+ >>> codext.encode("This is a test !", "autoclave-SECRET")
899
+ 'Llkj ml t amkb !'
900
+ ```
901
+
902
+ Encoding/decoding with various other codecs.
903
+
904
+ ```python
832
905
  >>> for i in range(8):
833
906
  print(codext.encode("this is a test", "dna-%d" % (i + 1)))
834
907
  GTGAGCCAGCCGGTATACAAGCCGGTATACAAGCAGACAAGTGAGCGGGTATGTGA
@@ -856,30 +929,6 @@ CACTCGGTCGGCCATATGTTCGGCCATATGTTCGTCTGTTCACTCGCCCATACACT
856
929
  f.read()
857
930
  'this is a test'
858
931
 
859
- >>> codext.decode("""
860
- =
861
- X
862
- :
863
- x
864
- n
865
- r
866
- y
867
- Y
868
- y
869
- p
870
- a
871
- `
872
- n
873
- |
874
- a
875
- o
876
- h
877
- `
878
- g
879
- o
880
- z """, "whitespace-after+before")
881
- 'CSC{not_so_invisible}'
882
-
883
932
  >>> print(codext.encode("An example test string", "baudot-tape"))
884
933
  ***.**
885
934
  . *
@@ -985,6 +1034,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
985
1034
  - [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
986
1035
  - [X] `beaufort`: aka Beaufort Cipher (variant of Vigenere Cipher)
987
1036
  - [X] `citrix`: aka Citrix CTX1 password encoding
1037
+ - [X] `phillips`: aka Phillips Cipher (polyalphabetic block cipher with 8 key squares)
988
1038
  - [X] `polybius`: aka Polybius Square Cipher
989
1039
  - [X] `railfence`: aka Rail Fence Cipher
990
1040
  - [X] `rotN`: aka Caesar cipher (*N* belongs to [1,25])
@@ -81,6 +81,8 @@ example-macro
81
81
 
82
82
  ## //img.shields.io/badge/Tweet%20(unbase)--lightgrey?logo=twitter&style=social" alt="Tweet on unbase" height="20"/></a>
83
83
 
84
+ Playing with base encodings.
85
+
84
86
  ```session
85
87
  $ echo "Test string !" | base122
86
88
  *.7!ft9�-f9Â
@@ -106,9 +108,63 @@ $ echo "Test string !" | base91 | base85 | base36 | base58-flickr | unbase -f Te
106
108
  Test string !
107
109
  ```
108
110
 
111
+ ## Usage (CLI)
112
+
113
+ Listing codecs.
114
+
115
+ ```session
116
+ $ codext list encodings
117
+ a1z26 adler32 affine alternative-rot ascii
118
+ atbash autoclave bacon barbie base
119
+ base1 base2 base3 base4 base8
120
+ <<snipped>>
121
+ ```
122
+
123
+ Finding a codec based on a name.
124
+
125
+ ```session
126
+ $ codext search bitcoin
127
+ base58
128
+ ```
129
+
130
+ Encoding a string.
131
+
132
+ ```sesssion
133
+ $ echo -en "This is a test" | codext encode polybius
134
+ 44232443 2443 11 44154344
135
+ ```
136
+
137
+ Encoding a file.
138
+
139
+ ```session
140
+ $ echo -en "this is a test" > to_be_encoded.txt
141
+ $ codext encode base64 < to_be_encoded.txt > text.b64
142
+ $ cat text.b64
143
+ dGhpcyBpcyBhIHRlc3Q=
144
+ ```
145
+
146
+ Chaining codecs.
147
+
148
+ ```session
149
+ $ echo -en "mrdvm6teie6t2cq=" | codext encode upper | codext decode base32 | codext decode base64
150
+ test
151
+ ```
152
+
153
+ Iteratively guessing decodings.
154
+
155
+ ```session
156
+ $ echo -en "test" | codext encode base64 gzip | codext guess
157
+ Codecs: gzip
158
+ dGVzdA==
159
+ $ echo -en "test" | codext encode base64 gzip | codext guess gzip -i base
160
+ Codecs: gzip, base64
161
+ test
162
+ ```
163
+
164
+
109
165
  ## Usage (Python)
110
166
 
111
- Getting the list of available codecs:
167
+ Getting the list of available codecs.
112
168
 
113
169
  ```python
114
170
  >>> import codext
@@ -116,6 +172,9 @@ Getting the list of available codecs:
116
172
  >>> codext.list()
117
173
  ['ascii85', 'base85', 'base100', 'base122', ..., 'tomtom', 'dna', 'html', 'markdown', 'url', 'resistor', 'sms', 'whitespace', 'whitespace-after-before']
118
174
 
175
+ Playing with some base encodings.
176
+
177
+ ```python
119
178
  >>> codext.encode("this is a test", "base58-bitcoin")
120
179
  'jo91waLQA1NNeBmZKUF'
121
180
 
@@ -130,7 +189,21 @@ Getting the list of available codecs:
130
189
 
131
190
  >>> codecs.decode("👫👟👠👪🐗👠👪🐗👘🐗👫👜👪👫", "base100")
132
191
  'this is a test'
192
+ ```
193
+
194
+ Playing with some cryptography-based codecs.
133
195
 
196
+ ```python
197
+ >>> codext.encode("This is a test !", "vigenere-MYSECRETKET")
198
+ 'Ffaw kj e mowm !'
199
+
200
+ >>> codext.encode("This is a test !", "autoclave-SECRET")
201
+ 'Llkj ml t amkb !'
202
+ ```
203
+
204
+ Encoding/decoding with various other codecs.
205
+
206
+ ```python
134
207
  >>> for i in range(8):
135
208
  print(codext.encode("this is a test", "dna-%d" % (i + 1)))
136
209
  GTGAGCCAGCCGGTATACAAGCCGGTATACAAGCAGACAAGTGAGCGGGTATGTGA
@@ -158,30 +231,6 @@ CACTCGGTCGGCCATATGTTCGGCCATATGTTCGTCTGTTCACTCGCCCATACACT
158
231
  f.read()
159
232
  'this is a test'
160
233
 
161
- >>> codext.decode("""
162
- =
163
- X
164
- :
165
- x
166
- n
167
- r
168
- y
169
- Y
170
- y
171
- p
172
- a
173
- `
174
- n
175
- |
176
- a
177
- o
178
- h
179
- `
180
- g
181
- o
182
- z """, "whitespace-after+before")
183
- 'CSC{not_so_invisible}'
184
-
185
234
  >>> print(codext.encode("An example test string", "baudot-tape"))
186
235
  ***.**
187
236
  . *
@@ -287,6 +336,7 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
287
336
  - [X] `barbie-N`: aka Barbie Typewriter (*N* belongs to [1, 4])
288
337
  - [X] `beaufort`: aka Beaufort Cipher (variant of Vigenere Cipher)
289
338
  - [X] `citrix`: aka Citrix CTX1 password encoding
339
+ - [X] `phillips`: aka Phillips Cipher (polyalphabetic block cipher with 8 key squares)
290
340
  - [X] `polybius`: aka Polybius Square Cipher
291
341
  - [X] `railfence`: aka Rail Fence Cipher
292
342
  - [X] `rotN`: aka Caesar cipher (*N* belongs to [1,25])
@@ -162,6 +162,25 @@ This implements the Citrix CTX1 password encoding algorithm.
162
162
 
163
163
  -----
164
164
 
165
+ ### Phillips Cipher
166
+
167
+ This implements Phillips cipher, a polyalphabetic code using 8 grids generated with one keyword.
168
+
169
+ **Codec** | **Conversions** | **Aliases** | **Comment**
170
+ :---: | :---: | --- | ---
171
+ `phillips` | text <-> phillips ciphertext | `phillips-key`, `phillips_password`, ... |
172
+
173
+ ```python
174
+ >>> codext.encode("this is a test", "phillips_mysuperkey")
175
+ 'ZCNM NM E XKMVZ'
176
+ >>> codext.encode("Another Test String", "phillips-PaSsWoRd")
177
+ 'SMEZKBE LBON OLEHQHV'
178
+ >>> codext.decode("SMEZKBE LBON OLEHQHV", "phillips-password")
179
+ 'ANOTHER TEST STRINGX'
180
+ ```
181
+
182
+ -----
183
+
165
184
  ### Polybius Square Cipher
166
185
 
167
186
  This implements the well-known Polybius Square cipher, using the square with the alphabet in normal order as the default. It can be used dynamically with a custom alphabet.
@@ -0,0 +1 @@
1
+ 1.16.2
@@ -671,7 +671,7 @@ def list_categories():
671
671
  c.append(d.rstrip("s"))
672
672
  # particular category, hardcoded from base/_base.py
673
673
  c += ["base-generic"]
674
- return c
674
+ return list(set(c))
675
675
  list_categories()
676
676
 
677
677
 
@@ -170,7 +170,7 @@ def main():
170
170
  listi = sparsers.add_parser("list", help="list items")
171
171
  lsparsers = listi.add_subparsers(dest="type", help="type of item to be listed", required=True)
172
172
  liste = lsparsers.add_parser("encodings", help="list encodings")
173
- liste.add_argument("category", nargs="+", help="selected categories")
173
+ liste.add_argument("category", nargs="*", help="selected categories")
174
174
  listm = lsparsers.add_parser("macros", help="list macros")
175
175
  addm = sparsers.add_parser("add-macro", help="add a macro to the registry")
176
176
  addm.add_argument("name", help="macro's name")
@@ -198,13 +198,13 @@ def main():
198
198
  # list encodings or macros
199
199
  elif args.command == "list":
200
200
  if args.type == "encodings":
201
- cats = args.category or list_categories()
202
- for c in sorted(cats):
203
- l = list_encodings(c)
204
- if len(l) > 0:
205
- if len(cats) > 0:
201
+ if args.category:
202
+ for c in sorted(args.category):
203
+ if len(l := list_encodings(c)) > 0:
206
204
  print(c.upper() + ":")
207
- __print_tabular(l)
205
+ __print_tabular(l)
206
+ else:
207
+ __print_tabular(list_encodings())
208
208
  elif args.type == "macros":
209
209
  l = list_macros()
210
210
  if len(l) > 0:
@@ -130,6 +130,10 @@ def base_encode(input, charset, errors="strict", exc=BaseEncodeError):
130
130
  while i > 0:
131
131
  i, c = divmod(i, n)
132
132
  r = charset[c] + r
133
+ # preserve leading zero bytes: big-integer bases such as Base58 map each
134
+ # leading null byte of the input to a leading charset[0] character
135
+ if not isinstance(input, int):
136
+ r = charset[0] * (len(input) - len(input.lstrip("\x00"))) + r
133
137
  return r
134
138
 
135
139
 
@@ -151,7 +155,8 @@ def base_decode(input, charset, errors="strict", exc=BaseDecodeError):
151
155
  i = i * n + charset.index(c)
152
156
  except ValueError:
153
157
  handle_error("base", errors, exc, decode=True)(c, k, dec(i), "base%d" % n)
154
- return dec(i)
158
+ # restore the leading zero bytes encoded as leading charset[0] characters
159
+ return chr(0) * (len(input) - len(input.lstrip(charset[0]))) + dec(i)
155
160
 
156
161
 
157
162
  # base codec factory functions
@@ -34,7 +34,10 @@ def base45_encode(mode):
34
34
  b45 = _get_charset(B45, mode)
35
35
  def encode(text, errors="strict"):
36
36
  t, s = b(text), ""
37
- for i in range(0, len(text), 2):
37
+ # iterate over the byte sequence (t), not len(text): when the input
38
+ # holds non-ASCII characters, b(text) is longer than text and using
39
+ # len(text) silently drops the trailing bytes
40
+ for i in range(0, len(t), 2):
38
41
  n = 256 * __ord(t[i])
39
42
  try:
40
43
  n += __ord(t[i+1])
@@ -54,7 +57,7 @@ def base45_decode(mode):
54
57
  def decode(text, errors="strict"):
55
58
  t, s = b(text), ""
56
59
  ehandler = handle_error("base45", errors, decode=True)
57
- for i in range(0, len(text), 3):
60
+ for i in range(0, len(t), 3):
58
61
  try:
59
62
  n = b45[__chr(t[i])]
60
63
  except KeyError:
@@ -4,6 +4,7 @@ from .atbash import *
4
4
  from .bacon import *
5
5
  from .barbie import *
6
6
  from .citrix import *
7
+ from .phillips import *
7
8
  from .polybius import *
8
9
  from .railfence import *
9
10
  from .rot import *
@@ -0,0 +1,128 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """Phillips Cipher Codec - phillips content encoding.
3
+
4
+ The Phillips cipher is a polyalphabetic substitution cipher using 8 key
5
+ squares. The first square is a 5×5 grid built from a keyword (I and J share
6
+ one cell). Seven additional squares are derived by rotating every row of the
7
+ previous square one step to the left. Plaintext is enciphered in bigrams,
8
+ each pair using the next square in a cycle of 8. Non-alphabetic characters
9
+ are passed through unchanged; J is treated as I.
10
+
11
+ This codec:
12
+ - en/decodes strings from str to str
13
+ - en/decodes strings from bytes to bytes
14
+ - decodes file content to str (read)
15
+ - encodes file content from str to bytes (write)
16
+
17
+ Reference: https://www.dcode.fr/phillips-cipher
18
+ """
19
+ from ..__common__ import *
20
+
21
+
22
+ __examples__ = {
23
+ 'enc(phillips)': None,
24
+ 'enc(phillips-key)': {'ATTACK': 'BSSBIC', 'TESTME': 'QBTPLY', 'ABCDEF': 'BKDFYD'},
25
+ 'enc-dec(phillips-key)': ['ATTACK', 'TESTME', 'ABCDEF'],
26
+ 'enc-dec(phillips-secret)': ['HELLOWORLD', 'ATTACKATDAWN'],
27
+ }
28
+ __guess__ = ["phillips-key", "phillips-secret", "phillips-password"]
29
+
30
+
31
+ _ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
32
+
33
+
34
+ def __make_grids(key):
35
+ """Return all 8 grids: the initial grid plus 7 row-rotated variants."""
36
+ # build the initial 5×5 grid from a keyword (J treated as I)
37
+ seen, letters = set(), []
38
+ for c in key.upper().replace("J", "I") + _ALPHABET:
39
+ if c in set(_ALPHABET) and c not in seen:
40
+ letters.append(c)
41
+ seen.add(c)
42
+ grid = [letters[i * 5:(i + 1) * 5] for i in range(5)]
43
+ # now build the other 7 row-rotated variant grids
44
+ grids = [grid]
45
+ for _ in range(7):
46
+ grid = [row[1:] + [row[0]] for row in grid]
47
+ grids.append(grid)
48
+ return grids
49
+
50
+
51
+ def __process_pair(a, b, grid, decode=False):
52
+ """Encode or decode a letter pair using Playfair substitution rules.
53
+
54
+ Same row → each letter shifts one step right (encode) / left (decode).
55
+ Same col → each letter shifts one step down (encode) / up (decode).
56
+ Rectangle → each letter moves to the other's column (self-inverse).
57
+ """
58
+ pos = {ch: (r, c) for r, row in enumerate(grid) for c, ch in enumerate(row)}
59
+ r1, c1 = pos[a]
60
+ r2, c2 = pos[b]
61
+ d = -1 if decode else 1
62
+ if r1 == r2:
63
+ return grid[r1][(c1 + d) % 5], grid[r2][(c2 + d) % 5]
64
+ if c1 == c2:
65
+ return grid[(r1 + d) % 5][c1], grid[(r2 + d) % 5][c2]
66
+ return grid[r1][c2], grid[r2][c1] # rectangle rule is its own inverse
67
+
68
+
69
+ def phillips_encode(key):
70
+ _key = (key or "").strip()
71
+ # Compute grids eagerly if key is valid; otherwise defer error to call time
72
+ _grids = __make_grids(_key) if _key and _key.isalpha() else None
73
+ def encode(text, errors="strict"):
74
+ if _grids is None:
75
+ raise LookupError("Bad parameter for encoding 'phillips': "
76
+ "key must be a non-empty alphabetic string")
77
+ t = ensure_str(text).upper().replace("J", "I")
78
+ alpha = [(i, c) for i, c in enumerate(t) if c in set(_ALPHABET)]
79
+ # Pad to an even count with a trailing X
80
+ padding_char = None
81
+ if len(alpha) % 2 == 1:
82
+ alpha.append((-1, "X"))
83
+ enc_map = {}
84
+ for pair_num, k in enumerate(range(0, len(alpha), 2)):
85
+ pos1, a = alpha[k]
86
+ pos2, b = alpha[k + 1]
87
+ e1, e2 = __process_pair(a, b, _grids[pair_num % 8])
88
+ enc_map[pos1] = e1
89
+ if pos2 >= 0:
90
+ enc_map[pos2] = e2
91
+ else:
92
+ padding_char = e2
93
+ result = [enc_map.get(i, c) for i, c in enumerate(t)]
94
+ if padding_char is not None:
95
+ result.append(padding_char)
96
+ return "".join(result), len(text)
97
+ return encode
98
+
99
+
100
+ def phillips_decode(key):
101
+ _key = (key or "").strip()
102
+ # Compute grids eagerly if key is valid; otherwise defer error to call time
103
+ _grids = __make_grids(_key) if _key and _key.isalpha() else None
104
+ def decode(text, errors="strict"):
105
+ if _grids is None:
106
+ raise LookupError("Bad parameter for decoding 'phillips': "
107
+ "key must be a non-empty alphabetic string")
108
+ t = ensure_str(text).upper().replace("J", "I")
109
+ alpha = [(i, c) for i, c in enumerate(t) if c in set(_ALPHABET)]
110
+ if len(alpha) % 2 == 1:
111
+ if errors == "strict":
112
+ raise ValueError("phillips: encoded text must contain an even "
113
+ "number of alphabetic characters")
114
+ alpha = alpha[:-1]
115
+ dec_map = {}
116
+ for pair_num, k in enumerate(range(0, len(alpha), 2)):
117
+ pos1, a = alpha[k]
118
+ pos2, b = alpha[k + 1]
119
+ d1, d2 = __process_pair(a, b, _grids[pair_num % 8], decode=True)
120
+ dec_map[pos1] = d1
121
+ dec_map[pos2] = d2
122
+ return "".join(dec_map.get(i, c) for i, c in enumerate(t)), len(text)
123
+ return decode
124
+
125
+
126
+ add("phillips", phillips_encode, phillips_decode, r"^phillips(?:[-_]cipher)?(?:[-_]([a-zA-Z]+))?$", printables_rate=1.,
127
+ penalty=.1)
128
+