Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Metrics/BlockLength:
- 'test/**/*'
- 'lib/classifier/extensions/vector.rb'
- 'lib/classifier/cli.rb'
- 'lib/classifier/keywords/cli.rb'

# Allow longer methods in complex algorithms (SVD, etc.) and CLI
Metrics/MethodLength:
Expand All @@ -39,6 +40,7 @@ Metrics/MethodLength:
- 'lib/classifier/extensions/vector.rb'
- 'lib/classifier/lsi/content_node.rb'
- 'lib/classifier/cli.rb'
- 'lib/classifier/keywords/cli.rb'

# Allow higher complexity for mathematical algorithms and CLI
Metrics/AbcSize:
Expand All @@ -49,6 +51,7 @@ Metrics/AbcSize:
- 'lib/classifier/lsi.rb'
- 'lib/classifier/lsi/content_node.rb'
- 'lib/classifier/cli.rb'
- 'lib/classifier/keywords/cli.rb'

Metrics/CyclomaticComplexity:
Max: 10
Expand All @@ -63,6 +66,7 @@ Metrics/PerceivedComplexity:
- 'lib/classifier/extensions/vector.rb'
- 'lib/classifier/lsi/content_node.rb'
- 'lib/classifier/cli.rb'
- 'lib/classifier/keywords/cli.rb'

# Class length limits - algorithms, tests and CLI can be longer
Metrics/ClassLength:
Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,49 @@ classifier "Great product, highly recommend"
# => positive
```

Extract keywords and analyze term importance using TF-IDF instantly:

```bash
# Extract from a raw string
keywords "Ruby is a programming language"
# => ruby:0.52 programming:0.41 language:0.38

# Extract from a file
keywords extract article.txt
# => machine:0.61 learning:0.58 neural:0.45 network:0.42

# Pipeline with stdin and web data
curl -s https://example.com/article | keywords extract

# Get top 5 terms only
keywords -n 5 "long document with many terms..."

# Use a custom model file
keywords -m custom_model.json "Ruby is a programming language"
```

Build your own vocabulary (fit data):
```bash
# Fit from multiple files
keywords fit corpus/*.txt

# Fit from stdin (each line is treated as a separate document)
cat documents.txt | keywords fit

# Tune vocabulary filters during fitting
keywords fit --min-df 2 --max-df 0.85 --ngram 1,2 corpus/*.txt
```

Inspect your model:
```bash
# Check model statistics and parameters
keywords info
# => Documents: 1,234
# => Vocabulary: 5,678
# => Min DF: 1
# => Max DF: 1.0
```

[CLI Guide →](https://rubyclassifier.com/docs/guides/cli/basics)

### Claude Code Plugin
Expand Down
2 changes: 1 addition & 1 deletion classifier.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Gem::Specification.new do |s|
s.required_ruby_version = '>= 3.1'
s.files = Dir['{lib,sig,exe}/**/*.{rb,rbs}', 'ext/**/*.{c,h,rb}', 'exe/*', 'bin/*', 'LICENSE', '*.md', 'test/*']
s.bindir = 'exe'
s.executables = ['classifier']
s.executables = %w[classifier keywords]
s.extensions = ['ext/classifier/extconf.rb']
s.license = 'LGPL'

Expand Down
1 change: 0 additions & 1 deletion exe/classifier
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Force UTF-8 encoding for proper handling of model data and user input
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

Expand Down
15 changes: 15 additions & 0 deletions exe/keywords
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Force UTF-8 encoding for proper handling of model data and user input
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
Comment thread
Yegorov marked this conversation as resolved.

require 'classifier/keywords/cli'

result = Classifier::Keywords::CLI.new(ARGV).run
Comment thread
Yegorov marked this conversation as resolved.

warn result[:error] unless result[:error].empty?
puts result[:output] unless result[:output].empty?

exit result[:exit_code]
20 changes: 20 additions & 0 deletions lib/classifier/extensions/word_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ def clean_word_hash(min_word_length = 3)
word_hash_for_words(gsub(/[^\w\s]/, '').split, min_word_length)
end

# Builds a mapping between stemmed roots and their most frequent original words.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [82/80]

# @rbs (?Integer) -> Hash[Symbol, String]
def stem_to_word_hash(min_word_length = 3)
mapping_stem_to_word_for_words(gsub(/[^\w\s]/, '').split, min_word_length)
end

private

# @rbs (Array[String], Integer) -> Hash[Symbol, Integer]
Expand All @@ -54,6 +60,20 @@ def word_hash_for_symbols(words)
d
end

# @rbs (Array[String], Integer) -> Hash[Symbol, Integer]
def mapping_stem_to_word_for_words(words, min_word_length)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/AbcSize: Assignment Branch Condition size for mapping_stem_to_word_for_words is too high. [17.97/15]

h = {}
words.map { _1.tap(&:downcase!) }.tally.each do |word, count|
next unless !CORPUS_SKIP_WORDS.include?(word) && word.length >= min_word_length

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [85/80]


stem = word.stem.intern
h[stem] ||= [word, count]
h[stem] = [word, count] if h.dig(stem, 1) < count
end
h.each_key { |k| h[k] = h[k].first }
h
end

CORPUS_SKIP_WORDS = ::Set.new(%w[
a
again
Expand Down
Loading
Loading