text-metrics 1.0.0.beta2 → 1.1.0
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/CHANGELOG.md +11 -0
- data/README.md +46 -5
- data/lib/generators/text_metrics/install_generator.rb +22 -0
- data/lib/text_metrics/configuration.rb +11 -0
- data/lib/text_metrics/processors/base.rb +35 -31
- data/lib/text_metrics/railtie.rb +9 -0
- data/lib/text_metrics/version.rb +1 -1
- data/lib/text_metrics.rb +21 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7420551fa74c4621b2ce5d2d8d1e83a5bb46a1790c4bb340ed2556cf08b7bc1
|
|
4
|
+
data.tar.gz: 599106caafbdb730d6ea72d532afdc5f2e7b5bf69402d73f8c782523ed49c6c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e35abe082c3a30179a7a1c171c80ae997690b609bd81ec21ea5f12d4b506e5cdad87802bd89d07867d193bd499f7a91e6f3fa1f05d9e11d9c8ab59c763413f1b
|
|
7
|
+
data.tar.gz: b9b9a018a2dc761540358386586923826ee5defd1fc5426a39d01acc2c5757de53c69629ec88daee674c0d6f14e9e2ba0c8b112e5a40348d52aea013f9815298
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
3
|
## main
|
|
4
|
+
- ... TBD ...
|
|
5
|
+
|
|
6
|
+
## 1.1.0
|
|
7
|
+
- Accept `:en` as an alias for `:en_us` in `TextMetrics.new`.
|
|
8
|
+
- Add five metrics to the analyzer and `#to_h`: `automated_readability_index`,
|
|
9
|
+
`type_token_ratio` (lexical diversity), `reading_time` (minutes, with a configurable
|
|
10
|
+
`wpm:`), and the previously-internal `polysyllabic_words_count` and `long_words_count`.
|
|
11
|
+
|
|
12
|
+
## 1.0.0
|
|
13
|
+
- First public release.
|
|
14
|
+
- see `UPGRADING.md` for migration details from the pre-release `0.x` API.
|
|
4
15
|
|
|
5
16
|
## ## 1.0.0.beta2
|
|
6
17
|
- Add `gunning_fog_index` to the analyzer metrics and `#to_h` output.
|
data/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Text Metrics is a Ruby library for analysing text. It was inspired by the Python
|
|
|
8
8
|
|
|
9
9
|
It gives you the everyday counts you need — words, characters, sentences and syllables — plus readability scores such as Flesch Reading Ease, Flesch-Kincaid Grade Level, SMOG, Gunning Fog, Coleman-Liau Index and LIX. English and French are supported.
|
|
10
10
|
|
|
11
|
+
It is battle-tested in production on millions of student writings at [Plume](https://plume-app.co).
|
|
12
|
+
|
|
11
13
|
## Accurate, dictionary-based syllable counting
|
|
12
14
|
|
|
13
15
|
Readability scores such as Flesch, Flesch-Kincaid and SMOG depend heavily on syllable counts. Many libraries estimate those counts with hyphenation rules, which are fast but often wrong. Text Metrics starts from real pronunciation data instead:
|
|
@@ -24,6 +26,8 @@ That makes syllable-sensitive scores more reliable, especially for longer or les
|
|
|
24
26
|
- **Flesch-Kincaid Grade** maps to a US school grade. It uses the same formula for every language because there is no validated French adaptation.
|
|
25
27
|
- **Gunning Fog** uses words with three or more syllables as complex words, matching the same syllable counts used by SMOG.
|
|
26
28
|
- **Coleman-Liau** counts alphabetic letters only (not digits or punctuation), per its definition.
|
|
29
|
+
- **Automated Readability Index** counts characters (letters, digits and punctuation, spaces excluded), per its original "strokes" definition — so it uses `characters_count`, unlike Coleman-Liau which uses letters only.
|
|
30
|
+
- **Type-token ratio** is distinct-words over total-words and is sensitive to text length: longer texts trend lower because words repeat. Compare it only across texts of similar length.
|
|
27
31
|
|
|
28
32
|
## Features
|
|
29
33
|
|
|
@@ -32,10 +36,14 @@ _Basic metrics:_
|
|
|
32
36
|
- [x] word count
|
|
33
37
|
- [x] character count
|
|
34
38
|
- [x] sentence count
|
|
39
|
+
- [x] polysyllabic word count (three or more syllables)
|
|
40
|
+
- [x] long word count (more than six characters)
|
|
35
41
|
- [x] average syllables per word
|
|
36
42
|
- [x] average letters per word
|
|
37
43
|
- [x] average words per sentence
|
|
38
44
|
- [x] average characters per sentence
|
|
45
|
+
- [x] type-token ratio (lexical diversity)
|
|
46
|
+
- [x] reading time (minutes, configurable words-per-minute)
|
|
39
47
|
|
|
40
48
|
_Readability tests:_
|
|
41
49
|
|
|
@@ -45,6 +53,7 @@ _Readability tests:_
|
|
|
45
53
|
- [x] Gunning Fog Index
|
|
46
54
|
- [x] Coleman-Liau Index
|
|
47
55
|
- [x] Lix Index
|
|
56
|
+
- [x] Automated Readability Index
|
|
48
57
|
|
|
49
58
|
## Installation
|
|
50
59
|
|
|
@@ -52,7 +61,7 @@ Text Metrics is not published to RubyGems yet. For now, install it from GitHub:
|
|
|
52
61
|
|
|
53
62
|
```ruby
|
|
54
63
|
# Gemfile
|
|
55
|
-
gem "text-metrics",
|
|
64
|
+
gem "text-metrics", "~> 1.1.0"
|
|
56
65
|
```
|
|
57
66
|
|
|
58
67
|
### Supported Ruby versions
|
|
@@ -68,11 +77,13 @@ metrics = TextMetrics.new("This gem analyses all kinds of text.")
|
|
|
68
77
|
metrics.to_h
|
|
69
78
|
# {
|
|
70
79
|
# words_count: 7, characters_count: 30, sentences_count: 1, syllables_count: 10,
|
|
71
|
-
# punctuation_count: 1,
|
|
80
|
+
# punctuation_count: 1, polysyllabic_words_count: 1, long_words_count: 1,
|
|
81
|
+
# syllables_per_word_average: 1.4, letters_per_word_average: 4.14,
|
|
72
82
|
# words_per_sentence_average: 7.0, characters_per_sentence_average: 30.0,
|
|
73
83
|
# words_per_punctuation_average: 7.0, punctuation_per_sentence_average: 1.0,
|
|
74
|
-
# flesch_reading_ease: 78.87, flesch_kincaid_grade: 4.0,
|
|
75
|
-
# smog_index: 0.0, gunning_fog_index: 8.5, coleman_liau_index: 4.33
|
|
84
|
+
# type_token_ratio: 1.0, flesch_reading_ease: 78.87, flesch_kincaid_grade: 4.0,
|
|
85
|
+
# lix: 21.29, smog_index: 0.0, gunning_fog_index: 8.5, coleman_liau_index: 4.33,
|
|
86
|
+
# automated_readability_index: 2.3, reading_time: 0.04
|
|
76
87
|
# }
|
|
77
88
|
|
|
78
89
|
# Or ask for a single metric:
|
|
@@ -81,18 +92,48 @@ metrics.characters_count # => 30
|
|
|
81
92
|
metrics.flesch_reading_ease # => 78.87
|
|
82
93
|
metrics.flesch_kincaid_grade # => 4.0
|
|
83
94
|
metrics.gunning_fog_index # => 8.5
|
|
95
|
+
|
|
96
|
+
# Reading time is in minutes; pass a custom reading pace if you like:
|
|
97
|
+
metrics.reading_time # => 0.04 (at the default 200 wpm)
|
|
98
|
+
metrics.reading_time(wpm: 130) # => 0.05
|
|
84
99
|
```
|
|
85
100
|
|
|
86
101
|
### Languages
|
|
87
102
|
|
|
88
|
-
American English (`:en_us`) is the default. To analyse French text, pass `language: :fr`:
|
|
103
|
+
American English (`:en_us`) is the default. `:en` is accepted as an alias for `:en_us`. To analyse French text, pass `language: :fr`:
|
|
89
104
|
|
|
90
105
|
```ruby
|
|
91
106
|
TextMetrics.new("Bonjour le monde.", language: :fr)
|
|
107
|
+
TextMetrics.new("Hello.", language: :en) # same as :en_us
|
|
92
108
|
```
|
|
93
109
|
|
|
94
110
|
Unsupported languages raise `TextMetrics::Error`.
|
|
95
111
|
|
|
112
|
+
### Configuration
|
|
113
|
+
|
|
114
|
+
Global defaults can be set via `TextMetrics.configure`. Call this once at boot time — in a Rails initializer, for example:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
TextMetrics.configure do |config|
|
|
118
|
+
# Words per minute used by #reading_time (default: 200).
|
|
119
|
+
config.wpm = 250
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
In a **Rails app**, generate the initializer with:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
rails generate text_metrics:install
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This creates `config/initializers/text_metrics.rb` with the options commented out at their defaults.
|
|
130
|
+
|
|
131
|
+
Per-call overrides are still supported and always take precedence over the global config:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
metrics.reading_time(wpm: 130)
|
|
135
|
+
```
|
|
136
|
+
|
|
96
137
|
### Comparing two texts
|
|
97
138
|
|
|
98
139
|
Levenshtein distance compares two strings, so it is exposed on the `TextMetrics` module:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module TextMetrics
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
desc "Creates a TextMetrics initializer in config/initializers"
|
|
9
|
+
|
|
10
|
+
def create_initializer
|
|
11
|
+
create_file "config/initializers/text_metrics.rb", <<~RUBY
|
|
12
|
+
# frozen_string_literal: true
|
|
13
|
+
|
|
14
|
+
TextMetrics.configure do |config|
|
|
15
|
+
# Words per minute used to compute reading_time (default: 200).
|
|
16
|
+
# config.wpm = 200
|
|
17
|
+
end
|
|
18
|
+
RUBY
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -7,26 +7,30 @@ module TextMetrics
|
|
|
7
7
|
class Base
|
|
8
8
|
GEM_PATH = File.dirname(__FILE__, 2).freeze
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
# from this list, so they can never drift apart.
|
|
10
|
+
# Single source of truth: #to_h and reader methods are both derived from this list.
|
|
12
11
|
METRICS = %i[
|
|
13
12
|
words_count
|
|
14
13
|
characters_count
|
|
15
14
|
sentences_count
|
|
16
15
|
syllables_count
|
|
17
16
|
punctuation_count
|
|
17
|
+
polysyllabic_words_count
|
|
18
|
+
long_words_count
|
|
18
19
|
syllables_per_word_average
|
|
19
20
|
letters_per_word_average
|
|
20
21
|
words_per_sentence_average
|
|
21
22
|
characters_per_sentence_average
|
|
22
23
|
words_per_punctuation_average
|
|
23
24
|
punctuation_per_sentence_average
|
|
25
|
+
type_token_ratio
|
|
24
26
|
flesch_reading_ease
|
|
25
27
|
flesch_kincaid_grade
|
|
26
28
|
lix
|
|
27
29
|
smog_index
|
|
28
30
|
gunning_fog_index
|
|
29
31
|
coleman_liau_index
|
|
32
|
+
automated_readability_index
|
|
33
|
+
reading_time
|
|
30
34
|
].freeze
|
|
31
35
|
|
|
32
36
|
attr_reader :text, :language
|
|
@@ -36,8 +40,6 @@ module TextMetrics
|
|
|
36
40
|
@language = language
|
|
37
41
|
end
|
|
38
42
|
|
|
39
|
-
# Every metric in one hash. Single source of truth for the public surface.
|
|
40
|
-
# Memoized — the analyzer is immutable once built.
|
|
41
43
|
def to_h
|
|
42
44
|
@to_h ||= METRICS.to_h { |metric| [metric, public_send(metric)] }
|
|
43
45
|
end
|
|
@@ -65,8 +67,14 @@ module TextMetrics
|
|
|
65
67
|
punctuation_marks.size
|
|
66
68
|
end
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
def polysyllabic_words_count
|
|
71
|
+
words.count { |word| count_syllables_in_word(word) >= 3 }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def long_words_count
|
|
75
|
+
words.count { |word| word.length > 6 }
|
|
76
|
+
end
|
|
77
|
+
|
|
70
78
|
def syllables_per_word_average
|
|
71
79
|
average_syllables_per_word.round(1)
|
|
72
80
|
end
|
|
@@ -97,16 +105,16 @@ module TextMetrics
|
|
|
97
105
|
(punctuation_count.to_f / sentences_count).round(2)
|
|
98
106
|
end
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
def type_token_ratio
|
|
109
|
+
return 0.0 if words_count.zero?
|
|
110
|
+
|
|
111
|
+
(words.uniq.size.to_f / words_count).round(2)
|
|
112
|
+
end
|
|
102
113
|
|
|
103
|
-
# Language-specific; subclasses supply the constants.
|
|
104
114
|
def flesch_reading_ease
|
|
105
115
|
raise NotImplementedError
|
|
106
116
|
end
|
|
107
117
|
|
|
108
|
-
# Flesch-Kincaid Grade Level (US school grade). The same formula is used for every
|
|
109
|
-
# language — there is no validated non-English adaptation.
|
|
110
118
|
def flesch_kincaid_grade
|
|
111
119
|
return 0.0 if words_count.zero?
|
|
112
120
|
|
|
@@ -116,7 +124,7 @@ module TextMetrics
|
|
|
116
124
|
def smog_index
|
|
117
125
|
return 0.0 if sentences_count < 3
|
|
118
126
|
|
|
119
|
-
(1.043 * Math.sqrt(30.0 *
|
|
127
|
+
(1.043 * Math.sqrt(30.0 * polysyllabic_words_count / sentences_count) + 3.1291).round(1)
|
|
120
128
|
rescue ZeroDivisionError
|
|
121
129
|
0.0
|
|
122
130
|
end
|
|
@@ -124,7 +132,7 @@ module TextMetrics
|
|
|
124
132
|
def gunning_fog_index
|
|
125
133
|
return 0.0 if words_count.zero?
|
|
126
134
|
|
|
127
|
-
(0.4 * (average_words_per_sentence + 100.0 *
|
|
135
|
+
(0.4 * (average_words_per_sentence + 100.0 * polysyllabic_words_count / words_count)).round(1)
|
|
128
136
|
end
|
|
129
137
|
|
|
130
138
|
def coleman_liau_index
|
|
@@ -139,14 +147,24 @@ module TextMetrics
|
|
|
139
147
|
def lix
|
|
140
148
|
return 0.0 if words_count.zero?
|
|
141
149
|
|
|
142
|
-
|
|
150
|
+
(average_words_per_sentence + 100.0 * long_words_count / words_count).round(2)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def automated_readability_index
|
|
154
|
+
return 0.0 if words_count.zero?
|
|
155
|
+
|
|
156
|
+
(4.71 * characters_count / words_count.to_f + 0.5 * average_words_per_sentence - 21.43).round(1)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def reading_time(wpm: nil)
|
|
160
|
+
wpm ||= TextMetrics.configuration.wpm
|
|
161
|
+
return 0.0 if words_count.zero? || wpm.zero?
|
|
143
162
|
|
|
144
|
-
(
|
|
163
|
+
(words_count / wpm.to_f).round(2)
|
|
145
164
|
end
|
|
146
165
|
|
|
147
166
|
private
|
|
148
167
|
|
|
149
|
-
# full-precision ratios feeding the readability formulas
|
|
150
168
|
def average_syllables_per_word
|
|
151
169
|
return 0.0 if words_count.zero?
|
|
152
170
|
|
|
@@ -165,34 +183,20 @@ module TextMetrics
|
|
|
165
183
|
words_count.to_f / sentences_count
|
|
166
184
|
end
|
|
167
185
|
|
|
168
|
-
# Count of alphabetic characters only (letters), as required by Coleman-Liau and the
|
|
169
|
-
# letters-per-word metric — distinct from #characters_count, which includes digits
|
|
170
|
-
# and punctuation.
|
|
171
186
|
def letters_count
|
|
172
187
|
@letters_count ||= text.scan(/[[:alpha:]]/).size
|
|
173
188
|
end
|
|
174
189
|
|
|
175
|
-
# Subclasses provide the language-specific syllable counting.
|
|
176
190
|
def count_syllables_in_word(word)
|
|
177
191
|
raise NotImplementedError
|
|
178
192
|
end
|
|
179
193
|
|
|
180
|
-
def count_polysyllabic_words
|
|
181
|
-
words.count { |word| count_syllables_in_word(word) >= 3 }
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# tokenizers
|
|
185
194
|
def punctuation_marks
|
|
186
195
|
@punctuation_marks ||= text.scan(/[.,!?;:]/)
|
|
187
196
|
end
|
|
188
197
|
|
|
189
198
|
def words
|
|
190
|
-
@words ||=
|
|
191
|
-
normalized_text = text.downcase.strip
|
|
192
|
-
|
|
193
|
-
# Split into words, including hyphenated words, and excluding numbers
|
|
194
|
-
normalized_text.scan(/\b[A-Za-zÀ-ÖØ-öø-ÿ'-]+\b/)
|
|
195
|
-
end
|
|
199
|
+
@words ||= text.downcase.strip.scan(/\b[A-Za-zÀ-ÖØ-öø-ÿ'-]+\b/)
|
|
196
200
|
end
|
|
197
201
|
|
|
198
202
|
def sentences
|
data/lib/text_metrics/version.rb
CHANGED
data/lib/text_metrics.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "text_metrics/version"
|
|
4
|
+
require "text_metrics/configuration"
|
|
5
|
+
require "text_metrics/railtie" if defined?(Rails::Railtie)
|
|
4
6
|
require "text_metrics/levenshtein"
|
|
5
7
|
require "text_metrics/processors/american_english"
|
|
6
8
|
require "text_metrics/processors/french"
|
|
@@ -10,6 +12,22 @@ module TextMetrics
|
|
|
10
12
|
|
|
11
13
|
DEFAULT_LANGUAGE = :en_us
|
|
12
14
|
|
|
15
|
+
class << self
|
|
16
|
+
def configuration
|
|
17
|
+
@configuration ||= Configuration.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
yield configuration
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def reset_configuration!
|
|
27
|
+
@configuration = Configuration.new
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
13
31
|
PROCESSORS = {
|
|
14
32
|
en_us: Processors::AmericanEnglish,
|
|
15
33
|
fr: Processors::French
|
|
@@ -34,8 +52,11 @@ module TextMetrics
|
|
|
34
52
|
|
|
35
53
|
# Coerce to a known language symbol, or raise a helpful error.
|
|
36
54
|
# Handles nil, strings and symbols without leaking a NoMethodError.
|
|
55
|
+
LANGUAGE_ALIASES = {en: :en_us}.freeze
|
|
56
|
+
|
|
37
57
|
def self.resolve_language(language)
|
|
38
58
|
resolved = language.to_s.to_sym
|
|
59
|
+
resolved = LANGUAGE_ALIASES.fetch(resolved, resolved)
|
|
39
60
|
return resolved if PROCESSORS.key?(resolved)
|
|
40
61
|
|
|
41
62
|
raise Error, "Unknown language #{language.inspect}. Available languages: #{PROCESSORS.keys.join(", ")}"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: text-metrics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrien POLY
|
|
@@ -120,14 +120,17 @@ files:
|
|
|
120
120
|
- LICENSE.txt
|
|
121
121
|
- README.md
|
|
122
122
|
- UPGRADING.md
|
|
123
|
+
- lib/generators/text_metrics/install_generator.rb
|
|
123
124
|
- lib/text-metrics.rb
|
|
124
125
|
- lib/text_metrics.rb
|
|
126
|
+
- lib/text_metrics/configuration.rb
|
|
125
127
|
- lib/text_metrics/dictionaries/english_word_syllable_database.txt
|
|
126
128
|
- lib/text_metrics/dictionaries/french_word_syllable_exceptions.yml
|
|
127
129
|
- lib/text_metrics/levenshtein.rb
|
|
128
130
|
- lib/text_metrics/processors/american_english.rb
|
|
129
131
|
- lib/text_metrics/processors/base.rb
|
|
130
132
|
- lib/text_metrics/processors/french.rb
|
|
133
|
+
- lib/text_metrics/railtie.rb
|
|
131
134
|
- lib/text_metrics/version.rb
|
|
132
135
|
homepage: https://github.com/plume-app/text-metrics
|
|
133
136
|
licenses:
|