codeless_code 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|