codeless_code 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +71 -43
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/codeless_code.gemspec +6 -4
- data/lib/codeless_code/catalog.rb +6 -2
- data/lib/codeless_code/cli.rb +10 -23
- data/lib/codeless_code/commands/filter_fables.rb +53 -10
- data/lib/codeless_code/fable.rb +7 -3
- data/lib/codeless_code/fable_set.rb +7 -2
- data/lib/codeless_code/filters/builders.rb +1 -0
- data/lib/codeless_code/filters/composite.rb +2 -0
- data/lib/codeless_code/filters/from_options.rb +2 -0
- data/lib/codeless_code/formats/plain.rb +1 -0
- data/lib/codeless_code/formats/raw.rb +1 -0
- data/lib/codeless_code/formats/term.rb +3 -3
- data/lib/codeless_code/language_set.rb +7 -1
- data/lib/codeless_code/options.rb +25 -15
- data/lib/codeless_code/renderers/fable.rb +15 -9
- data/lib/codeless_code/renderers/{page.rb → term_page.rb} +3 -1
- data/lib/codeless_code.rb +12 -5
- data/test/codeless_code/test_catalog.rb +68 -0
- data/test/codeless_code/test_fable.rb +2 -2
- data/test/codeless_code/test_fable_set.rb +18 -5
- data/test/codeless_code/test_language_set.rb +1 -18
- data/test/codeless_code/test_options.rb +65 -0
- data/test/helper.rb +3 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd5da6b3ee3fd39beace9072dc41a2247da1ed39f27ff050a3cbedc89ff18754
|
4
|
+
data.tar.gz: cdc34c935081bbbcbab8be9165997483adfb0a4bdc8c7b1bc69b5050fb940669
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1b00ec239abfdee2b0cc4fb81355e5336ddace39dd71bf924946e4f147c4ba33503c0ab9c603e393a54b2dda09e14e455ec7f39f3920db8365dee99ef469eea
|
7
|
+
data.tar.gz: 3d2e674fe69b5185acbc12e7de1549e56b9c364429751197445cc853744e7c4d3f386e283e0cda31fc31a5d19dd62c506a13357beed869d44dfd3d34c4308b8d
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -52,6 +52,18 @@ will be returned.
|
|
52
52
|
codeless_code -Da 2014 -Gg 2 -nS
|
53
53
|
```
|
54
54
|
|
55
|
+
### Listing
|
56
|
+
|
57
|
+
If the given filter returns more than one fable, they will be printed as a
|
58
|
+
list. By default, only the Number header and title of each fable will be
|
59
|
+
printed, one per line. You may use `-e` to specify additional headers to print.
|
60
|
+
Note that not every fable will have an entry for every header.
|
61
|
+
|
62
|
+
```sh
|
63
|
+
codeless_code -e Names,Geekiness
|
64
|
+
codeless_code -D 2015-06 -e Date -e Topics
|
65
|
+
```
|
66
|
+
|
55
67
|
### Current Options
|
56
68
|
|
57
69
|
```
|
@@ -67,76 +79,92 @@ Info
|
|
67
79
|
--version
|
68
80
|
|
69
81
|
Options
|
70
|
-
-
|
71
|
-
-
|
72
|
-
-
|
73
|
-
--
|
74
|
-
|
75
|
-
--
|
76
|
-
--
|
82
|
+
-c, --columns when listing fables, format the output into columns
|
83
|
+
-e, --headers headers to include in the list output. may be repeated
|
84
|
+
-f, --format one of: raw, plain, or term (default)
|
85
|
+
-o, --output write to the given file. "-" for STDOUT
|
86
|
+
-p, --path path to directory of fables. see github.com/aldesantis/the-codeless-code
|
87
|
+
--random select one fable, randomly, from the filtered list
|
88
|
+
--random-set select n fables, randomly, from the filtered list
|
89
|
+
--daily select one fable, randomly, from the filtered listbased on today's date
|
90
|
+
--trace print full error message if a fable fails to parse
|
91
|
+
-s, --sort when listing fables, sort by the given header
|
92
|
+
-r, --reverse when listing fables, reverse the order
|
77
93
|
|
78
94
|
Series Filters
|
79
95
|
-S, --series
|
80
|
-
-Ss, --series-start
|
81
|
-
-Se, --series-end
|
82
|
-
-hS, --has-series
|
83
|
-
-nS, --no-series
|
96
|
+
-Ss, --series-start series starts with
|
97
|
+
-Se, --series-end series ends with
|
98
|
+
-hS, --has-series has series listed
|
99
|
+
-nS, --no-series no series listed
|
84
100
|
|
85
101
|
Title Filters
|
86
102
|
-T, --title
|
87
|
-
-Ts, --title-start
|
88
|
-
-Te, --title-end
|
89
|
-
-hT, --has-title
|
90
|
-
-nT, --no-title
|
103
|
+
-Ts, --title-start title starts with
|
104
|
+
-Te, --title-end title ends with
|
105
|
+
-hT, --has-title has title listed
|
106
|
+
-nT, --no-title no title listed
|
91
107
|
|
92
108
|
Number Filters
|
93
|
-
-N, --number
|
94
|
-
-Ng, --number-gte
|
95
|
-
-Nl, --number-lte
|
96
|
-
-hN, --has-number
|
97
|
-
-nN, --no-number
|
109
|
+
-N, --number number (this is the default argument)
|
110
|
+
-Ng, --number-gte number or greater
|
111
|
+
-Nl, --number-lte number or lower
|
112
|
+
-hN, --has-number has number listed
|
113
|
+
-nN, --no-number no number listed
|
98
114
|
|
99
115
|
Language Filters
|
100
|
-
-L, --lang
|
116
|
+
-L, --lang language code (default: en)
|
101
117
|
|
102
118
|
Translator Filters
|
103
|
-
-
|
104
|
-
-
|
119
|
+
-R, --translator translator's name (default: first one, alphabetically)
|
120
|
+
-Rx, --translator-exact translator's name, case-sensitive (default: first one, alphabetically)
|
105
121
|
|
106
122
|
Date Filters
|
107
|
-
-D, --date
|
108
|
-
-Da, --date-after
|
109
|
-
-Db, --date-before
|
110
|
-
-nD, --no-date
|
123
|
+
-D, --date publish date
|
124
|
+
-Da, --date-after publish date or after
|
125
|
+
-Db, --date-before publish date or before
|
126
|
+
-nD, --no-date no publish date listed
|
111
127
|
|
112
128
|
Geekiness Filters
|
113
|
-
-G, --geekiness
|
114
|
-
-Gg, --geekiness-gte
|
115
|
-
-Gl, --geekiness-lte
|
116
|
-
-nG, --no-geekiness
|
129
|
+
-G, --geekiness geekiness rating
|
130
|
+
-Gg, --geekiness-gte geekiness rating or greater
|
131
|
+
-Gl, --geekiness-lte geekiness rating or lower
|
132
|
+
-nG, --no-geekiness no geekiness rating
|
117
133
|
|
118
134
|
Name Filters
|
119
135
|
-A, --name
|
120
|
-
-As, --name-start
|
121
|
-
-Ae, --name-end
|
122
|
-
-nA, --no-name
|
136
|
+
-As, --name-start name starts with
|
137
|
+
-Ae, --name-end name ends with
|
138
|
+
-nA, --no-name no name listed
|
123
139
|
|
124
140
|
Credits Filters
|
125
141
|
-C, --credits
|
126
|
-
-Cs, --credits-start
|
127
|
-
-Ce, --credits-end
|
128
|
-
-hC, --has-credits
|
129
|
-
-nC, --no-credits
|
142
|
+
-Cs, --credits-start credits starts with
|
143
|
+
-Ce, --credits-end credits ends with
|
144
|
+
-hC, --has-credits has credits listed
|
145
|
+
-nC, --no-credits no credits listed
|
130
146
|
|
131
147
|
Tagline Filters
|
132
148
|
-I, --tagline
|
133
|
-
-Is, --tagline-start
|
134
|
-
-Ie, --tagline-end
|
135
|
-
-hI, --has-tagline
|
136
|
-
-nI, --no-tagline
|
149
|
+
-Is, --tagline-start tagline starts with
|
150
|
+
-Ie, --tagline-end tagline ends with
|
151
|
+
-hI, --has-tagline has tagline listed
|
152
|
+
-nI, --no-tagline no tagline listed
|
153
|
+
```
|
154
|
+
|
155
|
+
## Development
|
156
|
+
|
157
|
+
### Testing
|
158
|
+
|
159
|
+
```sh
|
160
|
+
bundle exec rake test
|
161
|
+
COVERAGE=surewhynot bundle exec rake test
|
162
|
+
|
163
|
+
bundle exec guard
|
164
|
+
COVERAGE=1 bundle exec guard
|
137
165
|
```
|
138
166
|
|
139
|
-
|
167
|
+
### Contributing to codeless_code
|
140
168
|
|
141
169
|
* Check out the latest master to make sure the feature hasn't been
|
142
170
|
implemented or the bug hasn't been fixed yet.
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.6
|
data/codeless_code.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: codeless_code 0.1.
|
5
|
+
# stub: codeless_code 0.1.6 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "codeless_code".freeze
|
9
|
-
s.version = "0.1.
|
9
|
+
s.version = "0.1.6"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Jon Sangster".freeze]
|
14
|
-
s.date = "2018-11-
|
14
|
+
s.date = "2018-11-05"
|
15
15
|
s.description = "http://thecodelesscode.com contains many humorous and interesting fables and koans. The authors have open-sourced these fables, and many tanslations, available at https://github.com/aldesantis/the-codeless-code. This tool provides a CLI to filter through these fables and view them.".freeze
|
16
16
|
s.email = "jon@ertt.ca".freeze
|
17
17
|
s.executables = ["codeless_code".freeze]
|
@@ -1500,10 +1500,12 @@ Gem::Specification.new do |s|
|
|
1500
1500
|
"lib/codeless_code/language_set.rb",
|
1501
1501
|
"lib/codeless_code/options.rb",
|
1502
1502
|
"lib/codeless_code/renderers/fable.rb",
|
1503
|
-
"lib/codeless_code/renderers/
|
1503
|
+
"lib/codeless_code/renderers/term_page.rb",
|
1504
|
+
"test/codeless_code/test_catalog.rb",
|
1504
1505
|
"test/codeless_code/test_fable.rb",
|
1505
1506
|
"test/codeless_code/test_fable_set.rb",
|
1506
1507
|
"test/codeless_code/test_language_set.rb",
|
1508
|
+
"test/codeless_code/test_options.rb",
|
1507
1509
|
"test/helper.rb",
|
1508
1510
|
"test/test_codeless_code.rb"
|
1509
1511
|
]
|
@@ -14,14 +14,17 @@
|
|
14
14
|
# You should have received a copy of the GNU General Public License along with
|
15
15
|
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
16
|
module CodelessCode
|
17
|
+
# The entire collection of fables available to the application from a given
|
18
|
+
# data directory.
|
17
19
|
class Catalog
|
18
|
-
# extend Forwardable
|
19
20
|
include Enumerable
|
20
21
|
|
22
|
+
# @param root_dir [Pathname] A directory that contains {Fable} files
|
21
23
|
def initialize(root_dir)
|
22
24
|
@root_dir = root_dir
|
23
25
|
end
|
24
26
|
|
27
|
+
# @return [Enumerable<Symbol>] the codes of all available languages
|
25
28
|
def languages
|
26
29
|
@languages ||= @root_dir.glob('*-*')
|
27
30
|
.select(&:directory?)
|
@@ -31,10 +34,11 @@ module CodelessCode
|
|
31
34
|
.sort
|
32
35
|
end
|
33
36
|
|
37
|
+
# @param lang [Symbol]
|
34
38
|
# @return [LanguageSet]
|
35
39
|
def fetch(lang)
|
36
40
|
if languages.include?(lang)
|
37
|
-
LanguageSet.new(lang)
|
41
|
+
LanguageSet.new(lang, root_dir: @root_dir)
|
38
42
|
else
|
39
43
|
raise LanguageSet::NotFoundError,
|
40
44
|
format("No fables for language %p", lang)
|
data/lib/codeless_code/cli.rb
CHANGED
@@ -24,36 +24,28 @@ module CodelessCode
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def call
|
27
|
-
|
27
|
+
user_io = io_open
|
28
28
|
|
29
29
|
if options.key?(:help)
|
30
|
-
(
|
30
|
+
(user_io || $stdout).puts options.help
|
31
31
|
elsif options.key?(:version)
|
32
|
-
(
|
32
|
+
(user_io || $stdout).puts version_str
|
33
33
|
elsif options.key?(:list_translations)
|
34
|
-
Commands::ListTranslations.new(catalog, io:
|
34
|
+
Commands::ListTranslations.new(catalog, io: user_io).call
|
35
35
|
else
|
36
|
-
|
36
|
+
Commands::FilterFables.new(catalog, options, io: user_io)
|
37
|
+
.call(&method(:select))
|
37
38
|
end
|
38
39
|
|
39
40
|
rescue Slop::Error => e
|
40
41
|
warn format("%s\n\n%s", e, options.help)
|
41
42
|
exit 1
|
42
43
|
ensure
|
43
|
-
|
44
|
+
user_io&.close
|
44
45
|
end
|
45
46
|
|
46
47
|
private
|
47
48
|
|
48
|
-
def filter_fables(io)
|
49
|
-
filter = Filters::FromOptions.new(options)
|
50
|
-
fallback = options[:trace] ? nil : Formats::Raw
|
51
|
-
|
52
|
-
cmd = Commands::FilterFables.new(filter, output_format, io: io,
|
53
|
-
fallback_filter: fallback)
|
54
|
-
cmd.call(catalog, &method(:select))
|
55
|
-
end
|
56
|
-
|
57
49
|
def options
|
58
50
|
@options ||= Options.new(@command_name, @args)
|
59
51
|
end
|
@@ -62,24 +54,19 @@ module CodelessCode
|
|
62
54
|
@catalog ||= Catalog.new(options.data_dir)
|
63
55
|
end
|
64
56
|
|
57
|
+
# @return [IO,nil] the user's chosen output stream. +nil+ if unspecified
|
65
58
|
def io_open
|
66
59
|
if (path = options[:output])
|
67
60
|
path == '-' ? $stdout.dup : File.open(path, 'w')
|
68
61
|
end
|
69
62
|
end
|
70
63
|
|
71
|
-
def output_format
|
72
|
-
case options[:format]
|
73
|
-
when 'plain' then Formats::Plain
|
74
|
-
when 'raw' then Formats::Raw
|
75
|
-
else Formats::Term
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
64
|
def select(fables)
|
80
65
|
select_rand(fables)
|
81
66
|
end
|
82
67
|
|
68
|
+
# @return [Enumerable<Fable>] a random subset of the given collection, as
|
69
|
+
# specified by {#options}
|
83
70
|
def select_rand(fables)
|
84
71
|
if options[:daily]
|
85
72
|
fables.sample(1, random: Random.new(Date.today.strftime('%Y%m%d').to_i))
|
@@ -15,19 +15,25 @@
|
|
15
15
|
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
16
|
module CodelessCode
|
17
17
|
module Commands
|
18
|
+
# Filters down a {Catalog} of {Fable Fables} with criteria specified via
|
19
|
+
# the CLI. The results will be listed line-by-line, unless only one fable
|
20
|
+
# is found. In that case, it will be printed out.
|
18
21
|
class FilterFables
|
22
|
+
attr_reader :options
|
23
|
+
|
19
24
|
# @param io [IO] if given, the output will be written to this stream,
|
20
25
|
# otherwise we will attempt to invoke the user's PAGER app
|
21
|
-
def initialize(
|
22
|
-
@
|
23
|
-
@
|
26
|
+
def initialize(catalog, options, io: nil)
|
27
|
+
@catalog = catalog
|
28
|
+
@options = options
|
24
29
|
@io = io
|
25
|
-
@fallback_filter = fallback_filter
|
26
30
|
end
|
27
31
|
|
28
|
-
def call
|
29
|
-
|
32
|
+
def call
|
33
|
+
filter = Filters::FromOptions.new(options)
|
34
|
+
fables = @catalog.select(filter)
|
30
35
|
fables = yield fables if block_given?
|
36
|
+
fables = sort(fables)
|
31
37
|
|
32
38
|
case fables.size
|
33
39
|
when 0
|
@@ -41,11 +47,19 @@ module CodelessCode
|
|
41
47
|
|
42
48
|
private
|
43
49
|
|
50
|
+
def sort(fables)
|
51
|
+
if options.key?(:sort)
|
52
|
+
fables = fables.sort_by { |f| f[options[:sort]] || "\uffff" }
|
53
|
+
end
|
54
|
+
fables = fables.reverse if options[:reverse]
|
55
|
+
fables
|
56
|
+
end
|
57
|
+
|
44
58
|
def show(fable)
|
45
59
|
if @io.nil? && ENV.key?('PAGER')
|
46
60
|
pager(ENV['PAGER'], fable)
|
47
61
|
else
|
48
|
-
puts
|
62
|
+
puts for_pager(fable)
|
49
63
|
end
|
50
64
|
end
|
51
65
|
|
@@ -53,12 +67,41 @@ module CodelessCode
|
|
53
67
|
io = open format('|%s', cmd), 'w'
|
54
68
|
pid = io.pid
|
55
69
|
|
56
|
-
io.
|
57
|
-
|
70
|
+
io.puts for_pager(fable)
|
71
|
+
ensure
|
72
|
+
io&.close
|
73
|
+
end
|
74
|
+
|
75
|
+
def for_pager(fable)
|
76
|
+
render(fable).for_pager(output_format, fallback: fallback_filter)
|
77
|
+
end
|
78
|
+
|
79
|
+
def output_format
|
80
|
+
case options[:format]
|
81
|
+
when 'plain' then Formats::Plain
|
82
|
+
when 'raw' then Formats::Raw
|
83
|
+
else Formats::Term
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def fallback_filter
|
88
|
+
options[:trace] ? nil : Formats::Raw
|
58
89
|
end
|
59
90
|
|
60
91
|
def list(fables)
|
61
|
-
|
92
|
+
width = title_width(fables)
|
93
|
+
|
94
|
+
fables.each do |fable|
|
95
|
+
puts render(fable).for_list(options[:headers], title_width: width)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def title_width(fables)
|
100
|
+
if options[:columns]
|
101
|
+
-fables.map { |f| render(f).best_title.size }.compact.max
|
102
|
+
else
|
103
|
+
''
|
104
|
+
end
|
62
105
|
end
|
63
106
|
|
64
107
|
def render(fable)
|
data/lib/codeless_code/fable.rb
CHANGED
@@ -17,28 +17,30 @@ require 'forwardable'
|
|
17
17
|
require 'date'
|
18
18
|
|
19
19
|
module CodelessCode
|
20
|
+
# Model/Adapter for a "Codeless Code" fable stored in a text file.
|
20
21
|
class Fable
|
21
22
|
extend Forwardable
|
22
23
|
|
23
24
|
HEADER_PATTERN = /([^:\s]+)\s*:\s*(.+)\s*$/.freeze
|
24
25
|
|
25
|
-
|
26
|
-
attr_reader :read_headers
|
26
|
+
attr_reader :file, :read_headers
|
27
27
|
|
28
28
|
alias_method :read_headers?, :read_headers
|
29
29
|
def_delegators :headers, :[], :fetch, :key?
|
30
30
|
|
31
31
|
def initialize(file)
|
32
|
-
|
32
|
+
@file = file
|
33
33
|
@read_headers = false
|
34
34
|
@body_pos = nil
|
35
35
|
end
|
36
36
|
|
37
|
+
# @return [String] the actual story, including MediaWiki markup
|
37
38
|
def body
|
38
39
|
@body ||= read_body.freeze
|
39
40
|
end
|
40
41
|
alias_method :to_s, :body
|
41
42
|
|
43
|
+
# @return [Hash<String, String>] the story's metadata
|
42
44
|
def headers
|
43
45
|
@headers ||= begin
|
44
46
|
@read_headers = true
|
@@ -50,10 +52,12 @@ module CodelessCode
|
|
50
52
|
headers.key?(key)
|
51
53
|
end
|
52
54
|
|
55
|
+
# @return [::Date, nil]
|
53
56
|
def date
|
54
57
|
::Date.parse(self['Date']) if header?('Date')
|
55
58
|
end
|
56
59
|
|
60
|
+
# @return [Symbol]
|
57
61
|
def lang
|
58
62
|
@lang ||= dir_parts.first.to_sym
|
59
63
|
end
|
@@ -16,10 +16,15 @@
|
|
16
16
|
require 'forwardable'
|
17
17
|
|
18
18
|
module CodelessCode
|
19
|
+
# An {Enumerable collection} of {Fable fables} in a given language, all
|
20
|
+
# translated by the same person.
|
19
21
|
class FableSet
|
20
22
|
extend Forwardable
|
21
23
|
include Enumerable
|
22
24
|
|
25
|
+
FILE_PATTERN = '*.txt'.freeze
|
26
|
+
LANG_SEP = '-'.freeze
|
27
|
+
|
23
28
|
attr_accessor :dir
|
24
29
|
def_delegator :fables, :each
|
25
30
|
|
@@ -46,11 +51,11 @@ module CodelessCode
|
|
46
51
|
private
|
47
52
|
|
48
53
|
def files
|
49
|
-
dir.glob(
|
54
|
+
dir.glob(FILE_PATTERN)
|
50
55
|
end
|
51
56
|
|
52
57
|
def name_parts
|
53
|
-
dir.basename.to_s.split(
|
58
|
+
dir.basename.to_s.split(LANG_SEP)
|
54
59
|
end
|
55
60
|
end
|
56
61
|
end
|
@@ -17,6 +17,8 @@ require 'forwardable'
|
|
17
17
|
|
18
18
|
module CodelessCode
|
19
19
|
module Filters
|
20
|
+
# A filter that containers a collection of other filters. It will only
|
21
|
+
# match a {Fable} if it's matched by each contained filter.
|
20
22
|
class Composite
|
21
23
|
extend Forwardable
|
22
24
|
include Enumerable
|
@@ -18,9 +18,11 @@ require 'slop'
|
|
18
18
|
|
19
19
|
module CodelessCode
|
20
20
|
module Filters
|
21
|
+
# A {Composite} filter build from the arguments supplied via the CLI.
|
21
22
|
class FromOptions < Composite
|
22
23
|
extend Forwardable
|
23
24
|
|
25
|
+
# @param opts [Options]
|
24
26
|
def initialize(opts)
|
25
27
|
@opts = opts
|
26
28
|
@filters = nil
|
@@ -19,11 +19,11 @@ require 'nokogiri'
|
|
19
19
|
|
20
20
|
module CodelessCode
|
21
21
|
module Formats
|
22
|
+
# Renders the {Fable} using ANSI control characters for bold, italics,
|
23
|
+
# colors, etc.
|
22
24
|
class Term < Base
|
23
25
|
def call
|
24
|
-
raw
|
25
|
-
.map { |str| from_wiki(to_xhtml(regex(str))) }
|
26
|
-
.join("\n\n")
|
26
|
+
from_wiki(to_xhtml(regex(raw)))
|
27
27
|
end
|
28
28
|
|
29
29
|
def regex(str)
|
@@ -16,12 +16,16 @@
|
|
16
16
|
require 'forwardable'
|
17
17
|
|
18
18
|
module CodelessCode
|
19
|
+
# An {Enumerable collection} of {FableSet fable sets} in a given language, but
|
20
|
+
# possibly translated by the different people.
|
19
21
|
class LanguageSet
|
20
22
|
extend Forwardable
|
21
23
|
include Enumerable
|
22
24
|
|
23
25
|
NotFoundError = Class.new(StandardError)
|
24
26
|
|
27
|
+
LANG_PATTERN = '%s-*'.freeze
|
28
|
+
|
25
29
|
attr_accessor :lang, :root_dir
|
26
30
|
def_delegator :fable_sets, :each
|
27
31
|
|
@@ -34,8 +38,10 @@ module CodelessCode
|
|
34
38
|
dirs.map { |dir| FableSet.new(dir) }
|
35
39
|
end
|
36
40
|
|
41
|
+
private
|
42
|
+
|
37
43
|
def dirs
|
38
|
-
root_dir.glob(format(
|
44
|
+
root_dir.glob(format(LANG_PATTERN, lang)).select(&:directory?)
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -17,10 +17,14 @@ require 'forwardable'
|
|
17
17
|
require 'slop'
|
18
18
|
|
19
19
|
module CodelessCode
|
20
|
+
# Adapter class for ARGV which exposes the command-line arguments as data
|
21
|
+
# types.
|
20
22
|
class Options
|
21
23
|
extend Forwardable
|
22
24
|
include Enumerable
|
23
25
|
|
26
|
+
MAX_ARGS = 1 # --number flag may be passed as argument +cmd 3+ vs +cmd -N 3+
|
27
|
+
|
24
28
|
def_delegators :opts, :to_hash, :[]
|
25
29
|
def_delegators :to_hash, :each, :slice
|
26
30
|
|
@@ -29,34 +33,40 @@ module CodelessCode
|
|
29
33
|
@argv = argv
|
30
34
|
end
|
31
35
|
|
32
|
-
def opts
|
33
|
-
@opts ||=
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
Slop.parse(args, suppress_errors: suppress, &opts).tap do |opt|
|
39
|
-
if !suppress && opt.arguments.size > 2
|
40
|
-
raise format('too many arguments: %p', opt.arguments[1..-1])
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
36
|
+
def opts
|
37
|
+
@opts ||= parse_opts(suppress_errors: false)
|
38
|
+
end
|
39
|
+
|
40
|
+
def args
|
41
|
+
opts.arguments[1..-1]
|
44
42
|
end
|
45
43
|
|
46
44
|
def key?(key)
|
47
45
|
!!self[key]
|
48
46
|
end
|
49
47
|
|
48
|
+
# @return [Pathname] Where on the file system to search for fables. Will
|
49
|
+
# default to {CodelessCode.DEFAULT_DATA} if unspecified
|
50
50
|
def data_dir
|
51
51
|
@data_dir ||= self[:path] ? Pathname.new(self[:path]) : DEFAULT_DATA
|
52
52
|
end
|
53
53
|
|
54
|
+
# @return [String] a description of the supported command-line options
|
54
55
|
def help
|
55
|
-
|
56
|
+
parse_opts(suppress_errors: true).to_s
|
56
57
|
end
|
57
58
|
|
58
|
-
|
59
|
-
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_opts(suppress_errors:)
|
62
|
+
args = [@command_name] + @argv
|
63
|
+
opts = CodelessCode::OPTIONS.curry[@command_name]
|
64
|
+
|
65
|
+
Slop.parse(args, suppress_errors: suppress_errors, &opts).tap do |o|
|
66
|
+
if !suppress_errors && o.arguments.size > MAX_ARGS + 1
|
67
|
+
raise format('too many arguments: %p', o.arguments[1..-1])
|
68
|
+
end
|
69
|
+
end
|
60
70
|
end
|
61
71
|
end
|
62
72
|
end
|
@@ -15,11 +15,12 @@
|
|
15
15
|
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
16
|
module CodelessCode
|
17
17
|
module Renderers
|
18
|
+
# Prints a {Fable} is various ways.
|
18
19
|
class Fable < SimpleDelegator
|
19
20
|
HEADER_SORT = %w[Tagline Number Date].freeze
|
20
21
|
|
21
22
|
def for_pager(format, fallback: nil)
|
22
|
-
|
23
|
+
TermPage.new.tap do |page|
|
23
24
|
page.title = best_title
|
24
25
|
page.body = render_with(format, fallback: fallback)
|
25
26
|
|
@@ -27,12 +28,23 @@ module CodelessCode
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
def for_list
|
31
|
-
format(
|
31
|
+
def for_list(head_keys = [], title_width: '')
|
32
|
+
format("%s %#{title_width}s %s",
|
33
|
+
wide_number, best_title, render_slice(head_keys)).strip
|
34
|
+
end
|
35
|
+
|
36
|
+
def best_title
|
37
|
+
return title_with_subtitle if title&.size&.positive?
|
38
|
+
|
39
|
+
self['Name'] || self['Tagline'] || inspect
|
32
40
|
end
|
33
41
|
|
34
42
|
private
|
35
43
|
|
44
|
+
def render_slice(keys)
|
45
|
+
headers.slice(*keys).map {|k, v| format('%s: %p', k, v) }.join(', ')
|
46
|
+
end
|
47
|
+
|
36
48
|
def render_with(format, fallback:)
|
37
49
|
format.new(body).call
|
38
50
|
rescue => e
|
@@ -41,12 +53,6 @@ module CodelessCode
|
|
41
53
|
fallback.new(body).call
|
42
54
|
end
|
43
55
|
|
44
|
-
def best_title
|
45
|
-
return title_with_subtitle if title&.size&.positive?
|
46
|
-
|
47
|
-
self['Name'] || self['Tagline'] || inspect
|
48
|
-
end
|
49
|
-
|
50
56
|
def title_with_subtitle
|
51
57
|
[self['Series'], title, self['Subtitle']].compact.join(': ')
|
52
58
|
end
|
@@ -17,7 +17,9 @@ require 'colorized_string'
|
|
17
17
|
|
18
18
|
module CodelessCode
|
19
19
|
module Renderers
|
20
|
-
|
20
|
+
# Attempts to format the output for the +PAGER+ such that it fits within
|
21
|
+
# the bounds of the terminal window.
|
22
|
+
class TermPage
|
21
23
|
PAGER_FORMAT = [
|
22
24
|
'%<title>s', '%<sep1>s', '%<headers>s', '%<sep2>s', '', '%<body>s'
|
23
25
|
].join("\n").freeze
|
data/lib/codeless_code.rb
CHANGED
@@ -39,8 +39,8 @@ module CodelessCode
|
|
39
39
|
end
|
40
40
|
|
41
41
|
module Renderers
|
42
|
-
autoload :Fable,
|
43
|
-
autoload :
|
42
|
+
autoload :Fable, 'codeless_code/renderers/fable'
|
43
|
+
autoload :TermPage, 'codeless_code/renderers/term_page'
|
44
44
|
end
|
45
45
|
|
46
46
|
VERSION = Pathname.new(__dir__).join('..', 'VERSION').read.strip.freeze
|
@@ -68,8 +68,12 @@ module CodelessCode
|
|
68
68
|
|
69
69
|
o.separator ''
|
70
70
|
o.separator 'Options'
|
71
|
-
o.
|
71
|
+
o.boolean '-c', '--columns', 'when listing fables, format the output ' \
|
72
|
+
'into columns'
|
73
|
+
o.array '-e', '--headers', 'headers to include in the list output. ' \
|
74
|
+
'may be repeated'
|
72
75
|
o.string '-f', '--format', 'one of: raw, plain, or term (default)'
|
76
|
+
o.string '-o', '--output', 'write to the given file. "-" for STDOUT'
|
73
77
|
o.string '-p', '--path', 'path to directory of fables. ' \
|
74
78
|
'see github.com/aldesantis/the-codeless-code'
|
75
79
|
o.boolean '--random', 'select one fable, randomly, from the filtered list'
|
@@ -78,6 +82,9 @@ module CodelessCode
|
|
78
82
|
'based on today\'s date'
|
79
83
|
o.boolean '--trace', 'print full error message if a fable fails to parse'
|
80
84
|
|
85
|
+
o.string '-s', '--sort', 'when listing fables, sort by the given header'
|
86
|
+
o.boolean '-r', '--reverse', 'when listing fables, reverse the order'
|
87
|
+
|
81
88
|
o.separator ''
|
82
89
|
o.separator 'Series Filters'
|
83
90
|
o.string '-S', '--series'
|
@@ -108,9 +115,9 @@ module CodelessCode
|
|
108
115
|
|
109
116
|
o.separator ''
|
110
117
|
o.separator 'Translator Filters'
|
111
|
-
o.string '-
|
118
|
+
o.string '-R', '--translator', 'translator\'s name (default: first ' \
|
112
119
|
'one, alphabetically)'
|
113
|
-
o.string '-
|
120
|
+
o.string '-Rx', '--translator-exact', 'translator\'s name, ' \
|
114
121
|
'case-sensitive (default: first one, alphabetically)'
|
115
122
|
|
116
123
|
o.separator ''
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
2
|
+
# Copyright (C) 2018 Jon Sangster
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License along with
|
15
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
+
require 'helper'
|
17
|
+
|
18
|
+
class TestCatalog < UnitTest
|
19
|
+
def test_languages
|
20
|
+
assert_kind_of Enumerable, catalog.languages
|
21
|
+
|
22
|
+
catalog.languages.each do |lang|
|
23
|
+
assert_kind_of Symbol, lang
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_fetch
|
28
|
+
assert_kind_of LanguageSet, catalog.fetch(:en)
|
29
|
+
assert_equal :en, catalog.fetch(:en).lang
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_fetch_not_found
|
33
|
+
assert_raises(LanguageSet::NotFoundError) { catalog.fetch(:unknown) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_language_sets
|
37
|
+
assert_kind_of Enumerable, catalog.language_sets
|
38
|
+
catalog.language_sets.each { |set| assert_kind_of LanguageSet, set }
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_fable_sets
|
42
|
+
assert_kind_of Enumerable, catalog.fable_sets
|
43
|
+
catalog.fable_sets.each { |set| assert_kind_of FableSet, set }
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_fables
|
47
|
+
assert_kind_of Enumerable, catalog.fables
|
48
|
+
catalog.fables.each { |fable| assert_kind_of Fable, fable }
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_select
|
52
|
+
all_filter = ->(fable) { true }
|
53
|
+
none_filter = ->(fable) { false }
|
54
|
+
|
55
|
+
assert_kind_of Enumerable, catalog.select(all_filter)
|
56
|
+
catalog.select(all_filter).each { |fable| assert_kind_of Fable, fable }
|
57
|
+
|
58
|
+
assert_equal catalog.fables.size, catalog.select(all_filter).size
|
59
|
+
assert_equal 0, catalog.select(none_filter).size
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def catalog(dir = DEFAULT_DATA)
|
65
|
+
(@catalog ||= {})[dir] ||= Catalog.new(dir)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -17,21 +17,34 @@ require 'helper'
|
|
17
17
|
|
18
18
|
class TestFableSet < UnitTest
|
19
19
|
def test_lang
|
20
|
+
assert_kind_of Symbol, set.lang
|
21
|
+
|
20
22
|
assert_equal :en, set('en-*').lang
|
21
23
|
assert_equal :zh, set('zh-*').lang
|
22
24
|
end
|
23
25
|
|
24
26
|
def test_translator
|
27
|
+
assert_kind_of String, set.translator
|
28
|
+
|
25
29
|
assert_equal 'qi', set('*-qi').translator
|
26
30
|
assert_equal 'hanzik', set('*-hanzik').translator
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
30
|
-
assert_kind_of Enumerable, set.
|
33
|
+
def test_fables
|
34
|
+
assert_kind_of Enumerable, set.fables
|
35
|
+
|
36
|
+
set.fables.each { |fable| assert_kind_of Fable, fable }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_filter
|
40
|
+
all_filter = ->(fable) { true }
|
41
|
+
none_filter = ->(fable) { false }
|
42
|
+
|
43
|
+
assert_kind_of Enumerable, set.filter(all_filter)
|
44
|
+
set.filter(all_filter).each { |fable| assert_kind_of Fable, fable }
|
31
45
|
|
32
|
-
set.
|
33
|
-
|
34
|
-
end
|
46
|
+
assert_equal set.fables.size, set.filter(all_filter).size
|
47
|
+
assert_equal 0, set.filter(none_filter).size
|
35
48
|
end
|
36
49
|
|
37
50
|
private
|
@@ -16,14 +16,6 @@
|
|
16
16
|
require 'helper'
|
17
17
|
|
18
18
|
class TestLanguageSet < UnitTest
|
19
|
-
def test_class_available_languages
|
20
|
-
assert_kind_of Enumerable, LanguageSet.available_languages
|
21
|
-
|
22
|
-
LanguageSet.available_languages.each do |lang|
|
23
|
-
assert_kind_of Symbol, lang
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
19
|
def test_lang
|
28
20
|
assert_equal :en, set(:en).lang
|
29
21
|
assert_equal :zh, set(:zh).lang
|
@@ -32,23 +24,14 @@ class TestLanguageSet < UnitTest
|
|
32
24
|
def test_fable_sets
|
33
25
|
assert_kind_of Enumerable, set.fable_sets
|
34
26
|
refute_empty set.fable_sets
|
35
|
-
assert_equal set.fable_sets.size, set.dirs.size
|
36
27
|
|
37
28
|
set.fable_sets.each { |set| assert_kind_of FableSet, set }
|
38
29
|
end
|
39
30
|
|
40
|
-
def test_dirs
|
41
|
-
assert_kind_of Enumerable, set.dirs
|
42
|
-
|
43
|
-
set.dirs.each do |entry|
|
44
|
-
flunk format('%p is not a directory', entry) unless entry.directory?
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
31
|
private
|
49
32
|
|
50
33
|
def set(lang = :en)
|
51
|
-
(@set ||= {})[lang] ||= LanguageSet.new(lang)
|
34
|
+
(@set ||= {})[lang] ||= LanguageSet.new(lang, root_dir: DEFAULT_DATA)
|
52
35
|
end
|
53
36
|
end
|
54
37
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
2
|
+
# Copyright (C) 2018 Jon Sangster
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License along with
|
15
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
+
require 'helper'
|
17
|
+
|
18
|
+
class TestOptions < UnitTest
|
19
|
+
def test_opts
|
20
|
+
assert_kind_of Slop::Result, options.opts
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_opts_does_not_accept_bad_options
|
24
|
+
assert_raises { options('--unknown-argument').opts }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_args
|
28
|
+
assert_empty options.args
|
29
|
+
assert_empty options('--daily').args
|
30
|
+
assert_equal ['123'], options('-D', '2015', '123').args
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_args_too_many
|
34
|
+
assert_raises { options('first', 'second').opts }
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_help
|
38
|
+
assert_kind_of String, options.help
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_help_accepts_bad_options
|
42
|
+
options('--unknown-argument').help
|
43
|
+
pass 'did not raise'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_key?
|
47
|
+
assert options('--help').key?(:help), '--help should supply :help key'
|
48
|
+
assert options('-h').key?(:help), '-h should supply :help key'
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_data
|
52
|
+
assert_equal Pathname.new('/foo'), options('-p', '/foo').data_dir
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_data_dir_default
|
56
|
+
assert_equal DEFAULT_DATA, options.data_dir
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def options(*args)
|
62
|
+
(@options ||= {})[args] ||= Options.new('codeless_code', args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/test/helper.rb
CHANGED
@@ -23,12 +23,13 @@ end
|
|
23
23
|
|
24
24
|
SimpleCov.configure do
|
25
25
|
clean_filters
|
26
|
-
|
26
|
+
load_profile 'test_frameworks'
|
27
27
|
end
|
28
28
|
|
29
29
|
ENV['COVERAGE'] && SimpleCov.start do
|
30
|
-
add_filter
|
30
|
+
add_filter %r{/\..*/}
|
31
31
|
end
|
32
|
+
|
32
33
|
require 'rubygems'
|
33
34
|
require 'bundler'
|
34
35
|
begin
|
@@ -45,7 +46,6 @@ require 'minitest/autorun'
|
|
45
46
|
|
46
47
|
Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new)
|
47
48
|
|
48
|
-
$LOAD_PATH.unshift(__dir__)
|
49
49
|
$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))
|
50
50
|
require 'pry-byebug'
|
51
51
|
require 'codeless_code'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: codeless_code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Sangster
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -1726,10 +1726,12 @@ files:
|
|
1726
1726
|
- lib/codeless_code/language_set.rb
|
1727
1727
|
- lib/codeless_code/options.rb
|
1728
1728
|
- lib/codeless_code/renderers/fable.rb
|
1729
|
-
- lib/codeless_code/renderers/
|
1729
|
+
- lib/codeless_code/renderers/term_page.rb
|
1730
|
+
- test/codeless_code/test_catalog.rb
|
1730
1731
|
- test/codeless_code/test_fable.rb
|
1731
1732
|
- test/codeless_code/test_fable_set.rb
|
1732
1733
|
- test/codeless_code/test_language_set.rb
|
1734
|
+
- test/codeless_code/test_options.rb
|
1733
1735
|
- test/helper.rb
|
1734
1736
|
- test/test_codeless_code.rb
|
1735
1737
|
homepage: https://github.com/sangster/codeless_code
|