trackler 2.0.8.54 → 2.0.8.55

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/common/CONTRIBUTING.md +7 -6
  4. data/fixtures/tracks/animal/config.json +1 -0
  5. data/fixtures/tracks/animal/exercises/dog/a_test_example_for.animal +1 -0
  6. data/lib/trackler/implementation.rb +32 -38
  7. data/lib/trackler/implementations.rb +2 -2
  8. data/lib/trackler/track.rb +24 -4
  9. data/lib/trackler/version.rb +1 -1
  10. data/tracks/elixir/config.json +17 -0
  11. data/tracks/elixir/exercises/bracket-push/bracket_push_test.exs +34 -19
  12. data/tracks/elixir/exercises/saddle-points/example.exs +1 -1
  13. data/tracks/elixir/exercises/saddle-points/saddle_points.exs +1 -1
  14. data/tracks/elixir/exercises/saddle-points/saddle_points_test.exs +10 -10
  15. data/tracks/elixir/exercises/simple-cipher/example.exs +80 -0
  16. data/tracks/elixir/exercises/simple-cipher/simple_cipher.exs +49 -0
  17. data/tracks/elixir/exercises/simple-cipher/simple_cipher_test.exs +89 -0
  18. data/tracks/elixir/exercises/tournament/HINTS.md +3 -0
  19. data/tracks/elixir/exercises/tournament/example.exs +81 -0
  20. data/tracks/elixir/exercises/tournament/tournament.exs +17 -0
  21. data/tracks/elixir/exercises/tournament/tournament_test.exs +96 -0
  22. data/tracks/go/exercises/bob/bob.go +2 -2
  23. data/tracks/go/exercises/clock/clock.go +1 -8
  24. data/tracks/go/exercises/gigasecond/gigasecond.go +4 -6
  25. data/tracks/go/exercises/hamming/hamming.go +0 -1
  26. data/tracks/go/exercises/raindrops/raindrops.go +2 -1
  27. data/tracks/go/exercises/triangle/triangle.go +1 -2
  28. data/tracks/java/bin/journey-test.sh +6 -5
  29. data/tracks/java/exercises/nth-prime/src/example/java/PrimeCalculator.java +1 -5
  30. data/tracks/java/exercises/rna-transcription/src/test/java/RnaTranscriptionTest.java +5 -5
  31. data/tracks/pony/.github/stale.yml +0 -0
  32. data/tracks/pony/.travis.yml +2 -9
  33. data/tracks/pony/bin/install-deps +15 -8
  34. data/tracks/pony/bin/test-exercises +2 -0
  35. data/tracks/pony/config.json +2 -1
  36. data/tracks/pony/exercises/anagram/example.pony +1 -1
  37. data/tracks/pony/exercises/anagram/test.pony +2 -2
  38. data/tracks/pony/exercises/atbash-cipher/example.pony +6 -4
  39. data/tracks/pony/exercises/atbash-cipher/test.pony +24 -38
  40. data/tracks/pony/exercises/pascals-triangle/test.pony +3 -3
  41. data/tracks/pony/exercises/rna-transcription/test.pony +4 -4
  42. data/tracks/pony/exercises/roman-numerals/example.pony +12 -12
  43. data/tracks/pony/exercises/roman-numerals/test.pony +18 -18
  44. data/tracks/purescript/config.json +15 -0
  45. data/tracks/purescript/exercises/etl/bower.json +17 -0
  46. data/tracks/purescript/exercises/etl/examples/src/Etl.purs +20 -0
  47. data/tracks/purescript/exercises/etl/src/Etl.purs +3 -0
  48. data/tracks/purescript/exercises/etl/test/Main.purs +99 -0
  49. data/tracks/purescript/exercises/isogram/bower.json +18 -0
  50. data/tracks/purescript/exercises/isogram/examples/src/Isogram.purs +22 -0
  51. data/tracks/purescript/exercises/isogram/src/Isogram.purs +4 -0
  52. data/tracks/purescript/exercises/isogram/test/Main.purs +44 -0
  53. data/tracks/ruby/README.md +10 -9
  54. data/tracks/ruby/exercises/anagram/example.tt +1 -1
  55. data/tracks/ruby/exercises/bowling/example.tt +1 -1
  56. data/tracks/ruby/exercises/hamming/example.tt +2 -2
  57. data/tracks/ruby/exercises/luhn/.meta/.version +1 -0
  58. data/tracks/ruby/exercises/luhn/example.rb +18 -23
  59. data/tracks/ruby/exercises/luhn/example.tt +18 -0
  60. data/tracks/ruby/exercises/luhn/luhn_test.rb +60 -27
  61. data/tracks/ruby/exercises/pig-latin/.meta/.version +1 -0
  62. data/tracks/ruby/exercises/pig-latin/example.rb +4 -0
  63. data/tracks/ruby/exercises/pig-latin/example.tt +21 -0
  64. data/tracks/ruby/exercises/pig-latin/pig_latin_test.rb +73 -24
  65. data/tracks/ruby/exercises/rna-transcription/example.tt +1 -1
  66. data/tracks/ruby/lib/anagram_cases.rb +1 -1
  67. data/tracks/ruby/lib/binary_cases.rb +3 -3
  68. data/tracks/ruby/lib/bowling_cases.rb +1 -1
  69. data/tracks/ruby/lib/hamming_cases.rb +1 -1
  70. data/tracks/ruby/lib/luhn_cases.rb +27 -0
  71. data/tracks/ruby/lib/pig_latin_cases.rb +20 -0
  72. data/tracks/ruby/lib/rna_transcription_cases.rb +1 -1
  73. metadata +25 -3
  74. data/tracks/go/.github/stale.yml +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a6d856796589e6ec9283fdeea16822bb1d5b28c
4
- data.tar.gz: 05deca31a3c2e047dfd71ea9394515cc53d0e29c
3
+ metadata.gz: 350291aab0beae040bfec2a9a85f1e4faceac732
4
+ data.tar.gz: 00ecf8962d3f79bfff2872e5909644b808f80b8d
5
5
  SHA512:
6
- metadata.gz: 7ad433ebb8407e7287226b652fbcc81a83ef4991e19516f4548437e8ee7e84d3c12653c77969e30f30591ca750df623441f0f8706bcc8d1c2b6631ca7b3f41c8
7
- data.tar.gz: 995f196fbf184a1358774f0d2a817255afb271bd81a880770a4ae072abc830bf3b6cbc7add90deafe62e6d2efb80b5e44fc79a4d4775bbf3bbd14266e8065129
6
+ metadata.gz: b3ab8fa14033accfc8470e239b3319b0b62f5d0d7139887792cd4c6f0733c89c8493200ae93b2be108e4b9853a7e9be526f8bfcdd76cb93fd55fb7dd5d4f0810
7
+ data.tar.gz: b03ed5cb2599463588b8109f49a471fa744cd6447010d287c92f486b0b498419c067bb1599c7e66d503e04626e69e1c5e2cab83225740a97f11410b67ddcff04
data/README.md CHANGED
@@ -93,6 +93,13 @@ gem build trackler.gemspec
93
93
  gem install --local trackler-$VERSION.gem
94
94
  gem push trackler-$VERSION.gem
95
95
  ```
96
+ ## Users
97
+
98
+ Projects that make use of this gem:
99
+
100
+ * https://github.com/exercism/exercism.io
101
+ * https://github.com/exercism/x-api
102
+ * https://github.com/sandimetz/99bottles-polyglot
96
103
 
97
104
  ## License
98
105
 
@@ -41,7 +41,7 @@ themselves. There are other guides about contributing to other parts of the Exer
41
41
  * [Pull Request Guidelines](#pull-request-guidelines)
42
42
  * [Exercise Versioning](#exercise-versioning)
43
43
  * [Anatomy of an Exercise](#anatomy-of-an-exercise)
44
- * [config.json](#config-json)
44
+ * [Track configuration file (config.json)](#track-configuration-file)
45
45
  * [Track-Level Linting With Configlet](#track-level-linting-with-configlet)
46
46
  * [Git Basics](#git-basics)
47
47
  * [Getting the Code](#getting-the-code)
@@ -668,10 +668,11 @@ that the exercise is coherent.
668
668
  If you change the test suite, then make sure the reference solution is fixed
669
669
  to pass the updated tests.
670
670
 
671
- ### config.json
671
+ ### Track configuration file
672
672
 
673
- Each language track has a `config.json` file. Important keys are:
673
+ Each language track has a configuration file called `config.json`.
674
674
 
675
+ Important keys are:
675
676
  * `problems` - actively served via `exercism fetch`
676
677
  * `deprecated` - implemented, but aren't served anymore
677
678
  * `foregone` - will not be implemented in the track
@@ -690,9 +691,9 @@ is useful. Ignored directories don't get flagged as unimplemented problems.
690
691
  A problem might be foregone for a number of reasons, typically because it's a
691
692
  bad exercise for the language.
692
693
 
693
- The `config.json` also has an optional `test_pattern` key. This is a regex that
694
- test filenames will match. If test files contain `/test/`, then this key can be
695
- deleted.
694
+ Optional keys:
695
+ * `test_pattern` - A (case sensitive) regex pattern that test filenames will match. It is used to determine which files will be visible on a problem's test-suite page on the exercism.io site. The default value used if this key is not present is `test` (note: this is case _insensitve_)
696
+ * `ignore_pattern` - A (case insensitive) regex pattern that will cause files matching it to not be served to the student by `exercism fetch`. The default value used if this key is not present is `example`
696
697
 
697
698
  ### Track-Level Linting With Configlet
698
699
 
@@ -4,6 +4,7 @@
4
4
  "repository": "https://github.com/exercism/xanimal",
5
5
  "active": true,
6
6
  "test_pattern": "a.*animal",
7
+ "ignore_pattern": "[^_]example",
7
8
  "problems": [
8
9
  "dog"
9
10
  ],
@@ -4,30 +4,30 @@ require_relative 'file_bundle'
4
4
  module Trackler
5
5
  # Implementation is a language-specific implementation of an exercise.
6
6
  class Implementation
7
- IGNORE = [
8
- Regexp.new("HINTS\.md$"),
9
- Regexp.new("example", Regexp::IGNORECASE),
10
- Regexp.new("\/\.$"),
11
- Regexp.new("/\.meta/")
7
+ IGNORE_PATTERNS = [
8
+ "\/HINTS\.md$",
9
+ "\/\.$",
10
+ "/\.meta/"
12
11
  ]
13
12
 
14
- attr_reader :track_id, :repo, :problem, :root, :file_bundle
13
+ attr_reader :track, :problem
15
14
  attr_writer :files
16
- def initialize(track_id, repo, problem, root)
17
- @track_id = track_id
18
- @repo = repo
15
+ def initialize(track, problem)
16
+ @track = track
19
17
  @problem = problem
20
- @root = Pathname.new(root)
21
- @file_bundle = FileBundle.new(track_directory, IGNORE)
18
+ end
19
+
20
+ def file_bundle
21
+ @file_bundle ||= FileBundle.new(implementation_dir, regexes_to_ignore)
22
22
  end
23
23
 
24
24
  def exists?
25
- File.exist?(track_directory)
25
+ File.exist?(implementation_dir)
26
26
  end
27
27
 
28
28
  def files
29
29
  @files ||= Hash[file_bundle.paths.map {|path|
30
- [path.relative_path_from(track_directory).to_s, File.read(path)]
30
+ [path.relative_path_from(implementation_dir).to_s, File.read(path)]
31
31
  }].merge("README.md" => readme)
32
32
  end
33
33
 
@@ -42,26 +42,28 @@ module Trackler
42
42
  @readme ||= assemble_readme
43
43
  end
44
44
 
45
- def exercise_dir
46
- if File.exist?(track_dir.join('exercises'))
47
- File.join('exercises', problem.slug)
48
- else
49
- problem.slug
50
- end
51
- end
52
-
53
45
  def git_url
54
- [repo, "tree/master", exercise_dir].join("/")
46
+ [track.repository, "tree/master", exercise_dir].join("/")
55
47
  end
56
48
 
57
49
  private
58
50
 
59
- def track_directory
60
- @track_directory ||= track_dir.join(exercise_dir)
51
+ def regexes_to_ignore
52
+ (IGNORE_PATTERNS + [@track.ignore_pattern]).map do |pattern|
53
+ Regexp.new(pattern, Regexp::IGNORECASE)
54
+ end
55
+ end
56
+
57
+ def implementation_dir
58
+ @implementation_dir ||= track.dir.join(exercise_dir)
61
59
  end
62
60
 
63
- def track_dir
64
- @track_dir ||= root.join('tracks', track_id)
61
+ def exercise_dir
62
+ if File.exist?(track.dir.join('exercises'))
63
+ File.join('exercises', problem.slug)
64
+ else
65
+ problem.slug
66
+ end
65
67
  end
66
68
 
67
69
  def assemble_readme
@@ -85,8 +87,8 @@ module Trackler
85
87
  def readme_body
86
88
  [
87
89
  problem.description,
88
- implementation_hint,
89
- track_hint,
90
+ implementation_hints,
91
+ track.hints,
90
92
  ].reject(&:empty?).join("\n").strip
91
93
  end
92
94
 
@@ -101,16 +103,8 @@ It's possible to submit an incomplete solution so you can see how others have co
101
103
  README
102
104
  end
103
105
 
104
- def track_hint
105
- track_hints_filename = track_dir.join('exercises','TRACK_HINTS.md')
106
- unless File.exist?(track_hints_filename)
107
- track_hints_filename = track_dir.join('SETUP.md')
108
- end
109
- read track_hints_filename
110
- end
111
-
112
- def implementation_hint
113
- read File.join(track_directory, 'HINTS.md')
106
+ def implementation_hints
107
+ read File.join(implementation_dir, 'HINTS.md')
114
108
  end
115
109
 
116
110
  def read(f)
@@ -25,7 +25,7 @@ module Trackler
25
25
 
26
26
  def all
27
27
  @all ||= slugs.map { |slug|
28
- Implementation.new(track.id, repo, Problem.new(slug, root, track), root)
28
+ Implementation.new(track, Problem.new(slug, root, track))
29
29
  }
30
30
  end
31
31
 
@@ -35,7 +35,7 @@ module Trackler
35
35
 
36
36
  def implementation_map
37
37
  hash = Hash.new { |_, k|
38
- Implementation.new(track.id, repo, Problem.new(k, root, track), root)
38
+ Implementation.new(track, Problem.new(k, root, track))
39
39
  }
40
40
  all.each do |impl|
41
41
  hash[impl.problem.slug] = impl
@@ -81,6 +81,10 @@ module Trackler
81
81
  end
82
82
  end
83
83
 
84
+ def ignore_pattern
85
+ config.fetch('ignore_pattern', 'example')
86
+ end
87
+
84
88
  def docs(positional_image_path_which_is_deprecated = nil, image_path: nil)
85
89
  if positional_image_path_which_is_deprecated
86
90
  warn "DEPRECATION WARNING:\ntrack.docs: Positional argument is deprecated, please use keyword argument 'image_path:' instead\neg: track.docs(image_path: #{positional_image_path_which_is_deprecated.inspect})\n"
@@ -107,6 +111,18 @@ module Trackler
107
111
  active_slugs + foregone_slugs + deprecated_slugs
108
112
  end
109
113
 
114
+ def dir
115
+ root.join("tracks", id)
116
+ end
117
+
118
+ def hints
119
+ track_hints_filename = dir.join('exercises', 'TRACK_HINTS.md')
120
+ unless File.exist?(track_hints_filename)
121
+ track_hints_filename = dir.join('SETUP.md')
122
+ end
123
+ read track_hints_filename
124
+ end
125
+
110
126
  private
111
127
 
112
128
  # The slugs for the problems that are currently in the track.
@@ -139,10 +155,6 @@ module Trackler
139
155
  formats.max_by { |format| formats.count(format) }
140
156
  end
141
157
 
142
- def dir
143
- root.join("tracks", id)
144
- end
145
-
146
158
  def config
147
159
  @config ||= JSON.parse(File.read(config_filename))
148
160
  end
@@ -187,5 +199,13 @@ module Trackler
187
199
  def png_icon
188
200
  @png_icon ||= Image.new(File.join(dir, "img/icon.png"))
189
201
  end
202
+
203
+ def read(f)
204
+ if File.exist?(f)
205
+ File.read(f)
206
+ else
207
+ ""
208
+ end
209
+ end
190
210
  end
191
211
  end
@@ -1,3 +1,3 @@
1
1
  module Trackler
2
- VERSION = "2.0.8.54"
2
+ VERSION = "2.0.8.55"
3
3
  end
@@ -220,6 +220,15 @@
220
220
  "topics": [
221
221
  ]
222
222
  },
223
+ {
224
+ "slug": "tournament",
225
+ "difficulty": 4,
226
+ "topics": [
227
+ "string processing",
228
+ "sorting",
229
+ "formatting"
230
+ ]
231
+ },
223
232
  {
224
233
  "slug": "list-ops",
225
234
  "difficulty": 4,
@@ -385,6 +394,14 @@
385
394
  "topics": [
386
395
  ]
387
396
  },
397
+ {
398
+ "slug": "simple-cipher",
399
+ "difficulty": 3,
400
+ "topics": [
401
+ "string processing",
402
+ "encryption"
403
+ ]
404
+ },
388
405
  {
389
406
  "slug": "bank-account",
390
407
  "difficulty": 7,
@@ -9,57 +9,72 @@ defmodule BracketPushTest do
9
9
  use ExUnit.Case
10
10
 
11
11
  # @tag :pending
12
+ test "paired square brackets" do
13
+ assert BracketPush.check_brackets("[]")
14
+ end
15
+
16
+ @tag :pending
12
17
  test "empty string" do
13
18
  assert BracketPush.check_brackets("")
14
19
  end
15
20
 
16
21
  @tag :pending
17
- test "appropriate bracketing in a set of brackets" do
18
- assert BracketPush.check_brackets("{}")
22
+ test "unpaired brackets" do
23
+ refute BracketPush.check_brackets("[[")
19
24
  end
20
25
 
21
26
  @tag :pending
22
- test "unclosed brackets" do
23
- refute BracketPush.check_brackets("{{")
27
+ test "wrong ordered brackets" do
28
+ refute BracketPush.check_brackets("}{")
24
29
  end
25
30
 
26
31
  @tag :pending
27
- test "more than one pair of brackets" do
28
- assert BracketPush.check_brackets("{}[]")
32
+ test "wrong closing bracket" do
33
+ refute BracketPush.check_brackets("{]")
29
34
  end
30
35
 
31
36
  @tag :pending
32
- test "brackets are out of order" do
33
- refute BracketPush.check_brackets("}{")
37
+ test "paired with whitespace" do
38
+ assert BracketPush.check_brackets("{ }")
34
39
  end
35
40
 
36
41
  @tag :pending
37
- test "nested brackets" do
38
- assert BracketPush.check_brackets("{[()]}")
42
+ test "simple nested brackets" do
43
+ assert BracketPush.check_brackets("{[]}")
39
44
  end
40
45
 
41
46
  @tag :pending
42
- test "unbalanced nested brackets" do
43
- refute BracketPush.check_brackets("{[}]")
47
+ test "several paired brackets" do
48
+ assert BracketPush.check_brackets("{}[]")
44
49
  end
45
50
 
46
51
  @tag :pending
47
- test "bracket closure with deeper nesting" do
52
+ test "paired and nested brackets" do
53
+ assert BracketPush.check_brackets("([{}({}[])])")
54
+ end
55
+
56
+ @tag :pending
57
+ test "unopened closing brackets" do
48
58
  refute BracketPush.check_brackets("{[)][]}")
49
59
  end
50
60
 
51
61
  @tag :pending
52
- test "bracket closure in a long string of brackets" do
53
- assert BracketPush.check_brackets("{[]([()])}")
62
+ test "unpaired and nested brackets" do
63
+ refute BracketPush.check_brackets("([{])")
64
+ end
65
+
66
+ @tag :pending
67
+ test "paired and wrong nested brackets" do
68
+ refute BracketPush.check_brackets("[({]})")
54
69
  end
55
70
 
56
71
  @tag :pending
57
- test "should ignore non-bracket characters" do
58
- assert BracketPush.check_brackets("{hello[]([a()])b}c")
72
+ test "math expression" do
73
+ assert BracketPush.check_brackets("(((185 + 223.85) * 15) - 543)/2")
59
74
  end
60
75
 
61
76
  @tag :pending
62
- test "string with newlines" do
63
- assert BracketPush.check_brackets("[]\n{()}\n[(({}))]\n")
77
+ test "complex latex expression" do
78
+ assert BracketPush.check_brackets("\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)")
64
79
  end
65
80
  end
@@ -1,4 +1,4 @@
1
- defmodule Matrix do
1
+ defmodule SaddlePoints do
2
2
  @doc """
3
3
  Parses a string representation of a matrix
4
4
  to a list of rows
@@ -1,4 +1,4 @@
1
- defmodule Matrix do
1
+ defmodule SaddlePoints do
2
2
  @doc """
3
3
  Parses a string representation of a matrix
4
4
  to a list of rows
@@ -10,61 +10,61 @@ defmodule SaddlePointsTest do
10
10
 
11
11
  # @tag :pending
12
12
  test "extract rows" do
13
- rows = Matrix.rows("1 2\n10 20")
13
+ rows = SaddlePoints.rows("1 2\n10 20")
14
14
  assert rows == [[1, 2], [10, 20]]
15
15
  end
16
16
 
17
17
  @tag :pending
18
18
  test "extract a row" do
19
- rows = Matrix.rows("9 7\n8 6")
19
+ rows = SaddlePoints.rows("9 7\n8 6")
20
20
  assert Enum.at(rows, 0) == [9, 7]
21
21
  end
22
22
 
23
23
  @tag :pending
24
24
  test "extract other row" do
25
- rows = Matrix.rows("9 8 7\n19 18 17")
25
+ rows = SaddlePoints.rows("9 8 7\n19 18 17")
26
26
  assert Enum.at(rows, 1) == [19, 18, 17]
27
27
  end
28
28
 
29
29
  @tag :pending
30
30
  test "extract other row again" do
31
- rows = Matrix.rows("1 4 9\n16 25 36")
31
+ rows = SaddlePoints.rows("1 4 9\n16 25 36")
32
32
  assert Enum.at(rows, 1) == [16, 25, 36]
33
33
  end
34
34
 
35
35
  @tag :pending
36
36
  test "extract a column" do
37
- columns = Matrix.columns("1 2 3\n4 5 6\n7 8 9\n8 7 6")
37
+ columns = SaddlePoints.columns("1 2 3\n4 5 6\n7 8 9\n8 7 6")
38
38
  assert Enum.at(columns, 0) == [1, 4, 7, 8]
39
39
  end
40
40
 
41
41
  @tag :pending
42
42
  test "extract another column" do
43
- columns = Matrix.columns("89 1903 3\n18 3 1\n9 4 800")
43
+ columns = SaddlePoints.columns("89 1903 3\n18 3 1\n9 4 800")
44
44
  assert Enum.at(columns, 1) == [1903, 3, 4]
45
45
  end
46
46
 
47
47
  @tag :pending
48
48
  test "no saddle point" do
49
- saddle_points = Matrix.saddle_points("2 1\n1 2")
49
+ saddle_points = SaddlePoints.saddle_points("2 1\n1 2")
50
50
  assert saddle_points == []
51
51
  end
52
52
 
53
53
  @tag :pending
54
54
  test "a saddle point" do
55
- saddle_points = Matrix.saddle_points("1 2\n3 4")
55
+ saddle_points = SaddlePoints.saddle_points("1 2\n3 4")
56
56
  assert saddle_points == [{0, 1}]
57
57
  end
58
58
 
59
59
  @tag :pending
60
60
  test "another saddle point" do
61
- saddle_points = Matrix.saddle_points("18 3 39 19 91\n38 10 8 77 320\n3 4 8 6 7")
61
+ saddle_points = SaddlePoints.saddle_points("18 3 39 19 91\n38 10 8 77 320\n3 4 8 6 7")
62
62
  assert saddle_points == [{2, 2}]
63
63
  end
64
64
 
65
65
  @tag :pending
66
66
  test "multiple saddle points" do
67
- saddle_points = Matrix.saddle_points("4 5 4\n3 5 5\n1 5 4")
67
+ saddle_points = SaddlePoints.saddle_points("4 5 4\n3 5 5\n1 5 4")
68
68
  assert saddle_points == [{0, 1}, {1, 1}, {2, 1}]
69
69
  end
70
70
  end
@@ -0,0 +1,80 @@
1
+ defmodule SimpleCipher do
2
+ @alphabet "abcdefghijklmnopqrstuvwxyz" |> String.graphemes
3
+ @alphabet_size @alphabet |> length
4
+
5
+ for key_char <- @alphabet do
6
+ shifted_alphabet = Stream.cycle(@alphabet)
7
+ |> Stream.drop_while(&(&1 != key_char))
8
+ |> Enum.take(@alphabet_size)
9
+
10
+ for { plain, cipher } <- Enum.zip(@alphabet, shifted_alphabet) do
11
+ defp encode_char(unquote(plain), unquote(key_char)), do: unquote(cipher)
12
+ defp decode_char(unquote(cipher), unquote(key_char)), do: unquote(plain)
13
+ end
14
+ end
15
+
16
+ defp encode_char(plain, _), do: plain
17
+ defp decode_char(cipher, _), do: cipher
18
+
19
+ defp encode_char({plain, key}), do: encode_char(plain, key)
20
+ defp decode_char({cipher, key}), do: decode_char(cipher, key)
21
+
22
+ @doc """
23
+ Given a `plaintext` and `key`, encode each character of the `plaintext` by
24
+ shifting it by the corresponding letter in the alphabet shifted by the number
25
+ of letters represented by the `key` character, repeating the `key` if it is
26
+ shorter than the `plaintext`.
27
+
28
+ For example, for the letter 'd', the alphabet is rotated to become:
29
+
30
+ defghijklmnopqrstuvwxyzabc
31
+
32
+ You would encode the `plaintext` by taking the current letter and mapping it
33
+ to the letter in the same position in this rotated alphabet.
34
+
35
+ abcdefghijklmnopqrstuvwxyz
36
+ defghijklmnopqrstuvwxyzabc
37
+
38
+ "a" becomes "d", "t" becomes "w", etc...
39
+
40
+ Each letter in the `plaintext` will be encoded with the alphabet of the `key`
41
+ character in the same position. If the `key` is shorter than the `plaintext`,
42
+ repeat the `key`.
43
+
44
+ Example:
45
+
46
+ plaintext = "testing"
47
+ key = "abc"
48
+
49
+ The key should repeat to become the same length as the text, becoming
50
+ "abcabca". If the key is longer than the text, only use as many letters of it
51
+ as are necessary.
52
+ """
53
+ def encode(plaintext, key) do
54
+ convert_keystream(plaintext, key, &encode_char/1)
55
+ end
56
+
57
+ @doc """
58
+ Given a `ciphertext` and `key`, decode each character of the `ciphertext` by
59
+ finding the corresponding letter in the alphabet shifted by the number of
60
+ letters represented by the `key` character, repeating the `key` if it is
61
+ shorter than the `ciphertext`.
62
+
63
+ The same rules for key length and shifted alphabets apply as in `encode/2`,
64
+ but you will go the opposite way, so "d" becomes "a", "w" becomes "t",
65
+ etc..., depending on how much you shift the alphabet.
66
+ """
67
+ def decode(ciphertext, key) do
68
+ convert_keystream(ciphertext, key, &decode_char/1)
69
+ end
70
+
71
+ defp convert_keystream(text, key, converter) do
72
+ keystream = key |> String.graphemes |> Stream.cycle
73
+
74
+ text
75
+ |> String.graphemes
76
+ |> Enum.zip(keystream)
77
+ |> Enum.map_join(converter)
78
+ end
79
+ end
80
+
@@ -0,0 +1,49 @@
1
+ defmodule SimpleCipher do
2
+ @doc """
3
+ Given a `plaintext` and `key`, encode each character of the `plaintext` by
4
+ shifting it by the corresponding letter in the alphabet shifted by the number
5
+ of letters represented by the `key` character, repeating the `key` if it is
6
+ shorter than the `plaintext`.
7
+
8
+ For example, for the letter 'd', the alphabet is rotated to become:
9
+
10
+ defghijklmnopqrstuvwxyzabc
11
+
12
+ You would encode the `plaintext` by taking the current letter and mapping it
13
+ to the letter in the same position in this rotated alphabet.
14
+
15
+ abcdefghijklmnopqrstuvwxyz
16
+ defghijklmnopqrstuvwxyzabc
17
+
18
+ "a" becomes "d", "t" becomes "w", etc...
19
+
20
+ Each letter in the `plaintext` will be encoded with the alphabet of the `key`
21
+ character in the same position. If the `key` is shorter than the `plaintext`,
22
+ repeat the `key`.
23
+
24
+ Example:
25
+
26
+ plaintext = "testing"
27
+ key = "abc"
28
+
29
+ The key should repeat to become the same length as the text, becoming
30
+ "abcabca". If the key is longer than the text, only use as many letters of it
31
+ as are necessary.
32
+ """
33
+ def encode(plaintext, key) do
34
+ end
35
+
36
+ @doc """
37
+ Given a `ciphertext` and `key`, decode each character of the `ciphertext` by
38
+ finding the corresponding letter in the alphabet shifted by the number of
39
+ letters represented by the `key` character, repeating the `key` if it is
40
+ shorter than the `ciphertext`.
41
+
42
+ The same rules for key length and shifted alphabets apply as in `encode/2`,
43
+ but you will go the opposite way, so "d" becomes "a", "w" becomes "t",
44
+ etc..., depending on how much you shift the alphabet.
45
+ """
46
+ def decode(ciphertext, key) do
47
+ end
48
+ end
49
+