this_is_ruby 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a2047298c7f0c4729968ed6827707200b80ad5810ccde2c5fcd6fb009d4b662
4
- data.tar.gz: 541e8c560fdb8441a4aa4ec0f040827a10454feb8bd467cad44840a614a05455
3
+ metadata.gz: 70cb32fbaddf28dbdae4e21b4b8e009fbda116b02c967ddf08b9618db5df4a0c
4
+ data.tar.gz: fe332fb48d5d355a59cb13b7ff1efe4aa403f2ed031aaa5e7f66924bd179bb62
5
5
  SHA512:
6
- metadata.gz: f09cc8394377d79592a9e8e845a6da04c1dd4fcfa113ebebcf0460af1d8cc3b8c240f8e944048b9a471790d926e1d49123533546aaa093b111598255173de0f4
7
- data.tar.gz: f4eeae23cde25381e1fd4132f6904e7c4f5a07a1980a8e7855e115d5532ad8c84fb6b25af916264b786e2ec64f2e93f1679597edddfe70c7c7251aea287f35c2
6
+ metadata.gz: 71f21c362ac2d542b6329f3082dd6de70b6f6b37d2c78c00a0a334ba371d20b76093673f23cb18bd5d9b86838c6c1647d4cf0527fc29f474794dbfc04d783a48
7
+ data.tar.gz: 28d66d7abd69c80304bc01ee5c848cbc35b918854c014dd81b38ed859a4e9dc985c00834a954b3a171b3a4ee88ded33794862c1a860350efc522fd3d9ff916e6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Frontend sources are vendored by default. Telling GitHub a project is Ruby is
6
+ what the gem is for, and it read oddly to name that after a flag most people
7
+ would never pass. `--fair` keeps them counted, marking only what a tool wrote
8
+ or a generator copied in, for a repository whose frontend really is the point. `--all-frontend` is still accepted and does
9
+ nothing, so an existing CI step keeps working.
10
+ - Reword the summary and description.
11
+
12
+ ## 0.1.1
13
+
14
+ - The "now" column no longer counts files that are already out of GitHub's
15
+ language bar. It asks `git check-attr`, so Rails' own `db/schema.rb` line and
16
+ anything written by hand are honoured, and it subtracts only what this run
17
+ claims, so re-running on a repository already converted still shows the
18
+ contrast rather than reporting that nothing would change.
19
+
3
20
  ## 0.1.0
4
21
 
5
22
  Initial release.
@@ -8,18 +25,8 @@ Initial release.
8
25
  Webpacker and Vite builds, SimpleCov reports, SQL structure dumps and
9
26
  shadcn/ui components.
10
27
  - Writes a managed block in `.gitattributes`, leaving the rest of the file
11
- untouched, and stays quiet about anything already declared there.
12
- - `plan`, `apply` and `check` commands; `--all-frontend` opt-in.
13
-
14
- ## Unreleased
15
-
16
- - Exit 1 on an unknown command or option instead of 0, so a typo in a CI step
17
- fails rather than silently skipping `check`.
18
- - Leave a path alone when its Linguist attribute is already decided, including
19
- when it was explicitly unset (`-linguist-generated`) or made unspecified
20
- (`!linguist-generated`). Writing our line after such a declaration reversed
21
- it, because the last match wins.
22
- - Rewrite the managed block where it already sits, so content below it stays
23
- below it.
24
- - Read `git rev-parse` from stdout alone: with `GIT_TRACE` set, or on any git
25
- warning, the merged stream became part of the repository root.
28
+ untouched, and stays quiet about anything already decided there, including an
29
+ attribute the owner explicitly unset.
30
+ - Rewrites the block where it sits, so content below it stays below it.
31
+ - `plan`, `apply` and `check` commands; `--all-frontend` opt-in, which uses
32
+ `linguist-vendored` so diffs keep working.
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # this_is_ruby
2
2
 
3
- GitHub decides a repository's language by counting bytes. Ruby code can often be outnumbered by sheer volume of JS/TS/HTML boilerplate. So GitHub labels the repo HTML or TypeScript.
3
+ [![Gem](https://img.shields.io/gem/v/this_is_ruby)](https://rubygems.org/gems/this_is_ruby)
4
+ [![CI](https://github.com/evilmartians/this_is_ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/evilmartians/this_is_ruby/actions/workflows/ci.yml)
4
5
 
5
- This gem gives you controls.
6
+ Ruby is one of the most eloquent and efficient languages, but it gets punished for it by GitHub's language attribution. A repository's language there is whichever one has the most bytes, and a Rails app, carefully designed to require next to no boilerplate code, easily ends up with less Ruby than JS, TS or HTML (ERB).
7
+
8
+ This gem tells GitHub: this is Ruby, by marking your frontend as `linguist-vendored`. No side effects on diffs or other DX.
6
9
 
7
10
  ## Install
8
11
 
@@ -12,6 +15,9 @@ group :development do
12
15
  end
13
16
  ```
14
17
 
18
+ Or `gem install this_is_ruby` to run it against any repository without adding
19
+ it to a project.
20
+
15
21
  ## Use
16
22
 
17
23
  ```console
@@ -34,24 +40,26 @@ $ this_is_ruby check # exit 1 when .gitattributes is out of date
34
40
  | SimpleCov HTML reports, found by the report's own title | `linguist-generated` |
35
41
  | `db/structure.sql`, matching how Rails already treats `db/schema.rb` | `linguist-generated` |
36
42
  | shadcn/ui components, when `components.json` shows a generator put them there | `linguist-vendored` |
43
+ | your frontend sources: `app/javascript/`, `app/frontend/`, `frontend/` | `linguist-vendored` |
37
44
 
38
45
 
39
- By default the gem only points at files a tool produced. On [inertia-rails/react-starter-kit][irsk], where GitHub counts TypeScript at 60.6%, that clears the HTML but leaves TypeScript the primary language.
46
+ [irinanazarova/react-starter-kit-this-is-ruby][demo] is a fork of the Inertia Rails starter kit whose only change is the sixteen lines `this_is_ruby` wrote. Upstream, GitHub counts it as 60.6% TypeScript and 21.2% Ruby. The fork reads **82.6% Ruby, 1.1% TypeScript**. Compare its language bar with [the upstream one][irsk].
40
47
 
41
- ## Make it Ruby
48
+ ## If your frontend really is the point
42
49
 
43
- If you think that despite a huge amount of hand-written JS/TS/HTML, the important work in your repository is Ruby, you can say so:
50
+ Some repositories genuinely are TypeScript projects, and for those:
44
51
 
45
52
  ```console
46
- $ this_is_ruby --all-frontend
53
+ $ this_is_ruby --fair
47
54
  ```
48
- On the same repository that marks `app/javascript/` as `linguist-vendored`, and
49
- GitHub then counts it as **82.6% Ruby, 1.1% TypeScript**. That is not an
50
- estimate: [irinanazarova/react-starter-kit-this-is-ruby][demo] is a fork of the
51
- kit whose only change is the sixteen lines this command wrote. Compare its
52
- language bar with [the upstream one][irsk].
53
55
 
54
- The one line that matters is which attribute it uses. [Generated files are suppressed in diffs][docs]; vendored files are not. So `--all-frontend` emits `linguist-vendored`, every source file keeps showing up in full in pull requests, and the only thing that changes is the color of the bar at the top of the page:
56
+ That marks only the files nobody in the project wrote: every generated row in
57
+ the table above, plus the shadcn components a generator copied in. Your own
58
+ frontend keeps counting.
59
+
60
+ ## Why vendored and not generated
61
+
62
+ The one line that matters is which attribute it uses. [Generated files are suppressed in diffs][docs]; vendored files are not. So your frontend is marked `linguist-vendored`, every source file keeps showing up in full in pull requests, and the only thing that changes is the color of the bar at the top of the page:
55
63
 
56
64
  ```console
57
65
  $ git check-attr linguist-vendored linguist-generated -- app/javascript/pages/home/index.tsx
@@ -71,8 +79,7 @@ On GitHub itself:
71
79
  plainly: these files "are suppressed in diffs". Reviewers get a "Load diff"
72
80
  button instead of the patch. The gem uses this attribute only for tool
73
81
  output; everything hand-written gets `linguist-vendored`, which leaves diffs
74
- alone. That is why `--all-frontend` vendors your frontend rather than
75
- marking it generated.
82
+ alone. That is why your frontend is vendored rather than marked generated.
76
83
  - **Code search still indexes the files.** They stay findable, and GitHub adds
77
84
  `is:generated` and `is:vendored` filters, so a query written as
78
85
  `-is:vendored` will skip them.
@@ -27,7 +27,7 @@ module ThisIsRuby
27
27
  @argv = argv
28
28
  @out = out
29
29
  @err = err
30
- @options = {path: ".", all_frontend: false, color: out.tty?}
30
+ @options = {path: ".", frontend: true, color: out.tty?}
31
31
  end
32
32
 
33
33
  def call
@@ -38,7 +38,7 @@ module ThisIsRuby
38
38
 
39
39
  repo = Repo.at(@options[:path])
40
40
  attributes = AttributesFile.new(repo.root.join(".gitattributes"))
41
- plan = Plan.build(repo, attributes:, all_frontend: @options[:all_frontend])
41
+ plan = Plan.build(repo, attributes:, frontend: @options[:frontend])
42
42
  report = Report.new(plan, out: @out, color: @options[:color])
43
43
 
44
44
  send(command, plan, attributes, report)
@@ -78,7 +78,10 @@ module ThisIsRuby
78
78
  parser = OptionParser.new do |opts|
79
79
  opts.banner = USAGE
80
80
  opts.on("--path DIR", "repository to inspect (default: .)") { |dir| @options[:path] = dir }
81
- opts.on("--all-frontend", "also mark hand-written frontend sources as vendored") { @options[:all_frontend] = true }
81
+ opts.on("--fair", "exclude only what a tool wrote or a generator copied in") { @options[:frontend] = false }
82
+ # 0.1.x asked for today's default with a flag. Accept the old name and
83
+ # do nothing, rather than break a CI step that still passes it.
84
+ opts.on("--all-frontend", "deprecated alias, now the default") { @options[:frontend] = true }
82
85
  opts.on("--[no-]color", "colourise output") { |on| @options[:color] = on }
83
86
  opts.on("-v", "--version", "print the version") do
84
87
  @out.puts VERSION
@@ -7,9 +7,9 @@ module ThisIsRuby
7
7
 
8
8
  attr_reader :repo, :emissions, :declared
9
9
 
10
- def self.build(repo, attributes:, all_frontend: false)
10
+ def self.build(repo, attributes:, frontend: true)
11
11
  spoken_for = spoken_for(attributes.existing_attributes)
12
- found = Rules.for_level(all_frontend:).flat_map { |rule| rule.apply(repo) }
12
+ found = Rules.for_level(frontend:).flat_map { |rule| rule.apply(repo) }
13
13
  declared, emissions = found.partition do |emission|
14
14
  spoken_for.any? { |pattern| covers?(pattern, emission.pattern) }
15
15
  end
@@ -65,8 +65,12 @@ module ThisIsRuby
65
65
  @claimed_paths ||= emissions.flat_map(&:paths).to_set
66
66
  end
67
67
 
68
- def before = @before ||= LanguageEstimate.of(repo)
68
+ # The bar without our block: what GitHub reports today, minus the paths this
69
+ # run is claiming. Asking git means Rails' own `db/schema.rb` line and
70
+ # anything hand-written are honoured, while re-running on a repository we
71
+ # already converted still shows the contrast rather than "nothing changes".
72
+ def before = @before ||= LanguageEstimate.of(repo, excluding: repo.already_excluded - claimed_paths)
69
73
 
70
- def after = @after ||= LanguageEstimate.of(repo, excluding: claimed_paths)
74
+ def after = @after ||= LanguageEstimate.of(repo, excluding: repo.already_excluded | claimed_paths)
71
75
  end
72
76
  end
@@ -17,6 +17,9 @@ module ThisIsRuby
17
17
  # markers we check for all sit in the first few lines.
18
18
  PEEK_BYTES = 4096
19
19
 
20
+ # The attributes that take a file out of GitHub's language statistics.
21
+ LINGUIST_EXCLUSIONS = %w[linguist-generated linguist-vendored linguist-documentation].freeze
22
+
20
23
  attr_reader :root
21
24
 
22
25
  def self.at(path)
@@ -55,6 +58,17 @@ module ThisIsRuby
55
58
  full.open("rb") { |io| io.read(PEEK_BYTES) }
56
59
  end
57
60
 
61
+ # Paths git already reports as excluded from Linguist's counts, whoever
62
+ # declared them: our own block, the lines Rails generates, or something
63
+ # written by hand. Resolving the patterns ourselves would mean
64
+ # reimplementing gitignore matching, so ask git, which owns the answer.
65
+ def already_excluded
66
+ @already_excluded ||= begin
67
+ paths = tracked
68
+ paths.empty? ? Set.new : Set.new(check_attr(paths))
69
+ end
70
+ end
71
+
58
72
  def size(path)
59
73
  full = root.join(path)
60
74
  full.file? ? full.size : 0
@@ -62,6 +76,18 @@ module ThisIsRuby
62
76
 
63
77
  private
64
78
 
79
+ # `check-attr --stdin -z` reads NUL-separated paths and answers with
80
+ # NUL-separated (path, attribute, value) triples.
81
+ def check_attr(paths)
82
+ out, _err, status = Open3.capture3(
83
+ "git", "-C", root.to_s, "check-attr", "--stdin", "-z", *LINGUIST_EXCLUSIONS,
84
+ stdin_data: paths.join("\0")
85
+ )
86
+ return [] unless status.success?
87
+
88
+ out.split("\0").each_slice(3).filter_map { |path, _attribute, value| path if value == "set" }
89
+ end
90
+
65
91
  def tracked_set
66
92
  @tracked_set ||= tracked.to_set
67
93
  end
@@ -7,10 +7,10 @@ module ThisIsRuby
7
7
  # Rules never guess: when the evidence is not on disk they return nothing,
8
8
  # so a project that has no coverage report gets no line about one.
9
9
  #
10
- # :safe rules find files a tool produced, and always run. :frontend rules
11
- # find hand-written sources, and run only with --all-frontend, because
12
- # excluding those is the owner's call about what their repository is rather
13
- # than a fact about who typed the file.
10
+ # :safe rules find files a tool produced. :frontend rules find hand-written
11
+ # sources, which is a claim about what the repository is rather than about
12
+ # who typed the file; both run by default, and --fair drops the second group
13
+ # for a project whose frontend really is the point.
14
14
  class Rule
15
15
  LEVELS = %i[safe frontend].freeze
16
16
 
@@ -121,7 +121,7 @@ module ThisIsRuby
121
121
  Rule.new(
122
122
  key: :frontend_sources,
123
123
  attribute: "linguist-vendored",
124
- summary: "frontend sources, excluded at your request (--all-frontend)",
124
+ summary: "frontend sources (pass --fair to keep them counted)",
125
125
  level: :frontend
126
126
  ) do |repo|
127
127
  present = FRONTEND_ROOTS.select { |dir| under(repo, dir).any? }
@@ -129,8 +129,8 @@ module ThisIsRuby
129
129
  end
130
130
  ].freeze
131
131
 
132
- def for_level(all_frontend:)
133
- all_frontend ? ALL : ALL.select(&:safe?)
132
+ def for_level(frontend:)
133
+ frontend ? ALL : ALL.select(&:safe?)
134
134
  end
135
135
  end
136
136
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThisIsRuby
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: this_is_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Irina Nazarova
@@ -9,12 +9,10 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: |
13
- GitHub picks a repository's language by counting bytes, and a Rails app
14
- carries plenty it never authored: generated error pages, compiled assets,
15
- coverage reports, components copied in by a frontend generator. Together
16
- they routinely outweigh the app's own Ruby. this_is_ruby finds them and
17
- writes the .gitattributes overrides Linguist already understands.
12
+ description: |-
13
+ Ruby is one of the most eloquent and efficient languages, but it gets punished for it by GitHub's language attribution. Even more so, a Rails app, carefully designed to require next to no boilerplate code, easily ends up with less Ruby than JS, TS or HTML (ERB).
14
+
15
+ This gem tells GitHub: this is Ruby, by marking your frontend as "linguist-vendored". No side effects on diffs or other DX.
18
16
  email:
19
17
  - inazarova@evilmartians.com
20
18
  executables:
@@ -61,5 +59,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
59
  requirements: []
62
60
  rubygems_version: 3.6.7
63
61
  specification_version: 4
64
- summary: Tell GitHub which files in your Ruby project nobody wrote
62
+ summary: Tell GitHub your project is Ruby.
65
63
  test_files: []