├── ButtonClick.md
├── ComponentToHtml.md
├── Gemfile
├── HiddenProperty.md
├── Input.md
├── LazyLoading.md
├── Module.md
├── NewComponent.md
├── Output.md
├── Rakefile
├── RouterLink.md
├── Routing.md
├── RoutingWithParam.md
├── SafeNavigationOperator.md
├── Service.md
├── TemplateVariable.md
├── _config.yml
├── _includes
└── nav.html
├── _layouts
└── default.html
├── bin
├── git-linguist
└── github-linguist
├── github-linguist.gemspec
├── index.md
├── lib
├── linguist.rb
└── linguist
│ ├── VERSION
│ ├── blob.rb
│ ├── blob_helper.rb
│ ├── classifier.rb
│ ├── documentation.yml
│ ├── file_blob.rb
│ ├── generated.rb
│ ├── grammars.rb
│ ├── heuristics.rb
│ ├── heuristics.yml
│ ├── language.rb
│ ├── languages.yml
│ ├── lazy_blob.rb
│ ├── md5.rb
│ ├── popular.yml
│ ├── repository.rb
│ ├── samples.rb
│ ├── shebang.rb
│ ├── strategy
│ ├── extension.rb
│ ├── filename.rb
│ ├── modeline.rb
│ └── xml.rb
│ ├── tokenizer.rb
│ ├── vendor.yml
│ └── version.rb
├── ngFor.md
├── ngIf.md
└── ngSwitch.md
/ButtonClick.md:
--------------------------------------------------------------------------------
1 | ## Button Click Event
2 |
3 | **my-second-component.component.html**
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
11 | ```
12 |
13 | **my-second-component.component.ts**
14 |
15 | ```javascript
16 |
17 | import { Component, OnInit, Input } from '@angular/core';
18 |
19 | @Component({
20 | selector: 'app-my-second-component',
21 | templateUrl: './my-second-component.component.html',
22 | styleUrls: ['./my-second-component.component.css']
23 | })
24 |
25 | export class MySecondComponentComponent implements OnInit {
26 |
27 | constructor() { }
28 |
29 | ngOnInit() {
30 | }
31 |
32 | handleClickMe(){
33 | console.log("Clicked!")
34 | }
35 | }
36 |
37 | ```
38 |
--------------------------------------------------------------------------------
/ComponentToHtml.md:
--------------------------------------------------------------------------------
1 | ## Component To HTML
2 |
3 | **my-first-component.component.ts**
4 |
5 | ```javascript
6 |
7 | import { Component, OnInit } from '@angular/core';
8 |
9 | @Component({
10 | selector: 'app-my-first-component',
11 | templateUrl: './my-first-component.component.html',
12 | styleUrls: ['./my-first-component.component.css']
13 | })
14 |
15 | export class MyFirstComponentComponent implements OnInit {
16 |
17 | constructor() { }
18 |
19 | ngOnInit() {
20 | }
21 |
22 | employee={
23 | id:1,
24 | name:'Akshay Patel'
25 | }
26 | }
27 |
28 | ```
29 | **my-first-component.component.html**
30 |
31 | ```html
32 | Employee Details
33 |
34 |
35 | {% raw %} {{ employee.id }} {% endraw %}
36 |
37 | {% raw %} {{ employee.name }} {% endraw %}
38 |
39 |
40 | ```
41 |
42 | **app.component.html**
43 |
44 | ```html
45 |
46 | ```
47 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec :name => "github-linguist"
3 |
4 | group :debug do
5 | gem 'byebug' if RUBY_VERSION >= '2.2'
6 | end
7 |
--------------------------------------------------------------------------------
/HiddenProperty.md:
--------------------------------------------------------------------------------
1 | ## Hidden Property
2 |
3 | **my-first-component.component.html**
4 |
5 | ```html
6 |
7 |
8 | {% raw %} {{employee.id}} {% endraw %}
9 |
10 | {% raw %} {{employee.name}} {% endraw %}
11 |
12 |
13 |
14 | ```
15 |
16 | **my-first-component.component.ts**
17 |
18 | ```javascript
19 |
20 | import { Component, OnInit } from '@angular/core';
21 |
22 | @Component({
23 | selector: 'app-my-first-component',
24 | templateUrl: './my-first-component.component.html',
25 | styleUrls: ['./my-first-component.component.css']
26 | })
27 |
28 | export class MyFirstComponentComponent implements OnInit {
29 |
30 | constructor() { }
31 |
32 | ngOnInit() {
33 | }
34 |
35 | employee1=[{
36 | name:'Akshay Patel'
37 | },
38 | {
39 | id:2,
40 | name:'Panth Patel'
41 | }]
42 | }
43 |
44 | ```
45 |
--------------------------------------------------------------------------------
/Input.md:
--------------------------------------------------------------------------------
1 | ## Communication with child component using @Input
2 |
3 | **my-second-component.component.html**
4 |
5 | ```html
6 |
7 | Employee Details
8 |
9 |
10 | {% raw %} {{ employee.id }} {% endraw %}
11 |
12 | {% raw %} {{ employee.name }} {% endraw %}
13 |
14 |
15 |
16 | ```
17 |
18 | **my-second-component.component.ts**
19 |
20 | ```javascript
21 |
22 | import { Component, OnInit, Input } from '@angular/core';
23 |
24 | @Component({
25 | selector: 'app-my-second-component',
26 | templateUrl: './my-second-component.component.html',
27 | styleUrls: ['./my-second-component.component.css']
28 | })
29 |
30 | export class MySecondComponentComponent implements OnInit {
31 |
32 | @Input() employee:any
33 |
34 | constructor() { }
35 |
36 | ngOnInit() {
37 | }
38 | }
39 |
40 | ```
41 |
42 | **my-first-component.component.html**
43 |
44 | ```html
45 |
46 |
47 |
48 | ```
49 |
50 | **my-first-component.component.ts**
51 |
52 | ```javascript
53 |
54 | import { Component, OnInit } from '@angular/core';
55 |
56 | @Component({
57 | selector: 'app-my-first-component',
58 | templateUrl: './my-first-component.component.html',
59 | styleUrls: ['./my-first-component.component.css']
60 | })
61 |
62 | export class MyFirstComponentComponent implements OnInit {
63 |
64 | constructor() { }
65 |
66 | ngOnInit() {
67 | }
68 |
69 | employee1={
70 | id:1,
71 | name:'Akshay Patel'
72 | }
73 | }
74 |
75 | ```
76 |
77 | **app.component.html**
78 |
79 | ```html
80 |
81 |
82 |
83 | ```
84 |
--------------------------------------------------------------------------------
/LazyLoading.md:
--------------------------------------------------------------------------------
1 | ## Lazy Loaded Module
2 |
3 | **ng g m User**
4 |
5 | **ng g c user/login**
6 |
7 | **routes.ts**
8 |
9 | ```javascript
10 |
11 | import { Routes } from "@angular/router";
12 |
13 | export const appRoutes:Routes=[
14 | { path: 'user', loadChildren: './user/user.module#UserModule' }
15 | ]
16 |
17 | ```
18 |
19 | **app.module.ts**
20 | ```javascript
21 |
22 | import { BrowserModule } from '@angular/platform-browser';
23 | import { NgModule } from '@angular/core';
24 | import { AppComponent } from './app.component';
25 | import { RouterModule } from '@angular/router';
26 | import { appRoutes} from './routes';
27 |
28 | @NgModule({
29 |
30 | declarations: [
31 | AppComponent
32 | ],
33 |
34 | imports: [
35 | BrowserModule,
36 | RouterModule.forRoot(appRoutes)
37 | ],
38 |
39 | providers: [],
40 |
41 | bootstrap: [AppComponent]
42 |
43 | })
44 | export class AppModule { }
45 |
46 | ```
47 |
48 | **user.routes.ts**
49 | ```javascript
50 |
51 | import { LoginComponent } from "./login.component";
52 | export const userRoutes=[
53 | {path:'login',component:LoginComponent}
54 | ]
55 |
56 | ```
57 |
58 | **user.module.ts**
59 |
60 | ```javascript
61 |
62 | import { NgModule } from '@angular/core';
63 | import { CommonModule } from '@angular/common';
64 | import {RouterModule} from '@angular/router';
65 | import { LoginComponent } from './login.component';
66 | import { userRoutes } from './user.routes';
67 | import {FormsModule} from '@angular/forms';
68 |
69 | @NgModule({
70 |
71 | imports: [
72 | CommonModule,
73 | FormsModule,
74 | RouterModule.forChild(userRoutes)
75 | ],
76 |
77 | declarations: [
78 | LoginComponent
79 | ]
80 | })
81 |
82 | export class UserModule { }
83 |
84 | ```
85 |
86 |
87 |
--------------------------------------------------------------------------------
/Module.md:
--------------------------------------------------------------------------------
1 | ## Module
2 |
3 | **ng g m User**
4 |
5 | **ng g c user/login**
6 |
7 | **routes.ts**
8 |
9 | ```javascript
10 |
11 | import {Routes} from '@angular/router'
12 | import {LoginComponent} from './user/login/login.component'
13 |
14 | export const appRoutes:Routes=[
15 | {path:'user/login',component:LoginComponent},
16 | ]
17 |
18 | ```
19 |
20 | **app.module.ts**
21 |
22 | ```javascript
23 |
24 | import { BrowserModule } from '@angular/platform-browser';
25 | import { NgModule } from '@angular/core';
26 | import { AppComponent } from './app.component';
27 |
28 | import { LoginComponent } from './user/login/login.component';
29 | import {appRoutes} from './routes';
30 | import { RouterModule } from '@angular/router';
31 |
32 | @NgModule({
33 |
34 | declarations: [
35 | AppComponent,
36 | LoginComponent
37 | ],
38 |
39 | imports: [
40 | BrowserModule,
41 | RouterModule.forRoot(appRoutes)
42 | ],
43 |
44 | providers: [],
45 |
46 | bootstrap: [AppComponent]
47 |
48 | })
49 |
50 | export class AppModule { }
51 |
52 | ```
53 |
54 | URL : http://localhost:4200/user/login
55 |
--------------------------------------------------------------------------------
/NewComponent.md:
--------------------------------------------------------------------------------
1 | ## Create New Component
2 |
3 | **ng g c MyFirstComponent**
4 |
5 | If we have child module then we need to move import from app.module.ts to childmodule.module.ts
6 |
7 | **app.module.ts**
8 |
9 | ```javascript
10 | import { MyFirstComponentComponent } from './my-first-component/my-first-component.component';
11 |
12 | @NgModule({
13 |
14 | declarations: [
15 | AppComponent,
16 | MyFirstComponentComponent
17 | ],
18 |
19 | imports: [
20 | BrowserModule
21 | ],
22 |
23 | providers: [],
24 |
25 | bootstrap: [AppComponent]
26 | })
27 |
28 | export class AppModule { }
29 | ```
30 |
31 | **my-first-component.component.ts**
32 |
33 | ```javascript
34 | import { Component, OnInit } from '@angular/core';
35 |
36 | @Component({
37 |
38 | selector: 'app-my-first-component',
39 | templateUrl: './my-first-component.component.html',
40 | styleUrls: ['./my-first-component.component.css']
41 | })
42 |
43 | export class MyFirstComponentComponent implements OnInit {
44 |
45 | constructor() { }
46 |
47 |
48 | ngOnInit() {
49 | }
50 |
51 | }
52 | ```
53 |
--------------------------------------------------------------------------------
/Output.md:
--------------------------------------------------------------------------------
1 | ## Communicating with parent component using @Output
2 |
3 | **my-second-component.component.html**
4 |
5 | ```html
6 |
7 | Employee Details
8 |
9 |
10 | {% raw %} {{ employee.id }} {% endraw %}
11 |
12 | {% raw %} {{ employee.name }} {% endraw %}
13 |
14 |
15 |
16 |
17 |
18 |
19 | ```
20 |
21 | **my-second-component.component.ts**
22 |
23 | ```javascript
24 |
25 | import { Component, OnInit, Input,Output,EventEmitter } from '@angular/core';
26 |
27 | @Component({
28 | selector: 'app-my-second-component',
29 | templateUrl: './my-second-component.component.html',
30 | styleUrls: ['./my-second-component.component.css']
31 | })
32 |
33 | export class MySecondComponentComponent implements OnInit {
34 |
35 | @Input() employee:any
36 |
37 | @Output() eventClick = new EventEmitter()
38 |
39 | constructor() { }
40 |
41 | ngOnInit() {
42 | }
43 |
44 | handleClickMe(){
45 | this.eventClick.emit("Akshay")
46 | }
47 | }
48 |
49 | ```
50 |
51 | **my-first-component.component.html**
52 |
53 | ```html
54 |
55 |
56 |
57 | ```
58 |
59 | **my-first-component.component.ts**
60 |
61 | ```javascript
62 |
63 | import { Component, OnInit } from '@angular/core';
64 |
65 | @Component({
66 | selector: 'app-my-first-component',
67 | templateUrl: './my-first-component.component.html',
68 | styleUrls: ['./my-first-component.component.css']
69 | })
70 |
71 | export class MyFirstComponentComponent implements OnInit {
72 |
73 | constructor() { }
74 |
75 | ngOnInit() {
76 | }
77 |
78 | employee1={
79 | id:1,
80 | name:'Akshay Patel'
81 | }
82 |
83 | handleClickMe(data){
84 | console.log("Received: " + data)
85 | }
86 | }
87 |
88 | ```
89 |
90 | **app.component.html**
91 |
92 | ```html
93 |
94 |
95 |
96 | ```
97 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 | require 'rake/clean'
3 | require 'rake/testtask'
4 | require 'rake/extensiontask'
5 | require 'yaml'
6 | require 'yajl'
7 | require 'open-uri'
8 | require 'json'
9 |
10 | task :default => :test
11 |
12 | Rake::TestTask.new
13 |
14 | gem_spec = Gem::Specification.load('github-linguist.gemspec')
15 |
16 | Rake::ExtensionTask.new('linguist', gem_spec) do |ext|
17 | ext.lib_dir = File.join('lib', 'linguist')
18 | end
19 |
20 | # Extend test task to check for samples and fetch latest Ace modes
21 | task :test => [:compile, :check_samples, :fetch_ace_modes]
22 |
23 | desc "Check that we have samples.json generated"
24 | task :check_samples do
25 | unless File.exist?('lib/linguist/samples.json')
26 | Rake::Task[:samples].invoke
27 | end
28 | end
29 |
30 | desc "Fetch the latest Ace modes from its GitHub repository"
31 | task :fetch_ace_modes do
32 | ACE_FIXTURE_PATH = File.join('test', 'fixtures', 'ace_modes.json')
33 |
34 | File.delete(ACE_FIXTURE_PATH) if File.exist?(ACE_FIXTURE_PATH)
35 |
36 | begin
37 | ace_github_modes = open("https://api.github.com/repos/ajaxorg/ace/contents/lib/ace/mode").read
38 | File.write(ACE_FIXTURE_PATH, ace_github_modes)
39 | rescue OpenURI::HTTPError, SocketError
40 | # no internet? no problem.
41 | end
42 | end
43 |
44 | task :samples => :compile do
45 | require 'linguist/samples'
46 | json = Yajl.dump(Linguist::Samples.data, :pretty => true)
47 | File.write 'lib/linguist/samples.json', json
48 | end
49 |
50 | task :flex do
51 | if `flex -V` !~ /^flex \d+\.\d+\.\d+/
52 | fail "flex not detected"
53 | end
54 | system "cd ext/linguist && flex tokenizer.l"
55 | end
56 |
57 | task :build_gem => :samples do
58 | rm_rf "grammars"
59 | sh "script/grammar-compiler compile -o grammars || true"
60 | languages = YAML.load_file("lib/linguist/languages.yml")
61 | File.write("lib/linguist/languages.json", Yajl.dump(languages))
62 | `gem build github-linguist.gemspec`
63 | File.delete("lib/linguist/languages.json")
64 | end
65 |
66 | namespace :benchmark do
67 | benchmark_path = "benchmark/results"
68 |
69 | # $ bundle exec rake benchmark:generate CORPUS=path/to/samples
70 | desc "Generate results for"
71 | task :generate do
72 | ref = `git rev-parse HEAD`.strip[0,8]
73 |
74 | corpus = File.expand_path(ENV["CORPUS"] || "samples")
75 |
76 | require 'linguist'
77 |
78 | results = Hash.new
79 | Dir.glob("#{corpus}/**/*").each do |file|
80 | next unless File.file?(file)
81 | filename = file.gsub("#{corpus}/", "")
82 | results[filename] = Linguist::FileBlob.new(file).language
83 | end
84 |
85 | # Ensure results directory exists
86 | FileUtils.mkdir_p("benchmark/results")
87 |
88 | # Write results
89 | if `git status`.include?('working directory clean')
90 | result_filename = "benchmark/results/#{File.basename(corpus)}-#{ref}.json"
91 | else
92 | result_filename = "benchmark/results/#{File.basename(corpus)}-#{ref}-unstaged.json"
93 | end
94 |
95 | File.write(result_filename, results.to_json)
96 | puts "wrote #{result_filename}"
97 | end
98 |
99 | # $ bundle exec rake benchmark:compare REFERENCE=path/to/reference.json CANDIDATE=path/to/candidate.json
100 | desc "Compare results"
101 | task :compare do
102 | reference_file = ENV["REFERENCE"]
103 | candidate_file = ENV["CANDIDATE"]
104 |
105 | reference = Yajl.load(File.read(reference_file))
106 | reference_counts = Hash.new(0)
107 | reference.each { |filename, language| reference_counts[language] += 1 }
108 |
109 | candidate = Yajl.load(File.read(candidate_file))
110 | candidate_counts = Hash.new(0)
111 | candidate.each { |filename, language| candidate_counts[language] += 1 }
112 |
113 | changes = diff(reference_counts, candidate_counts)
114 |
115 | if changes.any?
116 | changes.each do |language, (before, after)|
117 | before_percent = 100 * before / reference.size.to_f
118 | after_percent = 100 * after / candidate.size.to_f
119 | puts "%s changed from %.1f%% to %.1f%%" % [language || 'unknown', before_percent, after_percent]
120 | end
121 | else
122 | puts "No changes"
123 | end
124 | end
125 | end
126 |
127 | namespace :classifier do
128 | LIMIT = 1_000
129 |
130 | desc "Run classifier against #{LIMIT} public gists"
131 | task :test do
132 | require 'linguist/classifier'
133 | require 'linguist/samples'
134 |
135 | total, correct, incorrect = 0, 0, 0
136 | $stdout.sync = true
137 |
138 | each_public_gist do |gist_url, file_url, file_language|
139 | next if file_language.nil? || file_language == 'Text'
140 | begin
141 | data = open(file_url).read
142 | guessed_language, score = Linguist::Classifier.classify(Linguist::Samples.cache, data).first
143 |
144 | total += 1
145 | guessed_language == file_language ? correct += 1 : incorrect += 1
146 |
147 | print "\r\e[0K%d:%d %g%%" % [correct, incorrect, (correct.to_f/total.to_f)*100]
148 | $stdout.flush
149 | rescue URI::InvalidURIError
150 | else
151 | break if total >= LIMIT
152 | end
153 | end
154 | puts ""
155 | end
156 |
157 | def each_public_gist
158 | require 'open-uri'
159 | url = "https://api.github.com/gists/public"
160 |
161 | loop do
162 | resp = open(url)
163 | url = resp.meta['link'][/<([^>]+)>; rel="next"/, 1]
164 | gists = Yajl.load(resp.read)
165 |
166 | for gist in gists
167 | for filename, attrs in gist['files']
168 | yield gist['url'], attrs['raw_url'], attrs['language']
169 | end
170 | end
171 | end
172 | end
173 | end
174 |
175 |
176 | def diff(a, b)
177 | (a.keys | b.keys).each_with_object({}) do |key, diff|
178 | diff[key] = [a[key], b[key]] unless a[key] == b[key]
179 | end
180 | end
181 |
--------------------------------------------------------------------------------
/RouterLink.md:
--------------------------------------------------------------------------------
1 | ## Router Link
2 |
3 | **my-first-component.component.html**
4 |
5 | ```html
6 |
7 | Employee Details
8 |
9 | {% raw %} {{employee.id}} {% endraw %}
10 | {% raw %} {{employee.name}} {% endraw %}
11 |
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Routing.md:
--------------------------------------------------------------------------------
1 | ## Routing
2 |
3 | **my-first-component.component.ts**
4 |
5 | ```javascript
6 |
7 | import { Component, OnInit } from '@angular/core';
8 |
9 | @Component({
10 | selector: 'app-my-first-component',
11 | templateUrl: './my-first-component.component.html',
12 | styleUrls: ['./my-first-component.component.css']
13 | })
14 |
15 | export class MyFirstComponentComponent implements OnInit {
16 |
17 | constructor() { }
18 |
19 | ngOnInit() {
20 | }
21 |
22 | employee1=[{
23 | id:1,
24 | name:'Akshay Patel'
25 | },
26 | {
27 | id:2,
28 | name:'Panth Patel'
29 | },
30 | {
31 | id:3,
32 | name:'Jemin Patel'
33 | }]
34 | }
35 |
36 | ```
37 |
38 | **my-first-component.component.html**
39 |
40 | ```html
41 |
42 | Employee Details
43 |
44 |
45 | {% raw %} {{employee.id}} {% endraw %}
46 | {% raw %} {{employee.name}} {% endraw %}
47 |
48 |
49 |
50 | ```
51 |
52 | **routes.ts**
53 |
54 | ```javascript
55 |
56 | import {Routes} from '@angular/router'
57 | import { MyFirstComponentComponent } from './my-first-component/my-first-component.component';
58 |
59 | export const appRoutes:Routes=[
60 |
61 | {path:'employees',component:MyFirstComponentComponent},
62 |
63 | {path:'',redirectTo:'/employees',pathMatch:'full'}
64 | ]
65 |
66 | ```
67 |
68 | **app.module.ts**
69 |
70 | ```javascript
71 |
72 | import { BrowserModule } from '@angular/platform-browser';
73 | import { NgModule } from '@angular/core';
74 | import { AppComponent } from './app.component';
75 | import { MyFirstComponentComponent } from './my-first-component/my-first-component.component';
76 | import {appRoutes} from './routes';
77 | import { RouterModule } from '@angular/router';
78 |
79 | @NgModule({
80 |
81 | declarations: [
82 | AppComponent,
83 | MyFirstComponentComponent
84 | ],
85 |
86 | imports: [
87 | BrowserModule,
88 | RouterModule.forRoot(appRoutes)
89 | ],
90 |
91 | providers: [],
92 |
93 | bootstrap: [AppComponent]
94 | })
95 |
96 | export class AppModule { }
97 |
98 | ```
99 |
100 | **app.component.html**
101 |
102 | ```html
103 |
104 |
105 |
106 | ```
107 |
--------------------------------------------------------------------------------
/RoutingWithParam.md:
--------------------------------------------------------------------------------
1 | ## Routing with Parameter
2 |
3 | **my-second-component.component.ts**
4 |
5 | ```javascript
6 |
7 | import { Component, OnInit } from '@angular/core';
8 | import {ActivatedRoute} from '@angular/router';
9 |
10 | @Component({
11 | selector: 'app-my-second-component',
12 | templateUrl: './my-second-component.component.html',
13 | styleUrls: ['./my-second-component.component.css']
14 | })
15 |
16 | export class MySecondComponentComponent implements OnInit {
17 |
18 | employee:any
19 |
20 | constructor(private route:ActivatedRoute) { }
21 |
22 | ngOnInit() {
23 | this.employee = this.employee1[this.route.snapshot.params['id']]
24 | }
25 |
26 | employee1=[{
27 | id:0,
28 | name:'Akshay Patel'
29 | },
30 | {
31 | id:1,
32 | name:'Panth Patel'
33 | },
34 | {
35 | id:2,
36 | name:'Jemin Patel'
37 | }]
38 | }
39 |
40 | ```
41 |
42 | **my-second-component.component.html**
43 |
44 | ```html
45 |
46 | Employee Details
47 | {% raw %} {{employee.id}} {% endraw %}
48 | {% raw %} {{employee.name}} {% endraw %}
49 |
50 |
51 | ```
52 |
53 | **routes.ts**
54 |
55 | ```javascript
56 |
57 | import {Routes} from '@angular/router'
58 | import { MySecondComponentComponent } from './my-second-component/my-second-component.component';
59 | import { MyFirstComponentComponent } from './my-first-component/my-first-component.component';
60 |
61 | export const appRoutes:Routes=[
62 |
63 | {path:'employees',component:MyFirstComponentComponent},
64 |
65 | {path:'employees/:id',component:MySecondComponentComponent},
66 |
67 | {path:'',redirectTo:'/employees',pathMatch:'full'}
68 | ]
69 |
70 | ```
71 |
72 | **app.module.ts**
73 |
74 | ```javascript
75 |
76 | import { BrowserModule } from '@angular/platform-browser';
77 | import { NgModule } from '@angular/core';
78 | import { AppComponent } from './app.component';
79 | import { MyFirstComponentComponent } from './my-first-component/my-first-component.component';
80 | import {appRoutes} from './routes';
81 | import { RouterModule } from '@angular/router';
82 | import { MySecondComponentComponent } from './my-second-component/my-second-component.component';
83 |
84 | @NgModule({
85 |
86 | declarations: [
87 | AppComponent,
88 | MyFirstComponentComponent,
89 | MySecondComponentComponent
90 | ],
91 |
92 | imports: [
93 | BrowserModule,
94 | RouterModule.forRoot(appRoutes)
95 | ],
96 |
97 | providers: [],
98 |
99 | bootstrap: [AppComponent]
100 | })
101 |
102 | export class AppModule { }
103 |
104 | ```
105 |
106 | **app.component.html**
107 |
108 | ```html
109 |
110 |
111 |
112 | ```
113 |
--------------------------------------------------------------------------------
/SafeNavigationOperator.md:
--------------------------------------------------------------------------------
1 | ## Safe Navigation Operator
2 |
3 | This will not throw an exception even though employee is undefined.
4 |
5 | **my-first-component.component.html**
6 |
7 | ```html
8 |
9 |
10 | {% raw %} {{employee?.id}} {% endraw %}
11 | {% raw %} {{employee?.name}} {% endraw %}
12 |
13 |
14 |
15 | ```
16 |
--------------------------------------------------------------------------------
/Service.md:
--------------------------------------------------------------------------------
1 | ## Service
2 |
3 | **ng g s EmployeeService**
4 |
5 | **employee-service.service.ts**
6 |
7 | ```javascript
8 |
9 | import { Injectable } from '@angular/core';
10 |
11 | @Injectable({
12 | providedIn: 'root'
13 | })
14 |
15 | export class EmployeeServiceService {
16 |
17 | constructor() { }
18 |
19 | getEmployees()
20 | {
21 | return EMPLOYEE
22 | }
23 | }
24 |
25 | const EMPLOYEE = [
26 | {
27 | id:1,
28 | name:'Akshay Patel'
29 | },
30 | {
31 | id:2,
32 | name:'Panth Patel'
33 | },
34 | {
35 | id:3,
36 | name:'Jemin Patel'
37 | }
38 | ]
39 |
40 | ```
41 |
42 | **app.module.ts**
43 |
44 | ```javascript
45 |
46 | import { BrowserModule } from '@angular/platform-browser';
47 | import { NgModule } from '@angular/core';
48 | import { AppComponent } from './app.component';
49 | import { MyFirstComponentComponent } from './my-first-component/my-first-component.component';
50 | import { EmployeeServiceService } from './Services/employee-service.service';
51 |
52 |
53 | @NgModule({
54 |
55 | declarations: [
56 | AppComponent,
57 | MyFirstComponentComponent,
58 | MySecondComponentComponent
59 | ],
60 |
61 | imports: [
62 | BrowserModule
63 | ],
64 |
65 | providers: [EmployeeServiceService],
66 |
67 | bootstrap: [AppComponent]
68 | })
69 |
70 | export class AppModule { }
71 |
72 | ```
73 |
74 | **my-first-component.component.ts**
75 |
76 | ```javascript
77 |
78 | import { Component, OnInit } from '@angular/core';
79 | import { EmployeeServiceService } from '../Services/employee-service.service';
80 |
81 | @Component({
82 | selector: 'app-my-first-component',
83 | templateUrl: './my-first-component.component.html',
84 | styleUrls: ['./my-first-component.component.css']
85 | })
86 |
87 | export class MyFirstComponentComponent implements OnInit {
88 | employee : any[]
89 |
90 | constructor(private employeeService : EmployeeServiceService)
91 | {
92 | }
93 |
94 | ngOnInit() {
95 | this.employee = this.employeeService.getEmployees()
96 | }
97 | }
98 |
99 | ```
100 |
101 | **my-first-component.component.html**
102 |
103 | ```html
104 |
105 |
106 | {% raw %} {{employee.id}} {% endraw %}
107 | {% raw %} {{employee.name}} {% endraw %}
108 |
109 |
110 |
111 | ```
112 |
--------------------------------------------------------------------------------
/TemplateVariable.md:
--------------------------------------------------------------------------------
1 | ## Using Template Variables To Interact with Child Components
2 |
3 | **Method**
4 |
5 | **my-first-component.component.html**
6 |
7 | ```html
8 |
9 |
10 |
11 |
12 | ```
13 |
14 | **my-second-component.component.ts**
15 |
16 | ```javascript
17 |
18 | import { Component, OnInit } from '@angular/core';
19 |
20 | @Component({
21 | selector: 'app-my-second-component',
22 | templateUrl: './my-second-component.component.html',
23 | styleUrls: ['./my-second-component.component.css']
24 | })
25 |
26 | export class MySecondComponentComponent implements OnInit {
27 |
28 | constructor() { }
29 |
30 | ngOnInit() {
31 | }
32 |
33 | TestMethod(){
34 | console.log("TestMethod of Second Component is called by First Component")
35 | }
36 | }
37 |
38 | ```
39 |
40 | **Property**
41 |
42 | **my-first-component.component.html**
43 |
44 | ```html
45 |
46 |
47 | {{secondComponent.TestPropery}}
48 |
49 | ```
50 |
51 | **my-second-component.component.ts**
52 |
53 | ```javascript
54 |
55 | import { Component, OnInit} from '@angular/core';
56 |
57 | @Component({
58 | selector: 'app-my-second-component',
59 | templateUrl: './my-second-component.component.html',
60 | styleUrls: ['./my-second-component.component.css']
61 | })
62 |
63 | export class MySecondComponentComponent implements OnInit {
64 |
65 | TestPropery:any = 'Akshay Patel'
66 |
67 | constructor() { }
68 |
69 | ngOnInit() {
70 | }
71 | }
72 |
73 | ```
74 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-dinky
2 |
3 |
--------------------------------------------------------------------------------
/_includes/nav.html:
--------------------------------------------------------------------------------
1 |
2 | Home
3 |
4 | 1. Create New Component
5 | 2. Component To HTML
6 | 3. @Input
7 | 4. Button Click Event
8 | 5. @Output
9 | 6. Template Variable
10 | 7. *ngFor
11 | 8. Safe Navigation Operator
12 | 9. *ngIf
13 | 10. Hidden Property
14 | 11. *ngSwitch
15 | 12. Service
16 | 13. Routing
17 | 14. Routing with Parameter
18 | 15. Router Link
19 | 16. Module
20 | 17. Lazy Loaded Module
21 |
22 |
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {% seo %}
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 |
30 | {% if site.github.is_project_page %}
31 |
34 | {% endif %}
35 |
36 | {% if site.github.is_user_page %}
37 |
40 | {% endif %}
41 |
42 |
43 |
46 |
47 |
50 |
51 |
52 | {% if site.google_analytics %}
53 |
61 | {% endif %}
62 |
63 |
64 |
--------------------------------------------------------------------------------
/bin/git-linguist:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | $LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib')
4 |
5 | require 'linguist'
6 | require 'rugged'
7 | require 'optparse'
8 | require 'json'
9 | require 'tempfile'
10 | require 'zlib'
11 |
12 | class GitLinguist
13 | def initialize(path, commit_oid, incremental = true)
14 | @repo_path = path
15 | @commit_oid = commit_oid
16 | @incremental = incremental
17 | end
18 |
19 | def linguist
20 | if @commit_oid.nil?
21 | raise "git-linguist must be called with a specific commit OID to perform language computation"
22 | end
23 | repo = Linguist::Repository.new(rugged, @commit_oid)
24 |
25 | if @incremental && stats = load_language_stats
26 | old_commit_oid, old_stats = stats
27 |
28 | # A cache with NULL oid means that we want to freeze
29 | # these language stats in place and stop computing
30 | # them (for performance reasons)
31 | return old_stats if old_commit_oid == NULL_OID
32 | repo.load_existing_stats(old_commit_oid, old_stats)
33 | end
34 |
35 | result = yield repo
36 |
37 | save_language_stats(@commit_oid, repo.cache)
38 | result
39 | end
40 |
41 | def load_language_stats
42 | version, oid, stats = load_cache
43 | if version == LANGUAGE_STATS_CACHE_VERSION && oid && stats
44 | [oid, stats]
45 | end
46 | end
47 |
48 | def save_language_stats(oid, stats)
49 | cache = [LANGUAGE_STATS_CACHE_VERSION, oid, stats]
50 | write_cache(cache)
51 | end
52 |
53 | def clear_language_stats
54 | File.unlink(cache_file)
55 | rescue Errno::ENOENT
56 | end
57 |
58 | def disable_language_stats
59 | save_language_stats(NULL_OID, {})
60 | end
61 |
62 | protected
63 | NULL_OID = ("0" * 40).freeze
64 |
65 | LANGUAGE_STATS_CACHE = 'language-stats.cache'
66 | LANGUAGE_STATS_CACHE_VERSION = "v3:#{Linguist::VERSION}"
67 |
68 | def rugged
69 | @rugged ||= Rugged::Repository.bare(@repo_path)
70 | end
71 |
72 | def cache_file
73 | File.join(@repo_path, LANGUAGE_STATS_CACHE)
74 | end
75 |
76 | def write_cache(object)
77 | return unless File.directory? @repo_path
78 |
79 | Tempfile.open('cache_file', @repo_path) do |f|
80 | marshal = Marshal.dump(object)
81 | f.write(Zlib::Deflate.deflate(marshal))
82 | f.close
83 | File.rename(f.path, cache_file)
84 | end
85 |
86 | FileUtils.chmod 0644, cache_file
87 | end
88 |
89 | def load_cache
90 | marshal = File.open(cache_file, "rb") { |f| Zlib::Inflate.inflate(f.read) }
91 | Marshal.load(marshal)
92 | rescue SystemCallError, ::Zlib::DataError, ::Zlib::BufError, TypeError
93 | nil
94 | end
95 | end
96 |
97 |
98 | def git_linguist(args)
99 | incremental = true
100 | commit = nil
101 |
102 | parser = OptionParser.new do |opts|
103 | opts.banner = <<~HELP
104 | Linguist v#{Linguist::VERSION}
105 | Detect language type and determine language breakdown for a given Git repository.
106 |
107 | Usage:
108 | git-linguist [OPTIONS] stats|breakdown|dump-cache|clear|disable
109 | HELP
110 |
111 | opts.on("-f", "--force", "Force a full rescan") { incremental = false }
112 | opts.on("-c", "--commit=COMMIT", "Commit to index") { |v| commit = v}
113 | end
114 |
115 | parser.parse!(args)
116 | git_dir = `git rev-parse --git-dir`.strip
117 | raise "git-linguist must be run in a Git repository" unless $?.success?
118 | wrapper = GitLinguist.new(git_dir, commit, incremental)
119 |
120 | case args.pop
121 | when "stats"
122 | wrapper.linguist do |linguist|
123 | puts JSON.dump(linguist.languages)
124 | end
125 | when "breakdown"
126 | wrapper.linguist do |linguist|
127 | puts JSON.dump(linguist.breakdown_by_file)
128 | end
129 | when "dump-cache"
130 | puts JSON.dump(wrapper.load_language_stats)
131 | when "clear"
132 | wrapper.clear_language_stats
133 | when "disable"
134 | wrapper.disable_language_stats
135 | else
136 | $stderr.print(parser.help)
137 | exit 1
138 | end
139 | rescue SystemExit
140 | exit 1
141 | rescue Exception => e
142 | $stderr.puts e.message
143 | $stderr.puts e.backtrace
144 | exit 1
145 | end
146 |
147 | git_linguist(ARGV)
148 |
--------------------------------------------------------------------------------
/bin/github-linguist:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | $LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib')
4 |
5 | require 'linguist'
6 | require 'rugged'
7 | require 'json'
8 | require 'optparse'
9 |
10 | path = ARGV[0] || Dir.pwd
11 |
12 | # special case if not given a directory
13 | # but still given the --breakdown or --json options/
14 | if path == "--breakdown"
15 | path = Dir.pwd
16 | breakdown = true
17 | elsif path == "--json"
18 | path = Dir.pwd
19 | json_breakdown = true
20 | end
21 |
22 | ARGV.shift
23 | breakdown = true if ARGV[0] == "--breakdown"
24 | json_breakdown = true if ARGV[0] == "--json"
25 |
26 | if File.directory?(path)
27 | rugged = Rugged::Repository.new(path)
28 | repo = Linguist::Repository.new(rugged, rugged.head.target_id)
29 | if !json_breakdown
30 | repo.languages.sort_by { |_, size| size }.reverse.each do |language, size|
31 | percentage = ((size / repo.size.to_f) * 100)
32 | percentage = sprintf '%.2f' % percentage
33 | puts "%-7s %s" % ["#{percentage}%", language]
34 | end
35 | end
36 | if breakdown
37 | puts
38 | file_breakdown = repo.breakdown_by_file
39 | file_breakdown.each do |lang, files|
40 | puts "#{lang}:"
41 | files.each do |file|
42 | puts file
43 | end
44 | puts
45 | end
46 | elsif json_breakdown
47 | puts JSON.dump(repo.breakdown_by_file)
48 | end
49 | elsif File.file?(path)
50 | blob = Linguist::FileBlob.new(path, Dir.pwd)
51 | type = if blob.text?
52 | 'Text'
53 | elsif blob.image?
54 | 'Image'
55 | else
56 | 'Binary'
57 | end
58 |
59 | puts "#{blob.name}: #{blob.loc} lines (#{blob.sloc} sloc)"
60 | puts " type: #{type}"
61 | puts " mime type: #{blob.mime_type}"
62 | puts " language: #{blob.language}"
63 |
64 | if blob.large?
65 | puts " blob is too large to be shown"
66 | end
67 |
68 | if blob.generated?
69 | puts " appears to be generated source code"
70 | end
71 |
72 | if blob.vendored?
73 | puts " appears to be a vendored file"
74 | end
75 | else
76 | abort <<-HELP
77 | Linguist v#{Linguist::VERSION}
78 | Detect language type for a file, or, given a repository, determine language breakdown.
79 |
80 | Usage: linguist
81 | linguist [--breakdown] [--json]
82 | linguist [--breakdown] [--json]
83 | HELP
84 | end
85 |
--------------------------------------------------------------------------------
/github-linguist.gemspec:
--------------------------------------------------------------------------------
1 | require File.expand_path('../lib/linguist/version', __FILE__)
2 |
3 | Gem::Specification.new do |s|
4 | s.name = 'github-linguist'
5 | s.version = ENV['GEM_VERSION'] || Linguist::VERSION
6 | s.summary = "GitHub Language detection"
7 | s.description = 'We use this library at GitHub to detect blob languages, highlight code, ignore binary files, suppress generated files in diffs, and generate language breakdown graphs.'
8 |
9 | s.authors = "GitHub"
10 | s.homepage = "https://github.com/github/linguist"
11 | s.license = "MIT"
12 |
13 | s.files = Dir['lib/**/*'] + Dir['ext/**/*'] + Dir['grammars/*'] + ['LICENSE']
14 | s.executables = ['github-linguist', 'git-linguist']
15 | s.extensions = ['ext/linguist/extconf.rb']
16 |
17 | s.add_dependency 'charlock_holmes', '~> 0.7.6'
18 | s.add_dependency 'escape_utils', '~> 1.2.0'
19 | s.add_dependency 'mime-types', '>= 1.19'
20 | s.add_dependency 'rugged', '>= 0.25.1'
21 |
22 | s.add_development_dependency 'minitest', '>= 5.0'
23 | s.add_development_dependency 'rake-compiler', '~> 0.9'
24 | s.add_development_dependency 'mocha'
25 | s.add_development_dependency 'plist', '~>3.1'
26 | s.add_development_dependency 'pry'
27 | s.add_development_dependency 'rake'
28 | s.add_development_dependency 'yajl-ruby'
29 | s.add_development_dependency 'color-proximity', '~> 0.2.1'
30 | s.add_development_dependency 'licensed', '~> 1.3.0'
31 | s.add_development_dependency 'licensee'
32 | end
33 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ## Angular Samples for Beginners
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/linguist.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/blob_helper'
2 | require 'linguist/generated'
3 | require 'linguist/grammars'
4 | require 'linguist/heuristics'
5 | require 'linguist/language'
6 | require 'linguist/repository'
7 | require 'linguist/samples'
8 | require 'linguist/shebang'
9 | require 'linguist/version'
10 | require 'linguist/strategy/xml'
11 |
12 | class << Linguist
13 | # Public: Detects the Language of the blob.
14 | #
15 | # blob - an object that includes the Linguist `BlobHelper` interface;
16 | # see Linguist::LazyBlob and Linguist::FileBlob for examples
17 | #
18 | # Returns Language or nil.
19 | def detect(blob, allow_empty: false)
20 | # Bail early if the blob is binary or empty.
21 | return nil if blob.likely_binary? || blob.binary? || (!allow_empty && blob.empty?)
22 |
23 | Linguist.instrument("linguist.detection", :blob => blob) do
24 | # Call each strategy until one candidate is returned.
25 | languages = []
26 | returning_strategy = nil
27 |
28 | STRATEGIES.each do |strategy|
29 | returning_strategy = strategy
30 | candidates = Linguist.instrument("linguist.strategy", :blob => blob, :strategy => strategy, :candidates => languages) do
31 | strategy.call(blob, languages)
32 | end
33 | if candidates.size == 1
34 | languages = candidates
35 | break
36 | elsif candidates.size > 1
37 | # More than one candidate was found, pass them to the next strategy.
38 | languages = candidates
39 | else
40 | # No candidates, try the next strategy
41 | end
42 | end
43 |
44 | Linguist.instrument("linguist.detected", :blob => blob, :strategy => returning_strategy, :language => languages.first)
45 |
46 | languages.first
47 | end
48 | end
49 |
50 | # Internal: The strategies used to detect the language of a file.
51 | #
52 | # A strategy is an object that has a `.call` method that takes two arguments:
53 | #
54 | # blob - An object that quacks like a blob.
55 | # languages - An Array of candidate Language objects that were returned by the
56 | # previous strategy.
57 | #
58 | # A strategy should return an Array of Language candidates.
59 | #
60 | # Strategies are called in turn until a single Language is returned.
61 | STRATEGIES = [
62 | Linguist::Strategy::Modeline,
63 | Linguist::Strategy::Filename,
64 | Linguist::Shebang,
65 | Linguist::Strategy::Extension,
66 | Linguist::Strategy::XML,
67 | Linguist::Heuristics,
68 | Linguist::Classifier
69 | ]
70 |
71 | # Public: Set an instrumenter.
72 | #
73 | # class CustomInstrumenter
74 | # def instrument(name, payload = {})
75 | # warn "Instrumenting #{name}: #{payload[:blob]}"
76 | # end
77 | # end
78 | #
79 | # Linguist.instrumenter = CustomInstrumenter.new
80 | #
81 | # The instrumenter must conform to the `ActiveSupport::Notifications`
82 | # interface, which defines `#instrument` and accepts:
83 | #
84 | # name - the String name of the event (e.g. "linguist.detected")
85 | # payload - a Hash of the exception context.
86 | attr_accessor :instrumenter
87 |
88 | # Internal: Perform instrumentation on a block
89 | #
90 | # Linguist.instrument("linguist.dosomething", :blob => blob) do
91 | # # logic to instrument here.
92 | # end
93 | #
94 | def instrument(*args, &bk)
95 | if instrumenter
96 | instrumenter.instrument(*args, &bk)
97 | elsif block_given?
98 | yield
99 | end
100 | end
101 |
102 | end
103 |
--------------------------------------------------------------------------------
/lib/linguist/VERSION:
--------------------------------------------------------------------------------
1 | 7.1.3
2 |
--------------------------------------------------------------------------------
/lib/linguist/blob.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/blob_helper'
2 |
3 | module Linguist
4 | # A Blob is a wrapper around the content of a file to make it quack
5 | # like a Grit::Blob. It provides the basic interface: `name`,
6 | # `data`, `path` and `size`.
7 | class Blob
8 | include BlobHelper
9 |
10 | # Public: Initialize a new Blob.
11 | #
12 | # path - A path String (does not necessarily exists on the file system).
13 | # content - Content of the file.
14 | # symlink - Whether the file is a symlink.
15 | #
16 | # Returns a Blob.
17 | def initialize(path, content, symlink: false)
18 | @path = path
19 | @content = content
20 | @symlink = symlink
21 | end
22 |
23 | # Public: Filename
24 | #
25 | # Examples
26 | #
27 | # Blob.new("/path/to/linguist/lib/linguist.rb", "").path
28 | # # => "/path/to/linguist/lib/linguist.rb"
29 | #
30 | # Returns a String
31 | attr_reader :path
32 |
33 | # Public: File name
34 | #
35 | # Returns a String
36 | def name
37 | File.basename(@path)
38 | end
39 |
40 | # Public: File contents.
41 | #
42 | # Returns a String.
43 | def data
44 | @content
45 | end
46 |
47 | # Public: Get byte size
48 | #
49 | # Returns an Integer.
50 | def size
51 | @content.bytesize
52 | end
53 |
54 | # Public: Get file extension.
55 | #
56 | # Returns a String.
57 | def extension
58 | extensions.last || ""
59 | end
60 |
61 | # Public: Return an array of the file extensions
62 | #
63 | # >> Linguist::Blob.new("app/views/things/index.html.erb").extensions
64 | # => [".html.erb", ".erb"]
65 | #
66 | # Returns an Array
67 | def extensions
68 | _, *segments = name.downcase.split(".", -1)
69 |
70 | segments.map.with_index do |segment, index|
71 | "." + segments[index..-1].join(".")
72 | end
73 | end
74 |
75 | # Public: Is this a symlink?
76 | #
77 | # Returns true or false.
78 | def symlink?
79 | @symlink
80 | end
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/lib/linguist/blob_helper.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/generated'
2 | require 'charlock_holmes'
3 | require 'escape_utils'
4 | require 'mime/types'
5 | require 'yaml'
6 |
7 | module Linguist
8 | # DEPRECATED Avoid mixing into Blob classes. Prefer functional interfaces
9 | # like `Linguist.detect` over `Blob#language`. Functions are much easier to
10 | # cache and compose.
11 | #
12 | # Avoid adding additional bloat to this module.
13 | #
14 | # BlobHelper is a mixin for Blobish classes that respond to "name",
15 | # "data" and "size" such as Grit::Blob.
16 | module BlobHelper
17 | # Public: Get the extname of the path
18 | #
19 | # Examples
20 | #
21 | # blob(name='foo.rb').extname
22 | # # => '.rb'
23 | #
24 | # Returns a String
25 | def extname
26 | File.extname(name.to_s)
27 | end
28 |
29 | # Internal: Lookup mime type for extension.
30 | #
31 | # Returns a MIME::Type
32 | def _mime_type
33 | if defined? @_mime_type
34 | @_mime_type
35 | else
36 | guesses = ::MIME::Types.type_for(extname.to_s)
37 |
38 | # Prefer text mime types over binary
39 | @_mime_type = guesses.detect { |type| type.ascii? } ||
40 | # Otherwise use the first guess
41 | guesses.first
42 | end
43 | end
44 |
45 | # Public: Get the actual blob mime type
46 | #
47 | # Examples
48 | #
49 | # # => 'text/plain'
50 | # # => 'text/html'
51 | #
52 | # Returns a mime type String.
53 | def mime_type
54 | _mime_type ? _mime_type.to_s : 'text/plain'
55 | end
56 |
57 | # Internal: Is the blob binary according to its mime type
58 | #
59 | # Return true or false
60 | def binary_mime_type?
61 | _mime_type ? _mime_type.binary? : false
62 | end
63 |
64 | # Internal: Is the blob binary according to its mime type,
65 | # overriding it if we have better data from the languages.yml
66 | # database.
67 | #
68 | # Return true or false
69 | def likely_binary?
70 | binary_mime_type? && !Language.find_by_filename(name)
71 | end
72 |
73 | # Public: Get the Content-Type header value
74 | #
75 | # This value is used when serving raw blobs.
76 | #
77 | # Examples
78 | #
79 | # # => 'text/plain; charset=utf-8'
80 | # # => 'application/octet-stream'
81 | #
82 | # Returns a content type String.
83 | def content_type
84 | @content_type ||= (binary_mime_type? || binary?) ? mime_type :
85 | (encoding ? "text/plain; charset=#{encoding.downcase}" : "text/plain")
86 | end
87 |
88 | # Public: Get the Content-Disposition header value
89 | #
90 | # This value is used when serving raw blobs.
91 | #
92 | # # => "attachment; filename=file.tar"
93 | # # => "inline"
94 | #
95 | # Returns a content disposition String.
96 | def disposition
97 | if text? || image?
98 | 'inline'
99 | elsif name.nil?
100 | "attachment"
101 | else
102 | "attachment; filename=#{EscapeUtils.escape_url(name)}"
103 | end
104 | end
105 |
106 | def encoding
107 | if hash = detect_encoding
108 | hash[:encoding]
109 | end
110 | end
111 |
112 | def ruby_encoding
113 | if hash = detect_encoding
114 | hash[:ruby_encoding]
115 | end
116 | end
117 |
118 | # Try to guess the encoding
119 | #
120 | # Returns: a Hash, with :encoding, :confidence, :type
121 | # this will return nil if an error occurred during detection or
122 | # no valid encoding could be found
123 | def detect_encoding
124 | @detect_encoding ||= CharlockHolmes::EncodingDetector.new.detect(data) if data
125 | end
126 |
127 | # Public: Is the blob binary?
128 | #
129 | # Return true or false
130 | def binary?
131 | # Large blobs aren't even loaded into memory
132 | if data.nil?
133 | true
134 |
135 | # Treat blank files as text
136 | elsif data == ""
137 | false
138 |
139 | # Charlock doesn't know what to think
140 | elsif encoding.nil?
141 | true
142 |
143 | # If Charlock says its binary
144 | else
145 | detect_encoding[:type] == :binary
146 | end
147 | end
148 |
149 | # Public: Is the blob empty?
150 | #
151 | # Return true or false
152 | def empty?
153 | data.nil? || data == ""
154 | end
155 |
156 | # Public: Is the blob text?
157 | #
158 | # Return true or false
159 | def text?
160 | !binary?
161 | end
162 |
163 | # Public: Is the blob a supported image format?
164 | #
165 | # Return true or false
166 | def image?
167 | ['.png', '.jpg', '.jpeg', '.gif'].include?(extname.downcase)
168 | end
169 |
170 | # Public: Is the blob a supported 3D model format?
171 | #
172 | # Return true or false
173 | def solid?
174 | extname.downcase == '.stl'
175 | end
176 |
177 | # Public: Is this blob a CSV file?
178 | #
179 | # Return true or false
180 | def csv?
181 | text? && extname.downcase == '.csv'
182 | end
183 |
184 | # Public: Is the blob a PDF?
185 | #
186 | # Return true or false
187 | def pdf?
188 | extname.downcase == '.pdf'
189 | end
190 |
191 | MEGABYTE = 1024 * 1024
192 |
193 | # Public: Is the blob too big to load?
194 | #
195 | # Return true or false
196 | def large?
197 | size.to_i > MEGABYTE
198 | end
199 |
200 | # Public: Is the blob safe to colorize?
201 | #
202 | # Return true or false
203 | def safe_to_colorize?
204 | !large? && text? && !high_ratio_of_long_lines?
205 | end
206 |
207 | # Internal: Does the blob have a ratio of long lines?
208 | #
209 | # Return true or false
210 | def high_ratio_of_long_lines?
211 | return false if loc == 0
212 | size / loc > 5000
213 | end
214 |
215 | # Public: Is the blob viewable?
216 | #
217 | # Non-viewable blobs will just show a "View Raw" link
218 | #
219 | # Return true or false
220 | def viewable?
221 | !large? && text?
222 | end
223 |
224 | vendored_paths = YAML.load_file(File.expand_path("../vendor.yml", __FILE__))
225 | VendoredRegexp = Regexp.new(vendored_paths.join('|'))
226 |
227 | # Public: Is the blob in a vendored directory?
228 | #
229 | # Vendored files are ignored by language statistics.
230 | #
231 | # See "vendor.yml" for a list of vendored conventions that match
232 | # this pattern.
233 | #
234 | # Return true or false
235 | def vendored?
236 | path =~ VendoredRegexp ? true : false
237 | end
238 |
239 | documentation_paths = YAML.load_file(File.expand_path("../documentation.yml", __FILE__))
240 | DocumentationRegexp = Regexp.new(documentation_paths.join('|'))
241 |
242 | # Public: Is the blob in a documentation directory?
243 | #
244 | # Documentation files are ignored by language statistics.
245 | #
246 | # See "documentation.yml" for a list of documentation conventions that match
247 | # this pattern.
248 | #
249 | # Return true or false
250 | def documentation?
251 | path =~ DocumentationRegexp ? true : false
252 | end
253 |
254 | # Public: Get each line of data
255 | #
256 | # Requires Blob#data
257 | #
258 | # Returns an Array of lines
259 | def lines
260 | @lines ||=
261 | if viewable? && data
262 | # `data` is usually encoded as ASCII-8BIT even when the content has
263 | # been detected as a different encoding. However, we are not allowed
264 | # to change the encoding of `data` because we've made the implicit
265 | # guarantee that each entry in `lines` is encoded the same way as
266 | # `data`.
267 | #
268 | # Instead, we re-encode each possible newline sequence as the
269 | # detected encoding, then force them back to the encoding of `data`
270 | # (usually a binary encoding like ASCII-8BIT). This means that the
271 | # byte sequence will match how newlines are likely encoded in the
272 | # file, but we don't have to change the encoding of `data` as far as
273 | # Ruby is concerned. This allows us to correctly parse out each line
274 | # without changing the encoding of `data`, and
275 | # also--importantly--without having to duplicate many (potentially
276 | # large) strings.
277 | begin
278 |
279 | data.split(encoded_newlines_re, -1)
280 | rescue Encoding::ConverterNotFoundError
281 | # The data is not splittable in the detected encoding. Assume it's
282 | # one big line.
283 | [data]
284 | end
285 | else
286 | []
287 | end
288 | end
289 |
290 | def encoded_newlines_re
291 | @encoded_newlines_re ||= Regexp.union(["\r\n", "\r", "\n"].
292 | map { |nl| nl.encode(ruby_encoding, "ASCII-8BIT").force_encoding(data.encoding) })
293 |
294 | end
295 |
296 | def first_lines(n)
297 | return lines[0...n] if defined? @lines
298 | return [] unless viewable? && data
299 |
300 | i, c = 0, 0
301 | while c < n && j = data.index(encoded_newlines_re, i)
302 | i = j + $&.length
303 | c += 1
304 | end
305 | data[0...i].split(encoded_newlines_re, -1)
306 | end
307 |
308 | def last_lines(n)
309 | if defined? @lines
310 | if n >= @lines.length
311 | @lines
312 | else
313 | lines[-n..-1]
314 | end
315 | end
316 | return [] unless viewable? && data
317 |
318 | no_eol = true
319 | i, c = data.length, 0
320 | k = i
321 | while c < n && j = data.rindex(encoded_newlines_re, i - 1)
322 | if c == 0 && j + $&.length == i
323 | no_eol = false
324 | n += 1
325 | end
326 | i = j
327 | k = j + $&.length
328 | c += 1
329 | end
330 | r = data[k..-1].split(encoded_newlines_re, -1)
331 | r.pop if !no_eol
332 | r
333 | end
334 |
335 | # Public: Get number of lines of code
336 | #
337 | # Requires Blob#data
338 | #
339 | # Returns Integer
340 | def loc
341 | lines.size
342 | end
343 |
344 | # Public: Get number of source lines of code
345 | #
346 | # Requires Blob#data
347 | #
348 | # Returns Integer
349 | def sloc
350 | lines.grep(/\S/).size
351 | end
352 |
353 | # Public: Is the blob a generated file?
354 | #
355 | # Generated source code is suppressed in diffs and is ignored by
356 | # language statistics.
357 | #
358 | # May load Blob#data
359 | #
360 | # Return true or false
361 | def generated?
362 | @_generated ||= Generated.generated?(path, lambda { data })
363 | end
364 |
365 | # Public: Detects the Language of the blob.
366 | #
367 | # May load Blob#data
368 | #
369 | # Returns a Language or nil if none is detected
370 | def language
371 | @language ||= Linguist.detect(self)
372 | end
373 |
374 | # Internal: Get the TextMate compatible scope for the blob
375 | def tm_scope
376 | language && language.tm_scope
377 | end
378 |
379 | DETECTABLE_TYPES = [:programming, :markup].freeze
380 |
381 | # Internal: Should this blob be included in repository language statistics?
382 | def include_in_language_stats?
383 | !vendored? &&
384 | !documentation? &&
385 | !generated? &&
386 | language && ( defined?(detectable?) && !detectable?.nil? ?
387 | detectable? :
388 | DETECTABLE_TYPES.include?(language.type)
389 | )
390 | end
391 | end
392 | end
393 |
--------------------------------------------------------------------------------
/lib/linguist/classifier.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/tokenizer'
2 |
3 | module Linguist
4 | # Language bayesian classifier.
5 | class Classifier
6 | CLASSIFIER_CONSIDER_BYTES = 50 * 1024
7 |
8 | # Public: Use the classifier to detect language of the blob.
9 | #
10 | # blob - An object that quacks like a blob.
11 | # possible_languages - Array of Language objects
12 | #
13 | # Examples
14 | #
15 | # Classifier.call(FileBlob.new("path/to/file"), [
16 | # Language["Ruby"], Language["Python"]
17 | # ])
18 | #
19 | # Returns an Array of Language objects, most probable first.
20 | def self.call(blob, possible_languages)
21 | language_names = possible_languages.map(&:name)
22 | classify(Samples.cache, blob.data[0...CLASSIFIER_CONSIDER_BYTES], language_names).map do |name, _|
23 | Language[name] # Return the actual Language objects
24 | end
25 | end
26 |
27 | # Public: Train classifier that data is a certain language.
28 | #
29 | # db - Hash classifier database object
30 | # language - String language of data
31 | # data - String contents of file
32 | #
33 | # Examples
34 | #
35 | # Classifier.train(db, 'Ruby', "def hello; end")
36 | #
37 | # Returns nothing.
38 | #
39 | # Set LINGUIST_DEBUG=1 or =2 to see probabilities per-token or
40 | # per-language. See also #dump_all_tokens, below.
41 | def self.train!(db, language, data)
42 | tokens = Tokenizer.tokenize(data)
43 |
44 | db['tokens_total'] ||= 0
45 | db['languages_total'] ||= 0
46 | db['tokens'] ||= {}
47 | db['language_tokens'] ||= {}
48 | db['languages'] ||= {}
49 |
50 | tokens.each do |token|
51 | db['tokens'][language] ||= {}
52 | db['tokens'][language][token] ||= 0
53 | db['tokens'][language][token] += 1
54 | db['language_tokens'][language] ||= 0
55 | db['language_tokens'][language] += 1
56 | db['tokens_total'] += 1
57 | end
58 | db['languages'][language] ||= 0
59 | db['languages'][language] += 1
60 | db['languages_total'] += 1
61 |
62 | nil
63 | end
64 |
65 | # Public: Guess language of data.
66 | #
67 | # db - Hash of classifier tokens database.
68 | # data - Array of tokens or String data to analyze.
69 | # languages - Array of language name Strings to restrict to.
70 | #
71 | # Examples
72 | #
73 | # Classifier.classify(db, "def hello; end")
74 | # # => [ 'Ruby', 0.90], ['Python', 0.2], ... ]
75 | #
76 | # Returns sorted Array of result pairs. Each pair contains the
77 | # String language name and a Float score.
78 | def self.classify(db, tokens, languages = nil)
79 | languages ||= db['languages'].keys
80 | new(db).classify(tokens, languages)
81 | end
82 |
83 | # Internal: Initialize a Classifier.
84 | def initialize(db = {})
85 | @tokens_total = db['tokens_total']
86 | @languages_total = db['languages_total']
87 | @tokens = db['tokens']
88 | @language_tokens = db['language_tokens']
89 | @languages = db['languages']
90 | end
91 |
92 | # Internal: Guess language of data
93 | #
94 | # data - Array of tokens or String data to analyze.
95 | # languages - Array of language name Strings to restrict to.
96 | #
97 | # Returns sorted Array of result pairs. Each pair contains the
98 | # String language name and a Float score.
99 | def classify(tokens, languages)
100 | return [] if tokens.nil? || languages.empty?
101 | tokens = Tokenizer.tokenize(tokens) if tokens.is_a?(String)
102 | scores = {}
103 |
104 | debug_dump_all_tokens(tokens, languages) if verbosity >= 2
105 |
106 | languages.each do |language|
107 | scores[language] = tokens_probability(tokens, language) + language_probability(language)
108 | debug_dump_probabilities(tokens, language, scores[language]) if verbosity >= 1
109 | end
110 |
111 | scores.sort { |a, b| b[1] <=> a[1] }.map { |score| [score[0], score[1]] }
112 | end
113 |
114 | # Internal: Probably of set of tokens in a language occurring - P(D | C)
115 | #
116 | # tokens - Array of String tokens.
117 | # language - Language to check.
118 | #
119 | # Returns Float between 0.0 and 1.0.
120 | def tokens_probability(tokens, language)
121 | tokens.inject(0.0) do |sum, token|
122 | sum += Math.log(token_probability(token, language))
123 | end
124 | end
125 |
126 | # Internal: Probably of token in language occurring - P(F | C)
127 | #
128 | # token - String token.
129 | # language - Language to check.
130 | #
131 | # Returns Float between 0.0 and 1.0.
132 | def token_probability(token, language)
133 | if @tokens[language][token].to_f == 0.0
134 | 1 / @tokens_total.to_f
135 | else
136 | @tokens[language][token].to_f / @language_tokens[language].to_f
137 | end
138 | end
139 |
140 | # Internal: Probably of a language occurring - P(C)
141 | #
142 | # language - Language to check.
143 | #
144 | # Returns Float between 0.0 and 1.0.
145 | def language_probability(language)
146 | Math.log(@languages[language].to_f / @languages_total.to_f)
147 | end
148 |
149 | private
150 | def verbosity
151 | @verbosity ||= (ENV['LINGUIST_DEBUG'] || 0).to_i
152 | end
153 |
154 | def debug_dump_probabilities(tokens, language, score)
155 | printf("%10s = %10.3f + %7.3f = %10.3f\n",
156 | language, tokens_probability(tokens, language), language_probability(language), score)
157 | end
158 |
159 | # Internal: show a table of probabilities for each pair.
160 | #
161 | # The number in each table entry is the number of "points" that each
162 | # token contributes toward the belief that the file under test is a
163 | # particular language. Points are additive.
164 | #
165 | # Points are the number of times a token appears in the file, times
166 | # how much more likely (log of probability ratio) that token is to
167 | # appear in one language vs. the least-likely language. Dashes
168 | # indicate the least-likely language (and zero points) for each token.
169 | def debug_dump_all_tokens(tokens, languages)
170 | maxlen = tokens.map { |tok| tok.size }.max
171 |
172 | printf "%#{maxlen}s", ""
173 | puts " #" + languages.map { |lang| sprintf("%10s", lang) }.join
174 |
175 | token_map = Hash.new(0)
176 | tokens.each { |tok| token_map[tok] += 1 }
177 |
178 | token_map.sort.each { |tok, count|
179 | arr = languages.map { |lang| [lang, token_probability(tok, lang)] }
180 | min = arr.map { |a,b| b }.min
181 | minlog = Math.log(min)
182 | if !arr.inject(true) { |result, n| result && n[1] == arr[0][1] }
183 | printf "%#{maxlen}s%5d", tok, count
184 |
185 | puts arr.map { |ent|
186 | ent[1] == min ? " -" : sprintf("%10.3f", count * (Math.log(ent[1]) - minlog))
187 | }.join
188 | end
189 | }
190 | end
191 | end
192 | end
193 |
--------------------------------------------------------------------------------
/lib/linguist/documentation.yml:
--------------------------------------------------------------------------------
1 | # Documentation files and directories are excluded from language
2 | # statistics.
3 | #
4 | # Lines in this file are Regexps that are matched against the file
5 | # pathname.
6 | #
7 | # Please add additional test coverage to
8 | # `test/test_blob.rb#test_documentation` if you make any changes.
9 |
10 | ## Documentation directories ##
11 |
12 | - ^[Dd]ocs?/
13 | - (^|/)[Dd]ocumentation/
14 | - (^|/)[Gg]roovydoc/
15 | - (^|/)[Jj]avadoc/
16 | - ^[Mm]an/
17 | - ^[Ee]xamples/
18 | - ^[Dd]emos?/
19 | - (^|/)inst/doc/
20 |
21 | ## Documentation files ##
22 |
23 | - (^|/)CHANGE(S|LOG)?(\.|$)
24 | - (^|/)CONTRIBUTING(\.|$)
25 | - (^|/)COPYING(\.|$)
26 | - (^|/)INSTALL(\.|$)
27 | - (^|/)LICEN[CS]E(\.|$)
28 | - (^|/)[Ll]icen[cs]e(\.|$)
29 | - (^|/)README(\.|$)
30 | - (^|/)[Rr]eadme(\.|$)
31 |
32 | # Samples folders
33 | - ^[Ss]amples?/
34 |
--------------------------------------------------------------------------------
/lib/linguist/file_blob.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/blob_helper'
2 | require 'linguist/blob'
3 |
4 | module Linguist
5 | # A FileBlob is a wrapper around a File object to make it quack
6 | # like a Grit::Blob. It provides the basic interface: `name`,
7 | # `data`, `path` and `size`.
8 | class FileBlob < Blob
9 | include BlobHelper
10 |
11 | # Public: Initialize a new FileBlob from a path
12 | #
13 | # path - A path String that exists on the file system.
14 | # base_path - Optional base to relativize the path
15 | #
16 | # Returns a FileBlob.
17 | def initialize(path, base_path = nil)
18 | @fullpath = path
19 | @path = base_path ? path.sub("#{base_path}/", '') : path
20 | end
21 |
22 | # Public: Read file permissions
23 | #
24 | # Returns a String like '100644'
25 | def mode
26 | @mode ||= File.stat(@fullpath).mode.to_s(8)
27 | end
28 |
29 | def symlink?
30 | return @symlink if defined? @symlink
31 | @symlink = (File.symlink?(@fullpath) rescue false)
32 | end
33 |
34 | # Public: Read file contents.
35 | #
36 | # Returns a String.
37 | def data
38 | @data ||= File.read(@fullpath)
39 | end
40 |
41 | # Public: Get byte size
42 | #
43 | # Returns an Integer.
44 | def size
45 | @size ||= File.size(@fullpath)
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/linguist/generated.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | class Generated
3 | # Public: Is the blob a generated file?
4 | #
5 | # name - String filename
6 | # data - String blob data. A block also may be passed in for lazy
7 | # loading. This behavior is deprecated and you should always
8 | # pass in a String.
9 | #
10 | # Return true or false
11 | def self.generated?(name, data)
12 | new(name, data).generated?
13 | end
14 |
15 | # Internal: Initialize Generated instance
16 | #
17 | # name - String filename
18 | # data - String blob data
19 | def initialize(name, data)
20 | @name = name
21 | @extname = File.extname(name)
22 | @_data = data
23 | end
24 |
25 | attr_reader :name, :extname
26 |
27 | # Lazy load blob data if block was passed in.
28 | #
29 | # Awful, awful stuff happening here.
30 | #
31 | # Returns String data.
32 | def data
33 | @data ||= @_data.respond_to?(:call) ? @_data.call() : @_data
34 | end
35 |
36 | # Public: Get each line of data
37 | #
38 | # Returns an Array of lines
39 | def lines
40 | # TODO: data should be required to be a String, no nils
41 | @lines ||= data ? data.split("\n", -1) : []
42 | end
43 |
44 | # Internal: Is the blob a generated file?
45 | #
46 | # Generated source code is suppressed in diffs and is ignored by
47 | # language statistics.
48 | #
49 | # Please add additional test coverage to
50 | # `test/test_blob.rb#test_generated` if you make any changes.
51 | #
52 | # Return true or false
53 | def generated?
54 | xcode_file? ||
55 | cocoapods? ||
56 | carthage_build? ||
57 | generated_graphql_relay? ||
58 | generated_net_designer_file? ||
59 | generated_net_specflow_feature_file? ||
60 | composer_lock? ||
61 | cargo_lock? ||
62 | node_modules? ||
63 | go_vendor? ||
64 | go_lock? ||
65 | npm_shrinkwrap_or_package_lock? ||
66 | godeps? ||
67 | go_mod? ||
68 | generated_by_zephir? ||
69 | minified_files? ||
70 | has_source_map? ||
71 | source_map? ||
72 | compiled_coffeescript? ||
73 | generated_parser? ||
74 | generated_net_docfile? ||
75 | generated_postscript? ||
76 | compiled_cython_file? ||
77 | generated_go? ||
78 | generated_protocol_buffer? ||
79 | generated_javascript_protocol_buffer? ||
80 | generated_apache_thrift? ||
81 | generated_jni_header? ||
82 | vcr_cassette? ||
83 | generated_module? ||
84 | generated_unity3d_meta? ||
85 | generated_racc? ||
86 | generated_jflex? ||
87 | generated_grammarkit? ||
88 | generated_roxygen2? ||
89 | generated_pkgdown? ||
90 | generated_jison? ||
91 | generated_yarn_lock? ||
92 | generated_grpc_cpp? ||
93 | generated_dart? ||
94 | generated_perl_ppport_header? ||
95 | generated_gamemakerstudio?
96 | end
97 |
98 | # Internal: Is the blob an Xcode file?
99 | #
100 | # Generated if the file extension is an Xcode
101 | # file extension.
102 | #
103 | # Returns true of false.
104 | def xcode_file?
105 | ['.nib', '.xcworkspacedata', '.xcuserstate'].include?(extname)
106 | end
107 |
108 | # Internal: Is the blob part of Pods/, which contains dependencies not meant for humans in pull requests.
109 | #
110 | # Returns true or false.
111 | def cocoapods?
112 | !!name.match(/(^Pods|\/Pods)\//)
113 | end
114 |
115 | # Internal: Is the blob part of Carthage/Build/, which contains dependencies not meant for humans in pull requests.
116 | #
117 | # Returns true or false.
118 | def carthage_build?
119 | !!name.match(/(^|\/)Carthage\/Build\//)
120 | end
121 |
122 | # Internal: Is the blob minified files?
123 | #
124 | # Consider a file minified if the average line length is
125 | # greater then 110c.
126 | #
127 | # Currently, only JS and CSS files are detected by this method.
128 | #
129 | # Returns true or false.
130 | def minified_files?
131 | return unless ['.js', '.css'].include? extname
132 | if lines.any?
133 | (lines.inject(0) { |n, l| n += l.length } / lines.length) > 110
134 | else
135 | false
136 | end
137 | end
138 |
139 | # Internal: Does the blob contain a source map reference?
140 | #
141 | # We assume that if one of the last 2 lines starts with a source map
142 | # reference, then the current file was generated from other files.
143 | #
144 | # We use the last 2 lines because the last line might be empty.
145 | #
146 | # We only handle JavaScript, no CSS support yet.
147 | #
148 | # Returns true or false.
149 | def has_source_map?
150 | return false unless extname.downcase == '.js'
151 | lines.last(2).any? { |line| line.start_with?('//# sourceMappingURL') }
152 | end
153 |
154 | # Internal: Is the blob a generated source map?
155 | #
156 | # Source Maps usually have .css.map or .js.map extensions. In case they
157 | # are not following the name convention, detect them based on the content.
158 | #
159 | # Returns true or false.
160 | def source_map?
161 | return false unless extname.downcase == '.map'
162 |
163 | name =~ /(\.css|\.js)\.map$/i || # Name convention
164 | lines[0] =~ /^{"version":\d+,/ || # Revision 2 and later begin with the version number
165 | lines[0] =~ /^\/\*\* Begin line maps\. \*\*\/{/ # Revision 1 begins with a magic comment
166 | end
167 |
168 | # Internal: Is the blob of JS generated by CoffeeScript?
169 | #
170 | # CoffeeScript is meant to output JS that would be difficult to
171 | # tell if it was generated or not. Look for a number of patterns
172 | # output by the CS compiler.
173 | #
174 | # Return true or false
175 | def compiled_coffeescript?
176 | return false unless extname == '.js'
177 |
178 | # CoffeeScript generated by > 1.2 include a comment on the first line
179 | if lines[0] =~ /^\/\/ Generated by /
180 | return true
181 | end
182 |
183 | if lines[0] == '(function() {' && # First line is module closure opening
184 | lines[-2] == '}).call(this);' && # Second to last line closes module closure
185 | lines[-1] == '' # Last line is blank
186 |
187 | score = 0
188 |
189 | lines.each do |line|
190 | if line =~ /var /
191 | # Underscored temp vars are likely to be Coffee
192 | score += 1 * line.gsub(/(_fn|_i|_len|_ref|_results)/).count
193 |
194 | # bind and extend functions are very Coffee specific
195 | score += 3 * line.gsub(/(__bind|__extends|__hasProp|__indexOf|__slice)/).count
196 | end
197 | end
198 |
199 | # Require a score of 3. This is fairly arbitrary. Consider
200 | # tweaking later.
201 | score >= 3
202 | else
203 | false
204 | end
205 | end
206 |
207 | # Internal: Is this a generated documentation file for a .NET assembly?
208 | #
209 | # .NET developers often check in the XML Intellisense file along with an
210 | # assembly - however, these don't have a special extension, so we have to
211 | # dig into the contents to determine if it's a docfile. Luckily, these files
212 | # are extremely structured, so recognizing them is easy.
213 | #
214 | # Returns true or false
215 | def generated_net_docfile?
216 | return false unless extname.downcase == ".xml"
217 | return false unless lines.count > 3
218 |
219 | # .NET Docfiles always open with and their first tag is an
220 | # tag
221 | return lines[1].include?("") &&
222 | lines[2].include?("") &&
223 | lines[-2].include?("")
224 | end
225 |
226 | # Internal: Is this a codegen file for a .NET project?
227 | #
228 | # Visual Studio often uses code generation to generate partial classes, and
229 | # these files can be quite unwieldy. Let's hide them.
230 | #
231 | # Returns true or false
232 | def generated_net_designer_file?
233 | name.downcase =~ /\.designer\.(cs|vb)$/
234 | end
235 |
236 | # Internal: Is this a codegen file for Specflow feature file?
237 | #
238 | # Visual Studio's SpecFlow extension generates *.feature.cs files
239 | # from *.feature files, they are not meant to be consumed by humans.
240 | # Let's hide them.
241 | #
242 | # Returns true or false
243 | def generated_net_specflow_feature_file?
244 | name.downcase =~ /\.feature\.cs$/
245 | end
246 |
247 | # Internal: Is the blob of JS a parser generated by PEG.js?
248 | #
249 | # PEG.js-generated parsers are not meant to be consumed by humans.
250 | #
251 | # Return true or false
252 | def generated_parser?
253 | return false unless extname == '.js'
254 |
255 | # PEG.js-generated parsers include a comment near the top of the file
256 | # that marks them as such.
257 | if lines[0..4].join('') =~ /^(?:[^\/]|\/[^\*])*\/\*(?:[^\*]|\*[^\/])*Generated by PEG.js/
258 | return true
259 | end
260 |
261 | false
262 | end
263 |
264 | # Internal: Is the blob of PostScript generated?
265 | #
266 | # PostScript files are often generated by other programs. If they tell us so,
267 | # we can detect them.
268 | #
269 | # Returns true or false.
270 | def generated_postscript?
271 | return false unless ['.ps', '.eps', '.pfa'].include? extname
272 |
273 | # Type 1 and Type 42 fonts converted to PostScript are stored as hex-encoded byte streams; these
274 | # streams are always preceded the `eexec` operator (if Type 1), or the `/sfnts` key (if Type 42).
275 | return true if data =~ /(\n|\r\n|\r)\s*(?:currentfile eexec\s+|\/sfnts\s+\[\1<)\h{8,}\1/
276 |
277 | # We analyze the "%%Creator:" comment, which contains the author/generator
278 | # of the file. If there is one, it should be in one of the first few lines.
279 | creator = lines[0..9].find {|line| line =~ /^%%Creator: /}
280 | return false if creator.nil?
281 |
282 | # Most generators write their version number, while human authors' or companies'
283 | # names don't contain numbers. So look if the line contains digits. Also
284 | # look for some special cases without version numbers.
285 | return true if creator =~ /[0-9]|draw|mpage|ImageMagick|inkscape|MATLAB/ ||
286 | creator =~ /PCBNEW|pnmtops|\(Unknown\)|Serif Affinity|Filterimage -tops/
287 |
288 | # EAGLE doesn't include a version number when it generates PostScript.
289 | # However, it does prepend its name to the document's "%%Title" field.
290 | !!creator.include?("EAGLE") and lines[0..4].find {|line| line =~ /^%%Title: EAGLE Drawing /}
291 | end
292 |
293 | def generated_go?
294 | return false unless extname == '.go'
295 | return false unless lines.count > 1
296 |
297 | return lines[0].include?("Code generated by")
298 | end
299 |
300 | PROTOBUF_EXTENSIONS = ['.py', '.java', '.h', '.cc', '.cpp', '.rb']
301 |
302 | # Internal: Is the blob a C++, Java or Python source file generated by the
303 | # Protocol Buffer compiler?
304 | #
305 | # Returns true of false.
306 | def generated_protocol_buffer?
307 | return false unless PROTOBUF_EXTENSIONS.include?(extname)
308 | return false unless lines.count > 1
309 |
310 | return lines[0].include?("Generated by the protocol buffer compiler. DO NOT EDIT!")
311 | end
312 |
313 | # Internal: Is the blob a Javascript source file generated by the
314 | # Protocol Buffer compiler?
315 | #
316 | # Returns true of false.
317 | def generated_javascript_protocol_buffer?
318 | return false unless extname == ".js"
319 | return false unless lines.count > 6
320 |
321 | return lines[5].include?("GENERATED CODE -- DO NOT EDIT!")
322 | end
323 |
324 | APACHE_THRIFT_EXTENSIONS = ['.rb', '.py', '.go', '.js', '.m', '.java', '.h', '.cc', '.cpp', '.php']
325 |
326 | # Internal: Is the blob generated by Apache Thrift compiler?
327 | #
328 | # Returns true or false
329 | def generated_apache_thrift?
330 | return false unless APACHE_THRIFT_EXTENSIONS.include?(extname)
331 | return lines.first(6).any? { |l| l.include?("Autogenerated by Thrift Compiler") }
332 | end
333 |
334 | # Internal: Is the blob a C/C++ header generated by the Java JNI tool javah?
335 | #
336 | # Returns true of false.
337 | def generated_jni_header?
338 | return false unless extname == '.h'
339 | return false unless lines.count > 2
340 |
341 | return lines[0].include?("/* DO NOT EDIT THIS FILE - it is machine generated */") &&
342 | lines[1].include?("#include ")
343 | end
344 |
345 | # Internal: Is the blob part of node_modules/, which are not meant for humans in pull requests.
346 | #
347 | # Returns true or false.
348 | def node_modules?
349 | !!name.match(/node_modules\//)
350 | end
351 |
352 | # Internal: Is the blob part of the Go vendor/ tree,
353 | # not meant for humans in pull requests.
354 | #
355 | # Returns true or false.
356 | def go_vendor?
357 | !!name.match(/vendor\/((?!-)[-0-9A-Za-z]+(? 2
416 | # VCR Cassettes have "recorded_with: VCR" in the second last line.
417 | return lines[-2].include?("recorded_with: VCR")
418 | end
419 |
420 | # Internal: Is this a compiled C/C++ file from Cython?
421 | #
422 | # Cython-compiled C/C++ files typically contain:
423 | # /* Generated by Cython x.x.x on ... */
424 | # on the first line.
425 | #
426 | # Return true or false
427 | def compiled_cython_file?
428 | return false unless ['.c', '.cpp'].include? extname
429 | return false unless lines.count > 1
430 | return lines[0].include?("Generated by Cython")
431 | end
432 |
433 | # Internal: Is it a KiCAD or GFortran module file?
434 | #
435 | # KiCAD module files contain:
436 | # PCBNEW-LibModule-V1 yyyy-mm-dd h:mm:ss XM
437 | # on the first line.
438 | #
439 | # GFortran module files contain:
440 | # GFORTRAN module version 'x' created from
441 | # on the first line.
442 | #
443 | # Return true of false
444 | def generated_module?
445 | return false unless extname == '.mod'
446 | return false unless lines.count > 1
447 | return lines[0].include?("PCBNEW-LibModule-V") ||
448 | lines[0].include?("GFORTRAN module version '")
449 | end
450 |
451 | # Internal: Is this a metadata file from Unity3D?
452 | #
453 | # Unity3D Meta files start with:
454 | # fileFormatVersion: X
455 | # guid: XXXXXXXXXXXXXXX
456 | #
457 | # Return true or false
458 | def generated_unity3d_meta?
459 | return false unless extname == '.meta'
460 | return false unless lines.count > 1
461 | return lines[0].include?("fileFormatVersion: ")
462 | end
463 |
464 | # Internal: Is this a Racc-generated file?
465 | #
466 | # A Racc-generated file contains:
467 | # # This file is automatically generated by Racc x.y.z
468 | # on the third line.
469 | #
470 | # Return true or false
471 | def generated_racc?
472 | return false unless extname == '.rb'
473 | return false unless lines.count > 2
474 | return lines[2].start_with?("# This file is automatically generated by Racc")
475 | end
476 |
477 | # Internal: Is this a JFlex-generated file?
478 | #
479 | # A JFlex-generated file contains:
480 | # /* The following code was generated by JFlex x.y.z on d/at/e ti:me */
481 | # on the first line.
482 | #
483 | # Return true or false
484 | def generated_jflex?
485 | return false unless extname == '.java'
486 | return false unless lines.count > 1
487 | return lines[0].start_with?("/* The following code was generated by JFlex ")
488 | end
489 |
490 | # Internal: Is this a GrammarKit-generated file?
491 | #
492 | # A GrammarKit-generated file typically contain:
493 | # // This is a generated file. Not intended for manual editing.
494 | # on the first line. This is not always the case, as it's possible to
495 | # customize the class header.
496 | #
497 | # Return true or false
498 | def generated_grammarkit?
499 | return false unless extname == '.java'
500 | return false unless lines.count > 1
501 | return lines[0].start_with?("// This is a generated file. Not intended for manual editing.")
502 | end
503 |
504 | # Internal: Is this a roxygen2-generated file?
505 | #
506 | # A roxygen2-generated file typically contain:
507 | # % Generated by roxygen2: do not edit by hand
508 | # on the first line.
509 | #
510 | # Return true or false
511 | def generated_roxygen2?
512 | return false unless extname == '.Rd'
513 | return false unless lines.count > 1
514 |
515 | return lines[0].include?("% Generated by roxygen2: do not edit by hand")
516 | end
517 |
518 | # Internal: Is this a pkgdown-generated file?
519 | #
520 | # A pkgdown-generated file typically contains:
521 | #
522 | # on the first or second line.
523 | #
524 | # Return true or false
525 | def generated_pkgdown?
526 | return false unless extname == '.html'
527 | return false unless lines.count > 1
528 |
529 | return lines[0].start_with?("") ||
530 | lines[1].start_with?("")
531 | end
532 |
533 | # Internal: Is this a Jison-generated file?
534 | #
535 | # Jison-generated parsers typically contain:
536 | # /* parser generated by jison
537 | # on the first line.
538 | #
539 | # Jison-generated lexers typically contain:
540 | # /* generated by jison-lex
541 | # on the first line.
542 | #
543 | # Return true or false
544 | def generated_jison?
545 | return false unless extname == '.js'
546 | return false unless lines.count > 1
547 | return lines[0].start_with?("/* parser generated by jison ") ||
548 | lines[0].start_with?("/* generated by jison-lex ")
549 | end
550 |
551 | # Internal: Is the blob a generated yarn lockfile?
552 | #
553 | # Returns true or false.
554 | def generated_yarn_lock?
555 | return false unless name.match(/yarn\.lock/)
556 | return false unless lines.count > 0
557 | return lines[0].include?("# THIS IS AN AUTOGENERATED FILE")
558 | end
559 |
560 | # Internal: Is this a protobuf/grpc-generated C++ file?
561 | #
562 | # A generated file contains:
563 | # // Generated by the gRPC C++ plugin.
564 | # on the first line.
565 | #
566 | # Return true or false
567 | def generated_grpc_cpp?
568 | return false unless %w{.cpp .hpp .h .cc}.include? extname
569 | return false unless lines.count > 1
570 | return lines[0].start_with?("// Generated by the gRPC")
571 | end
572 |
573 | # Internal: Is this a generated Dart file?
574 | #
575 | # A dart-lang/appengine generated file contains:
576 | # // Generated code. Do not modify.
577 | # on the first line.
578 | #
579 | # An owl generated file contains:
580 | # // GENERATED CODE - DO NOT MODIFY
581 | # on the first line.
582 | #
583 | # Return true or false
584 | def generated_dart?
585 | return false unless extname == '.dart'
586 | return false unless lines.count > 1
587 | return lines.first.downcase =~ /generated code\W{2,3}do not modify/
588 | end
589 |
590 | # Internal: Is the file a generated Perl/Pollution/Portability header file?
591 | #
592 | # Returns true or false.
593 | def generated_perl_ppport_header?
594 | return false unless name.match(/ppport\.h$/)
595 | return false unless lines.count > 10
596 | return lines[8].include?("Automatically created by Devel::PPPort")
597 | end
598 |
599 | # Internal: Is this a relay-compiler generated graphql file?
600 | #
601 | # Return true or false
602 | def generated_graphql_relay?
603 | !!name.match(/__generated__\//)
604 | end
605 |
606 | # Internal: Is this a generated Game Maker Studio (2) metadata file?
607 | #
608 | # All Game Maker Studio 2 generated files will be JSON, .yy or .yyp, and have
609 | # a part that looks like "modelName: GMname" on the 3rd line
610 | #
611 | # Return true or false
612 | def generated_gamemakerstudio?
613 | return false unless ['.yy', '.yyp'].include? extname
614 | return false unless lines.count > 3
615 | return lines[2].match(/\"modelName\"\:\s*\"GM/) ||
616 | lines[0] =~ /^\d\.\d\.\d.+\|\{/
617 | end
618 | end
619 | end
620 |
--------------------------------------------------------------------------------
/lib/linguist/grammars.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | module Grammars
3 | # Get the path to the directory containing the language grammar JSON files.
4 | #
5 | # Returns a String.
6 | def self.path
7 | File.expand_path("../../../grammars", __FILE__)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/linguist/heuristics.rb:
--------------------------------------------------------------------------------
1 | require 'yaml'
2 |
3 | module Linguist
4 | # A collection of simple heuristics that can be used to better analyze languages.
5 | class Heuristics
6 | HEURISTICS_CONSIDER_BYTES = 50 * 1024
7 |
8 | # Public: Use heuristics to detect language of the blob.
9 | #
10 | # blob - An object that quacks like a blob.
11 | # possible_languages - Array of Language objects
12 | #
13 | # Examples
14 | #
15 | # Heuristics.call(FileBlob.new("path/to/file"), [
16 | # Language["Ruby"], Language["Python"]
17 | # ])
18 | #
19 | # Returns an Array of languages, or empty if none matched or were inconclusive.
20 | def self.call(blob, candidates)
21 | return [] if blob.symlink?
22 | self.load()
23 |
24 | data = blob.data[0...HEURISTICS_CONSIDER_BYTES]
25 |
26 | @heuristics.each do |heuristic|
27 | if heuristic.matches?(blob.name, candidates)
28 | return Array(heuristic.call(data))
29 | end
30 | end
31 |
32 | [] # No heuristics matched
33 | end
34 |
35 | # Internal: Load heuristics from 'heuristics.yml'.
36 | def self.load()
37 | if @heuristics.any?
38 | return
39 | end
40 |
41 | data = YAML.load_file(File.expand_path("../heuristics.yml", __FILE__))
42 | named_patterns = data['named_patterns'].map { |k,v| [k, self.to_regex(v)] }.to_h
43 |
44 | data['disambiguations'].each do |disambiguation|
45 | exts = disambiguation['extensions']
46 | rules = disambiguation['rules']
47 | rules.map! do |rule|
48 | rule['pattern'] = self.parse_rule(named_patterns, rule)
49 | rule
50 | end
51 | @heuristics << new(exts, rules)
52 | end
53 | end
54 |
55 | def self.parse_rule(named_patterns, rule)
56 | if !rule['and'].nil?
57 | rules = rule['and'].map { |block| self.parse_rule(named_patterns, block) }
58 | return And.new(rules)
59 | elsif !rule['pattern'].nil?
60 | return self.to_regex(rule['pattern'])
61 | elsif !rule['negative_pattern'].nil?
62 | pat = self.to_regex(rule['negative_pattern'])
63 | return NegativePattern.new(pat)
64 | elsif !rule['named_pattern'].nil?
65 | return named_patterns[rule['named_pattern']]
66 | else
67 | return AlwaysMatch.new()
68 | end
69 | end
70 |
71 | # Internal: Converts a string or array of strings to regexp
72 | #
73 | # str: string or array of strings. If it is an array of strings,
74 | # Regexp.union will be used.
75 | def self.to_regex(str)
76 | if str.kind_of?(Array)
77 | Regexp.union(str.map { |s| Regexp.new(s) })
78 | else
79 | Regexp.new(str)
80 | end
81 | end
82 |
83 | # Internal: Array of defined heuristics
84 | @heuristics = []
85 |
86 | # Internal
87 | def initialize(exts_and_langs, rules)
88 | @exts_and_langs = exts_and_langs
89 | @rules = rules
90 | end
91 |
92 | # Internal: Check if this heuristic matches the candidate filenames or
93 | # languages.
94 | def matches?(filename, candidates)
95 | filename = filename.downcase
96 | candidates = candidates.compact.map(&:name)
97 | @exts_and_langs.any? { |ext| filename.end_with?(ext) }
98 | end
99 |
100 | # Internal: Perform the heuristic
101 | def call(data)
102 | matched = @rules.find do |rule|
103 | rule['pattern'].match(data)
104 | end
105 | if !matched.nil?
106 | languages = matched['language']
107 | if languages.is_a?(Array)
108 | languages.map{ |l| Language[l] }
109 | else
110 | Language[languages]
111 | end
112 | end
113 | end
114 | end
115 |
116 | class And
117 |
118 | def initialize(pats)
119 | @pats = pats
120 | end
121 |
122 | def match(input)
123 | return !@pats.any? { |pat| !pat.match(input) }
124 | end
125 |
126 | end
127 |
128 | class AlwaysMatch
129 | def match(input)
130 | return true
131 | end
132 | end
133 |
134 | class NegativePattern
135 |
136 | def initialize(pat)
137 | @pat = pat
138 | end
139 |
140 | def match(input)
141 | return !@pat.match(input)
142 | end
143 |
144 | end
145 | end
146 |
--------------------------------------------------------------------------------
/lib/linguist/heuristics.yml:
--------------------------------------------------------------------------------
1 | # A collection of simple regexp-based rules that can be applied to content
2 | # to disambiguate languages with the same file extension.
3 | #
4 | # There two top-level keys: disambiguations and named_patterns.
5 | #
6 | # disambiguations - a list of disambiguation rules, one for each
7 | # extension or group of extensions.
8 | # extensions - an array of file extensions that this block applies to.
9 | # rules - list of rules that are applied in order to the content
10 | # of a file with matching extension. Rules are evaluated
11 | # until one of them matches. If none matches, no language
12 | # is returned.
13 | # language - Language to be returned if the rule matches.
14 | # pattern - Ruby-compatible regular expression that makes the rule
15 | # match. If no pattern is specified, the rule always match.
16 | # Pattern can be a string with a single regular expression
17 | # or an array of strings that will be merged in a single
18 | # regular expression (with union).
19 | # and - An and block merges multiple rules and checks that all of
20 | # of them must match.
21 | # negative_pattern - Same as pattern, but checks for absence of matches.
22 | # named_pattern - A pattern can be reused by specifying it in the
23 | # named_patterns section and referencing it here by its
24 | # key.
25 | # named_patterns - Key-value map of reusable named patterns.
26 | #
27 | # Please keep this list alphabetized.
28 | #
29 | ---
30 | disambiguations:
31 | - extensions: ['.as']
32 | rules:
33 | - language: ActionScript
34 | pattern: '^\s*(package\s+[a-z0-9_\.]+|import\s+[a-zA-Z0-9_\.]+;|class\s+[A-Za-z0-9_]+\s+extends\s+[A-Za-z0-9_]+)'
35 | - language: AngelScript
36 | - extensions: ['.asc']
37 | rules:
38 | - language: Public Key
39 | pattern: '^(----[- ]BEGIN|ssh-(rsa|dss)) '
40 | - language: AsciiDoc
41 | pattern: '^[=-]+(\s|\n)|{{[A-Za-z]'
42 | - language: AGS Script
43 | pattern: '^(\/\/.+|((import|export)\s+)?(function|int|float|char)\s+((room|repeatedly|on|game)_)?([A-Za-z]+[A-Za-z_0-9]+)\s*[;\(])'
44 | - extensions: ['.bb']
45 | rules:
46 | - language: BlitzBasic
47 | pattern: '(<^\s*; |End Function)'
48 | - language: BitBake
49 | pattern: '^\s*(# |include|require)\b'
50 | - extensions: ['.builds']
51 | rules:
52 | - language: XML
53 | pattern: '^(\s*)(?i:)'
102 | - language: JavaScript
103 | pattern: '(?m:\/\/|("|'')use strict\1|export\s+default\s|\/\*.*?\*\/)'
104 | - extensions: ['.f']
105 | rules:
106 | - language: Forth
107 | pattern: '^: '
108 | - language: Filebench WML
109 | pattern: 'flowop'
110 | - language: Fortran
111 | named_pattern: fortran
112 | - extensions: ['.for']
113 | rules:
114 | - language: Forth
115 | pattern: '^: '
116 | - language: Fortran
117 | named_pattern: fortran
118 | - extensions: ['.fr']
119 | rules:
120 | - language: Forth
121 | pattern: '^(: |also |new-device|previous )'
122 | - language: Frege
123 | pattern: '^\s*(import|module|package|data|type) '
124 | - language: Text
125 | - extensions: ['.fs']
126 | rules:
127 | - language: Forth
128 | pattern: '^(: |new-device)'
129 | - language: 'F#'
130 | pattern: '^\s*(#light|import|let|module|namespace|open|type)'
131 | - language: GLSL
132 | pattern: '^\s*(#version|precision|uniform|varying|vec[234])'
133 | - language: Filterscript
134 | pattern: '#include|#pragma\s+(rs|version)|__attribute__'
135 | - extensions: ['.gml']
136 | rules:
137 | - language: XML
138 | pattern: '(?i:^\s*(\<\?xml|xmlns))'
139 | - language: Graph Modeling Language
140 | pattern: '(?i:^\s*(graph|node)\s+\[$)'
141 | - language: Game Maker Language
142 | - extensions: ['.gs']
143 | rules:
144 | - language: Gosu
145 | pattern: '^uses java\.'
146 | - extensions: ['.h']
147 | rules:
148 | - language: Objective-C
149 | named_pattern: objectivec
150 | - language: C++
151 | named_pattern: cpp
152 | - extensions: ['.hh']
153 | rules:
154 | - language: Hack
155 | pattern: '<\?hh'
156 | - extensions: ['.ice']
157 | rules:
158 | - language: Slice
159 | pattern: '^\s*(#\s*(include|if[n]def|pragma)|module\s+[A-Za-z][_A-Za-z0-9]*)'
160 | - language: JSON
161 | pattern: '/\A\s*[{\[]/'
162 | - extensions: ['.inc']
163 | rules:
164 | - language: PHP
165 | pattern: '^<\?(?:php)?'
166 | - language: POV-Ray SDL
167 | pattern: '^\s*#(declare|local|macro|while)\s'
168 | - extensions: ['.l']
169 | rules:
170 | - language: Common Lisp
171 | pattern: '\(def(un|macro)\s'
172 | - language: Lex
173 | pattern: '^(%[%{}]xs|<.*>)'
174 | - language: Roff
175 | pattern: '^\.[A-Za-z]{2}(\s|$)'
176 | - language: PicoLisp
177 | pattern: '^\((de|class|rel|code|data|must)\s'
178 | - extensions: ['.ls']
179 | rules:
180 | - language: LoomScript
181 | pattern: '^\s*package\s*[\w\.\/\*\s]*\s*{'
182 | - language: LiveScript
183 | - extensions: ['.lsp', '.lisp']
184 | rules:
185 | - language: Common Lisp
186 | pattern: '^\s*\((?i:defun|in-package|defpackage) '
187 | - language: NewLisp
188 | pattern: '^\s*\(define '
189 | - extensions: ['.m']
190 | rules:
191 | - language: Objective-C
192 | named_pattern: objectivec
193 | - language: Mercury
194 | pattern: ':- module'
195 | - language: MUF
196 | pattern: '^: '
197 | - language: M
198 | pattern: '^\s*;'
199 | - language: Mathematica
200 | pattern: '\*\)$'
201 | - language: MATLAB
202 | pattern: '^\s*%'
203 | - language: Limbo
204 | pattern: '^\w+\s*:\s*module\s*{'
205 | - extensions: ['.md']
206 | rules:
207 | - language: Markdown
208 | pattern:
209 | - '(^[-A-Za-z0-9=#!\*\[|>])|<\/'
210 | - '\A\z'
211 | - language: GCC Machine Description
212 | pattern: '^(;;|\(define_)'
213 | - language: Markdown
214 | - extensions: ['.ml']
215 | rules:
216 | - language: OCaml
217 | pattern: '(^\s*module)|let rec |match\s+(\S+\s)+with'
218 | - language: Standard ML
219 | pattern: '=> |case\s+(\S+\s)+of'
220 | - extensions: ['.mod']
221 | rules:
222 | - language: XML
223 | pattern: '\s'
286 | - extensions: ['.pro']
287 | rules:
288 | - language: Prolog
289 | pattern: '^[^\[#]+:-'
290 | - language: INI
291 | pattern: 'last_client='
292 | - language: QMake
293 | and:
294 | - pattern: HEADERS
295 | - pattern: SOURCES
296 | - language: IDL
297 | pattern: '^\s*function[ \w,]+$'
298 | - extensions: ['.properties']
299 | rules:
300 | - language: INI
301 | and:
302 | - named_pattern: key_equals_value
303 | - pattern: '^[;\[]'
304 | - language: Java Properties
305 | and:
306 | - named_pattern: key_equals_value
307 | - pattern: '^[#!]'
308 | - language: INI
309 | named_pattern: key_equals_value
310 | - language: Java properties
311 | pattern: '^[^#!][^:]*:'
312 | - extensions: ['.props']
313 | rules:
314 | - language: XML
315 | pattern: '^(\s*)(?i:)\s*(\d{2}:\d{2}:\d{2},\d{3})$'
371 | - extensions: ['.t']
372 | rules:
373 | - language: Perl
374 | named_pattern: perl5
375 | - language: Perl 6
376 | named_pattern: perl6
377 | - language: Turing
378 | pattern: '^\s*%[ \t]+|^\s*var\s+\w+(\s*:\s*\w+)?\s*:=\s*\w+'
379 | - extensions: ['.toc']
380 | rules:
381 | - language: World of Warcraft Addon Data
382 | pattern: '^## |@no-lib-strip@'
383 | - language: TeX
384 | pattern: '^\\(contentsline|defcounter|beamer|boolfalse)'
385 | - extensions: ['.ts']
386 | rules:
387 | - language: XML
388 | pattern: ' '
394 | # Heads up - we don't usually write heuristics like this (with no regex match)
395 | - language: Scilab
396 | - extensions: ['.tsx']
397 | rules:
398 | - language: TypeScript
399 | pattern: '^\s*(import.+(from\s+|require\()[''"]react|\/\/\/\s*'
424 | - '^\s*template\s*<'
425 | - '^[ \t]*try'
426 | - '^[ \t]*catch\s*\('
427 | - '^[ \t]*(class|(using[ \t]+)?namespace)\s+\w+'
428 | - '^[ \t]*(private|public|protected):$'
429 | - 'std::\w+'
430 | fortran: '^(?i:[c*][^abd-z]| (subroutine|program|end|data)\s|\s*!)'
431 | key_equals_value: '^[^#!;][^=]*='
432 | objectivec: '^\s*(@(interface|class|protocol|property|end|synchronised|selector|implementation)\b|#import\s+.+\.h[">])'
433 | perl5: '\buse\s+(?:strict\b|v?5\.)'
434 | perl6: '^\s*(?:use\s+v6\b|\bmodule\b|\b(?:my\s+)?class\b)'
435 |
--------------------------------------------------------------------------------
/lib/linguist/language.rb:
--------------------------------------------------------------------------------
1 | require 'escape_utils'
2 | require 'yaml'
3 | begin
4 | require 'yajl'
5 | rescue LoadError
6 | end
7 |
8 | require 'linguist/classifier'
9 | require 'linguist/heuristics'
10 | require 'linguist/samples'
11 | require 'linguist/file_blob'
12 | require 'linguist/blob_helper'
13 | require 'linguist/strategy/filename'
14 | require 'linguist/strategy/extension'
15 | require 'linguist/strategy/modeline'
16 | require 'linguist/shebang'
17 |
18 | module Linguist
19 | # Language names that are recognizable by GitHub. Defined languages
20 | # can be highlighted, searched and listed under the Top Languages page.
21 | #
22 | # Languages are defined in `lib/linguist/languages.yml`.
23 | class Language
24 | @languages = []
25 | @index = {}
26 | @name_index = {}
27 | @alias_index = {}
28 | @language_id_index = {}
29 |
30 | @extension_index = Hash.new { |h,k| h[k] = [] }
31 | @interpreter_index = Hash.new { |h,k| h[k] = [] }
32 | @filename_index = Hash.new { |h,k| h[k] = [] }
33 |
34 | # Valid Languages types
35 | TYPES = [:data, :markup, :programming, :prose]
36 |
37 | # Detect languages by a specific type
38 | #
39 | # type - A symbol that exists within TYPES
40 | #
41 | # Returns an array
42 | def self.by_type(type)
43 | all.select { |h| h.type == type }
44 | end
45 |
46 | # Internal: Create a new Language object
47 | #
48 | # attributes - A hash of attributes
49 | #
50 | # Returns a Language object
51 | def self.create(attributes = {})
52 | language = new(attributes)
53 |
54 | @languages << language
55 |
56 | # All Language names should be unique. Raise if there is a duplicate.
57 | if @name_index.key?(language.name)
58 | raise ArgumentError, "Duplicate language name: #{language.name}"
59 | end
60 |
61 | # Language name index
62 | @index[language.name.downcase] = @name_index[language.name.downcase] = language
63 |
64 | language.aliases.each do |name|
65 | # All Language aliases should be unique. Raise if there is a duplicate.
66 | if @alias_index.key?(name)
67 | raise ArgumentError, "Duplicate alias: #{name}"
68 | end
69 |
70 | @index[name.downcase] = @alias_index[name.downcase] = language
71 | end
72 |
73 | language.extensions.each do |extension|
74 | if extension !~ /^\./
75 | raise ArgumentError, "Extension is missing a '.': #{extension.inspect}"
76 | end
77 |
78 | @extension_index[extension.downcase] << language
79 | end
80 |
81 | language.interpreters.each do |interpreter|
82 | @interpreter_index[interpreter] << language
83 | end
84 |
85 | language.filenames.each do |filename|
86 | @filename_index[filename] << language
87 | end
88 |
89 | @language_id_index[language.language_id] = language
90 |
91 | language
92 | end
93 |
94 | # Public: Get all Languages
95 | #
96 | # Returns an Array of Languages
97 | def self.all
98 | @languages
99 | end
100 |
101 | # Public: Look up Language by its proper name.
102 | #
103 | # name - The String name of the Language
104 | #
105 | # Examples
106 | #
107 | # Language.find_by_name('Ruby')
108 | # # => #
109 | #
110 | # Returns the Language or nil if none was found.
111 | def self.find_by_name(name)
112 | return nil if !name.is_a?(String) || name.to_s.empty?
113 | name && (@name_index[name.downcase] || @name_index[name.split(',', 2).first.downcase])
114 | end
115 |
116 | # Public: Look up Language by one of its aliases.
117 | #
118 | # name - A String alias of the Language
119 | #
120 | # Examples
121 | #
122 | # Language.find_by_alias('cpp')
123 | # # => #
124 | #
125 | # Returns the Language or nil if none was found.
126 | def self.find_by_alias(name)
127 | return nil if !name.is_a?(String) || name.to_s.empty?
128 | name && (@alias_index[name.downcase] || @alias_index[name.split(',', 2).first.downcase])
129 | end
130 |
131 | # Public: Look up Languages by filename.
132 | #
133 | # The behaviour of this method recently changed.
134 | # See the second example below.
135 | #
136 | # filename - The path String.
137 | #
138 | # Examples
139 | #
140 | # Language.find_by_filename('Cakefile')
141 | # # => [#]
142 | # Language.find_by_filename('foo.rb')
143 | # # => []
144 | #
145 | # Returns all matching Languages or [] if none were found.
146 | def self.find_by_filename(filename)
147 | basename = File.basename(filename)
148 | @filename_index[basename]
149 | end
150 |
151 | # Public: Look up Languages by file extension.
152 | #
153 | # The behaviour of this method recently changed.
154 | # See the second example below.
155 | #
156 | # filename - The path String.
157 | #
158 | # Examples
159 | #
160 | # Language.find_by_extension('dummy.rb')
161 | # # => [#]
162 | # Language.find_by_extension('rb')
163 | # # => []
164 | #
165 | # Returns all matching Languages or [] if none were found.
166 | def self.find_by_extension(filename)
167 | # find the first extension with language definitions
168 | extname = FileBlob.new(filename.downcase).extensions.detect do |e|
169 | !@extension_index[e].empty?
170 | end
171 |
172 | @extension_index[extname]
173 | end
174 |
175 | # Public: Look up Languages by interpreter.
176 | #
177 | # interpreter - String of interpreter name
178 | #
179 | # Examples
180 | #
181 | # Language.find_by_interpreter("bash")
182 | # # => [#]
183 | #
184 | # Returns the matching Language
185 | def self.find_by_interpreter(interpreter)
186 | @interpreter_index[interpreter]
187 | end
188 |
189 | # Public: Look up Languages by its language_id.
190 | #
191 | # language_id - Integer of language_id
192 | #
193 | # Examples
194 | #
195 | # Language.find_by_id(100)
196 | # # => [#]
197 | #
198 | # Returns the matching Language
199 | def self.find_by_id(language_id)
200 | @language_id_index[language_id.to_i]
201 | end
202 |
203 | # Public: Look up Language by its name.
204 | #
205 | # name - The String name of the Language
206 | #
207 | # Examples
208 | #
209 | # Language['Ruby']
210 | # # => #
211 | #
212 | # Language['ruby']
213 | # # => #
214 | #
215 | # Returns the Language or nil if none was found.
216 | def self.[](name)
217 | return nil if !name.is_a?(String) || name.to_s.empty?
218 |
219 | lang = @index[name.downcase]
220 | return lang if lang
221 |
222 | @index[name.split(',', 2).first.downcase]
223 | end
224 |
225 | # Public: A List of popular languages
226 | #
227 | # Popular languages are sorted to the top of language chooser
228 | # dropdowns.
229 | #
230 | # This list is configured in "popular.yml".
231 | #
232 | # Returns an Array of Languages.
233 | def self.popular
234 | @popular ||= all.select(&:popular?).sort_by { |lang| lang.name.downcase }
235 | end
236 |
237 | # Public: A List of non-popular languages
238 | #
239 | # Unpopular languages appear below popular ones in language
240 | # chooser dropdowns.
241 | #
242 | # This list is created from all the languages not listed in "popular.yml".
243 | #
244 | # Returns an Array of Languages.
245 | def self.unpopular
246 | @unpopular ||= all.select(&:unpopular?).sort_by { |lang| lang.name.downcase }
247 | end
248 |
249 | # Public: A List of languages with assigned colors.
250 | #
251 | # Returns an Array of Languages.
252 | def self.colors
253 | @colors ||= all.select(&:color).sort_by { |lang| lang.name.downcase }
254 | end
255 |
256 | # Internal: Initialize a new Language
257 | #
258 | # attributes - A hash of attributes
259 | def initialize(attributes = {})
260 | # @name is required
261 | @name = attributes[:name] || raise(ArgumentError, "missing name")
262 |
263 | @fs_name = attributes[:fs_name]
264 |
265 | # Set type
266 | @type = attributes[:type] ? attributes[:type].to_sym : nil
267 | if @type && !TYPES.include?(@type)
268 | raise ArgumentError, "invalid type: #{@type}"
269 | end
270 |
271 | @color = attributes[:color]
272 |
273 | # Set aliases
274 | @aliases = [default_alias] + (attributes[:aliases] || [])
275 |
276 | # Load the TextMate scope name or try to guess one
277 | @tm_scope = attributes[:tm_scope] || begin
278 | context = case @type
279 | when :data, :markup, :prose
280 | 'text'
281 | when :programming, nil
282 | 'source'
283 | end
284 | "#{context}.#{@name.downcase}"
285 | end
286 |
287 | @ace_mode = attributes[:ace_mode]
288 | @codemirror_mode = attributes[:codemirror_mode]
289 | @codemirror_mime_type = attributes[:codemirror_mime_type]
290 | @wrap = attributes[:wrap] || false
291 |
292 | # Set the language_id
293 | @language_id = attributes[:language_id]
294 |
295 | # Set extensions or default to [].
296 | @extensions = attributes[:extensions] || []
297 | @interpreters = attributes[:interpreters] || []
298 | @filenames = attributes[:filenames] || []
299 |
300 | # Set popular, and searchable flags
301 | @popular = attributes.key?(:popular) ? attributes[:popular] : false
302 | @searchable = attributes.key?(:searchable) ? attributes[:searchable] : true
303 |
304 | # If group name is set, save the name so we can lazy load it later
305 | if attributes[:group_name]
306 | @group = nil
307 | @group_name = attributes[:group_name]
308 |
309 | # Otherwise we can set it to self now
310 | else
311 | @group = self
312 | end
313 | end
314 |
315 | # Public: Get proper name
316 | #
317 | # Examples
318 | #
319 | # # => "Ruby"
320 | # # => "Python"
321 | # # => "Perl"
322 | #
323 | # Returns the name String
324 | attr_reader :name
325 |
326 | # Public:
327 | #
328 | attr_reader :fs_name
329 |
330 | # Public: Get type.
331 | #
332 | # Returns a type Symbol or nil.
333 | attr_reader :type
334 |
335 | # Public: Get color.
336 | #
337 | # Returns a hex color String.
338 | attr_reader :color
339 |
340 | # Public: Get aliases
341 | #
342 | # Examples
343 | #
344 | # Language['C++'].aliases
345 | # # => ["cpp"]
346 | #
347 | # Returns an Array of String names
348 | attr_reader :aliases
349 |
350 | # Public: Get language_id (used in GitHub search)
351 | #
352 | # Examples
353 | #
354 | # # => "1"
355 | # # => "2"
356 | # # => "3"
357 | #
358 | # Returns the integer language_id
359 | attr_reader :language_id
360 |
361 | # Public: Get the name of a TextMate-compatible scope
362 | #
363 | # Returns the scope
364 | attr_reader :tm_scope
365 |
366 | # Public: Get Ace mode
367 | #
368 | # Examples
369 | #
370 | # # => "text"
371 | # # => "javascript"
372 | # # => "c_cpp"
373 | #
374 | # Returns a String name or nil
375 | attr_reader :ace_mode
376 |
377 | # Public: Get CodeMirror mode
378 | #
379 | # Maps to a directory in the `mode/` source code.
380 | # https://github.com/codemirror/CodeMirror/tree/master/mode
381 | #
382 | # Examples
383 | #
384 | # # => "nil"
385 | # # => "javascript"
386 | # # => "clike"
387 | #
388 | # Returns a String name or nil
389 | attr_reader :codemirror_mode
390 |
391 | # Public: Get CodeMirror MIME type mode
392 | #
393 | # Examples
394 | #
395 | # # => "nil"
396 | # # => "text/x-javascript"
397 | # # => "text/x-csrc"
398 | #
399 | # Returns a String name or nil
400 | attr_reader :codemirror_mime_type
401 |
402 | # Public: Should language lines be wrapped
403 | #
404 | # Returns true or false
405 | attr_reader :wrap
406 |
407 | # Public: Get extensions
408 | #
409 | # Examples
410 | #
411 | # # => ['.rb', '.rake', ...]
412 | #
413 | # Returns the extensions Array
414 | attr_reader :extensions
415 |
416 | # Public: Get interpreters
417 | #
418 | # Examples
419 | #
420 | # # => ['awk', 'gawk', 'mawk' ...]
421 | #
422 | # Returns the interpreters Array
423 | attr_reader :interpreters
424 |
425 | # Public: Get filenames
426 | #
427 | # Examples
428 | #
429 | # # => ['Rakefile', ...]
430 | #
431 | # Returns the extensions Array
432 | attr_reader :filenames
433 |
434 | # Public: Get URL escaped name.
435 | #
436 | # Examples
437 | #
438 | # "C%23"
439 | # "C%2B%2B"
440 | # "Common%20Lisp"
441 | #
442 | # Returns the escaped String.
443 | def escaped_name
444 | EscapeUtils.escape_url(name).gsub('+', '%20')
445 | end
446 |
447 | # Public: Get default alias name
448 | #
449 | # Returns the alias name String
450 | def default_alias
451 | name.downcase.gsub(/\s/, '-')
452 | end
453 | alias_method :default_alias_name, :default_alias
454 |
455 | # Public: Get Language group
456 | #
457 | # Returns a Language
458 | def group
459 | @group ||= Language.find_by_name(@group_name)
460 | end
461 |
462 | # Public: Is it popular?
463 | #
464 | # Returns true or false
465 | def popular?
466 | @popular
467 | end
468 |
469 | # Public: Is it not popular?
470 | #
471 | # Returns true or false
472 | def unpopular?
473 | !popular?
474 | end
475 |
476 | # Public: Is it searchable?
477 | #
478 | # Unsearchable languages won't by indexed by solr and won't show
479 | # up in the code search dropdown.
480 | #
481 | # Returns true or false
482 | def searchable?
483 | @searchable
484 | end
485 |
486 | # Public: Return name as String representation
487 | def to_s
488 | name
489 | end
490 |
491 | def ==(other)
492 | eql?(other)
493 | end
494 |
495 | def eql?(other)
496 | equal?(other)
497 | end
498 |
499 | def hash
500 | name.hash
501 | end
502 |
503 | def inspect
504 | "#<#{self.class} name=#{name}>"
505 | end
506 | end
507 |
508 | extensions = Samples.cache['extnames']
509 | interpreters = Samples.cache['interpreters']
510 | popular = YAML.load_file(File.expand_path("../popular.yml", __FILE__))
511 |
512 | languages_yml = File.expand_path("../languages.yml", __FILE__)
513 | languages_json = File.expand_path("../languages.json", __FILE__)
514 |
515 | if File.exist?(languages_json) && defined?(Yajl)
516 | languages = Yajl.load(File.read(languages_json))
517 | else
518 | languages = YAML.load_file(languages_yml)
519 | end
520 |
521 | languages.each do |name, options|
522 | options['extensions'] ||= []
523 | options['interpreters'] ||= []
524 | options['filenames'] ||= []
525 |
526 | if extnames = extensions[name]
527 | extnames.each do |extname|
528 | if !options['extensions'].index { |x| x.downcase.end_with? extname.downcase }
529 | warn "#{name} has a sample with extension (#{extname.downcase}) that isn't explicitly defined in languages.yml"
530 | options['extensions'] << extname
531 | end
532 | end
533 | end
534 |
535 | interpreters ||= {}
536 |
537 | if interpreter_names = interpreters[name]
538 | interpreter_names.each do |interpreter|
539 | if !options['interpreters'].include?(interpreter)
540 | options['interpreters'] << interpreter
541 | end
542 | end
543 | end
544 |
545 | Language.create(
546 | :name => name,
547 | :fs_name => options['fs_name'],
548 | :color => options['color'],
549 | :type => options['type'],
550 | :aliases => options['aliases'],
551 | :tm_scope => options['tm_scope'],
552 | :ace_mode => options['ace_mode'],
553 | :codemirror_mode => options['codemirror_mode'],
554 | :codemirror_mime_type => options['codemirror_mime_type'],
555 | :wrap => options['wrap'],
556 | :group_name => options['group'],
557 | :searchable => options.fetch('searchable', true),
558 | :language_id => options['language_id'],
559 | :extensions => Array(options['extensions']),
560 | :interpreters => options['interpreters'].sort,
561 | :filenames => options['filenames'],
562 | :popular => popular.include?(name)
563 | )
564 | end
565 | end
566 |
--------------------------------------------------------------------------------
/lib/linguist/lazy_blob.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/blob_helper'
2 | require 'linguist/language'
3 | require 'rugged'
4 |
5 | module Linguist
6 | class LazyBlob
7 | GIT_ATTR = ['linguist-documentation',
8 | 'linguist-language',
9 | 'linguist-vendored',
10 | 'linguist-generated',
11 | 'linguist-detectable']
12 |
13 | GIT_ATTR_OPTS = { :priority => [:index], :skip_system => true }
14 | GIT_ATTR_FLAGS = Rugged::Repository::Attributes.parse_opts(GIT_ATTR_OPTS)
15 |
16 | include BlobHelper
17 |
18 | MAX_SIZE = 128 * 1024
19 |
20 | attr_reader :repository
21 | attr_reader :oid
22 | attr_reader :path
23 | attr_reader :mode
24 |
25 | alias :name :path
26 |
27 | def initialize(repo, oid, path, mode = nil)
28 | @repository = repo
29 | @oid = oid
30 | @path = path
31 | @mode = mode
32 | @data = nil
33 | end
34 |
35 | def git_attributes
36 | @git_attributes ||= repository.fetch_attributes(
37 | name, GIT_ATTR, GIT_ATTR_FLAGS)
38 | end
39 |
40 | def documentation?
41 | if attr = git_attributes['linguist-documentation']
42 | boolean_attribute(attr)
43 | else
44 | super
45 | end
46 | end
47 |
48 | def generated?
49 | if attr = git_attributes['linguist-generated']
50 | boolean_attribute(attr)
51 | else
52 | super
53 | end
54 | end
55 |
56 | def vendored?
57 | if attr = git_attributes['linguist-vendored']
58 | return boolean_attribute(attr)
59 | else
60 | super
61 | end
62 | end
63 |
64 | def language
65 | return @language if defined?(@language)
66 |
67 | @language = if lang = git_attributes['linguist-language']
68 | Language.find_by_alias(lang)
69 | else
70 | super
71 | end
72 | end
73 |
74 | def detectable?
75 | if attr = git_attributes['linguist-detectable']
76 | return boolean_attribute(attr)
77 | else
78 | nil
79 | end
80 | end
81 |
82 | def data
83 | load_blob!
84 | @data
85 | end
86 |
87 | def size
88 | load_blob!
89 | @size
90 | end
91 |
92 | def symlink?
93 | # We don't create LazyBlobs for symlinks.
94 | false
95 | end
96 |
97 | def cleanup!
98 | @data.clear if @data
99 | end
100 |
101 | protected
102 |
103 | # Returns true if the attribute is present and not the string "false".
104 | def boolean_attribute(attribute)
105 | attribute != "false"
106 | end
107 |
108 | def load_blob!
109 | @data, @size = Rugged::Blob.to_buffer(repository, oid, MAX_SIZE) if @data.nil?
110 | end
111 | end
112 | end
113 |
--------------------------------------------------------------------------------
/lib/linguist/md5.rb:
--------------------------------------------------------------------------------
1 | require 'digest/md5'
2 |
3 | module Linguist
4 | module MD5
5 | # Public: Create deep nested digest of value object.
6 | #
7 | # Useful for object comparison.
8 | #
9 | # obj - Object to digest.
10 | #
11 | # Returns String hex digest
12 | def self.hexdigest(obj)
13 | digest = Digest::MD5.new
14 |
15 | case obj
16 | when String, Symbol, Integer
17 | digest.update "#{obj.class}"
18 | digest.update "#{obj}"
19 | when TrueClass, FalseClass, NilClass
20 | digest.update "#{obj.class}"
21 | when Array
22 | digest.update "#{obj.class}"
23 | for e in obj
24 | digest.update(hexdigest(e))
25 | end
26 | when Hash
27 | digest.update "#{obj.class}"
28 | for e in obj.map { |(k, v)| hexdigest([k, v]) }.sort
29 | digest.update(e)
30 | end
31 | else
32 | raise TypeError, "can't convert #{obj.inspect} into String"
33 | end
34 |
35 | digest.hexdigest
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/linguist/popular.yml:
--------------------------------------------------------------------------------
1 | # Popular languages appear at the top of language dropdowns
2 | #
3 | # This file should only be edited by GitHub staff
4 |
5 | - ActionScript
6 | - C
7 | - C#
8 | - C++
9 | - CSS
10 | - Clojure
11 | - CoffeeScript
12 | - Go
13 | - HTML
14 | - Haskell
15 | - Java
16 | - JavaScript
17 | - Lua
18 | - MATLAB
19 | - Objective-C
20 | - PHP
21 | - Perl
22 | - Python
23 | - R
24 | - Ruby
25 | - Scala
26 | - Shell
27 | - Swift
28 | - TeX
29 | - Vim script
30 |
--------------------------------------------------------------------------------
/lib/linguist/repository.rb:
--------------------------------------------------------------------------------
1 | require 'linguist/lazy_blob'
2 | require 'rugged'
3 |
4 | module Linguist
5 | # A Repository is an abstraction of a Grit::Repo or a basic file
6 | # system tree. It holds a list of paths pointing to Blobish objects.
7 | #
8 | # Its primary purpose is for gathering language statistics across
9 | # the entire project.
10 | class Repository
11 | attr_reader :repository
12 |
13 | # Public: Create a new Repository based on the stats of
14 | # an existing one
15 | def self.incremental(repo, commit_oid, old_commit_oid, old_stats)
16 | repo = self.new(repo, commit_oid)
17 | repo.load_existing_stats(old_commit_oid, old_stats)
18 | repo
19 | end
20 |
21 | # Public: Initialize a new Repository to be analyzed for language
22 | # data
23 | #
24 | # repo - a Rugged::Repository object
25 | # commit_oid - the sha1 of the commit that will be analyzed;
26 | # this is usually the master branch
27 | #
28 | # Returns a Repository
29 | def initialize(repo, commit_oid)
30 | @repository = repo
31 | @commit_oid = commit_oid
32 |
33 | @old_commit_oid = nil
34 | @old_stats = nil
35 |
36 | raise TypeError, 'commit_oid must be a commit SHA1' unless commit_oid.is_a?(String)
37 | end
38 |
39 | # Public: Load the results of a previous analysis on this repository
40 | # to speed up the new scan.
41 | #
42 | # The new analysis will be performed incrementally as to only take
43 | # into account the file changes since the last time the repository
44 | # was scanned
45 | #
46 | # old_commit_oid - the sha1 of the commit that was previously analyzed
47 | # old_stats - the result of the previous analysis, obtained by calling
48 | # Repository#cache on the old repository
49 | #
50 | # Returns nothing
51 | def load_existing_stats(old_commit_oid, old_stats)
52 | @old_commit_oid = old_commit_oid
53 | @old_stats = old_stats
54 | nil
55 | end
56 |
57 | # Public: Returns a breakdown of language stats.
58 | #
59 | # Examples
60 | #
61 | # # => { 'Ruby' => 46319,
62 | # 'JavaScript' => 258 }
63 | #
64 | # Returns a Hash of language names and Integer size values.
65 | def languages
66 | @sizes ||= begin
67 | sizes = Hash.new { 0 }
68 | cache.each do |_, (language, size)|
69 | sizes[language] += size
70 | end
71 | sizes
72 | end
73 | end
74 |
75 | # Public: Get primary Language of repository.
76 | #
77 | # Returns a language name
78 | def language
79 | @language ||= begin
80 | primary = languages.max_by { |(_, size)| size }
81 | primary && primary[0]
82 | end
83 | end
84 |
85 | # Public: Get the total size of the repository.
86 | #
87 | # Returns a byte size Integer
88 | def size
89 | @size ||= languages.inject(0) { |s,(_,v)| s + v }
90 | end
91 |
92 | # Public: Return the language breakdown of this repository by file
93 | #
94 | # Returns a map of language names => [filenames...]
95 | def breakdown_by_file
96 | @file_breakdown ||= begin
97 | breakdown = Hash.new { |h,k| h[k] = Array.new }
98 | cache.each do |filename, (language, _)|
99 | breakdown[language] << filename
100 | end
101 | breakdown
102 | end
103 | end
104 |
105 | # Public: Return the cached results of the analysis
106 | #
107 | # This is a per-file breakdown that can be passed to other instances
108 | # of Linguist::Repository to perform incremental scans
109 | #
110 | # Returns a map of filename => [language, size]
111 | def cache
112 | @cache ||= begin
113 | if @old_commit_oid == @commit_oid
114 | @old_stats
115 | else
116 | compute_stats(@old_commit_oid, @old_stats)
117 | end
118 | end
119 | end
120 |
121 | def read_index
122 | attr_index = Rugged::Index.new
123 | attr_index.read_tree(current_tree)
124 | repository.index = attr_index
125 | end
126 |
127 | def current_tree
128 | @tree ||= Rugged::Commit.lookup(repository, @commit_oid).tree
129 | end
130 |
131 | protected
132 | MAX_TREE_SIZE = 100_000
133 |
134 | def compute_stats(old_commit_oid, cache = nil)
135 | return {} if current_tree.count_recursive(MAX_TREE_SIZE) >= MAX_TREE_SIZE
136 |
137 | old_tree = old_commit_oid && Rugged::Commit.lookup(repository, old_commit_oid).tree
138 | read_index
139 | diff = Rugged::Tree.diff(repository, old_tree, current_tree)
140 |
141 | # Clear file map and fetch full diff if any .gitattributes files are changed
142 | if cache && diff.each_delta.any? { |delta| File.basename(delta.new_file[:path]) == ".gitattributes" }
143 | diff = Rugged::Tree.diff(repository, old_tree = nil, current_tree)
144 | file_map = {}
145 | else
146 | file_map = cache ? cache.dup : {}
147 | end
148 |
149 | diff.each_delta do |delta|
150 | old = delta.old_file[:path]
151 | new = delta.new_file[:path]
152 |
153 | file_map.delete(old)
154 | next if delta.binary
155 |
156 | if [:added, :modified].include? delta.status
157 | # Skip submodules and symlinks
158 | mode = delta.new_file[:mode]
159 | mode_format = (mode & 0170000)
160 | next if mode_format == 0120000 || mode_format == 040000 || mode_format == 0160000
161 |
162 | blob = Linguist::LazyBlob.new(repository, delta.new_file[:oid], new, mode.to_s(8))
163 |
164 | if blob.include_in_language_stats?
165 | file_map[new] = [blob.language.group.name, blob.size]
166 | end
167 |
168 | blob.cleanup!
169 | end
170 | end
171 |
172 | file_map
173 | end
174 | end
175 | end
176 |
--------------------------------------------------------------------------------
/lib/linguist/samples.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require 'yajl'
3 | rescue LoadError
4 | require 'yaml'
5 | end
6 |
7 | require 'linguist/md5'
8 | require 'linguist/classifier'
9 | require 'linguist/shebang'
10 |
11 | module Linguist
12 | # Model for accessing classifier training data.
13 | module Samples
14 | # Path to samples root directory
15 | ROOT = File.expand_path("../../../samples", __FILE__)
16 |
17 | # Path for serialized samples db
18 | PATH = File.expand_path('../samples.json', __FILE__)
19 |
20 | # Hash of serialized samples object
21 | def self.cache
22 | @cache ||= begin
23 | serializer = defined?(Yajl) ? Yajl : YAML
24 | serializer.load(File.read(PATH, encoding: 'utf-8'))
25 | end
26 | end
27 |
28 | # Public: Iterate over each sample.
29 | #
30 | # &block - Yields Sample to block
31 | #
32 | # Returns nothing.
33 | def self.each(&block)
34 | Dir.entries(ROOT).sort!.each do |category|
35 | next if category == '.' || category == '..'
36 |
37 | dirname = File.join(ROOT, category)
38 | Dir.entries(dirname).each do |filename|
39 | next if filename == '.' || filename == '..'
40 |
41 | if filename == 'filenames'
42 | Dir.entries(File.join(dirname, filename)).each do |subfilename|
43 | next if subfilename == '.' || subfilename == '..'
44 |
45 | yield({
46 | :path => File.join(dirname, filename, subfilename),
47 | :language => category,
48 | :filename => subfilename
49 | })
50 | end
51 | else
52 | path = File.join(dirname, filename)
53 | extname = File.extname(filename)
54 |
55 | yield({
56 | :path => path,
57 | :language => category,
58 | :interpreter => Shebang.interpreter(File.read(path)),
59 | :extname => extname.empty? ? nil : extname
60 | })
61 | end
62 | end
63 | end
64 |
65 | nil
66 | end
67 |
68 | # Public: Build Classifier from all samples.
69 | #
70 | # Returns trained Classifier.
71 | def self.data
72 | db = {}
73 | db['extnames'] = {}
74 | db['interpreters'] = {}
75 | db['filenames'] = {}
76 |
77 | each do |sample|
78 | language_name = sample[:language]
79 |
80 | if sample[:extname]
81 | db['extnames'][language_name] ||= []
82 | if !db['extnames'][language_name].include?(sample[:extname])
83 | db['extnames'][language_name] << sample[:extname]
84 | db['extnames'][language_name].sort!
85 | end
86 | end
87 |
88 | if sample[:interpreter]
89 | db['interpreters'][language_name] ||= []
90 | if !db['interpreters'][language_name].include?(sample[:interpreter])
91 | db['interpreters'][language_name] << sample[:interpreter]
92 | db['interpreters'][language_name].sort!
93 | end
94 | end
95 |
96 | if sample[:filename]
97 | db['filenames'][language_name] ||= []
98 | db['filenames'][language_name] << sample[:filename]
99 | db['filenames'][language_name].sort!
100 | end
101 |
102 | data = File.read(sample[:path])
103 | Classifier.train!(db, language_name, data)
104 | end
105 |
106 | db['md5'] = Linguist::MD5.hexdigest(db)
107 |
108 | db
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/lib/linguist/shebang.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | class Shebang
3 | # Public: Use shebang to detect language of the blob.
4 | #
5 | # blob - An object that quacks like a blob.
6 | # candidates - A list of candidate languages.
7 | #
8 | # Examples
9 | #
10 | # Shebang.call(FileBlob.new("path/to/file"))
11 | #
12 | # Returns an array of languages from the candidate list for which the
13 | # blob's shebang is valid. Returns an empty list if there is no shebang.
14 | # If the candidate list is empty, any language is a valid candidate.
15 | def self.call(blob, candidates)
16 | return [] if blob.symlink?
17 |
18 | languages = Language.find_by_interpreter interpreter(blob.data)
19 | candidates.any? ? candidates & languages : languages
20 | end
21 |
22 | # Public: Get the interpreter from the shebang
23 | #
24 | # Returns a String or nil
25 | def self.interpreter(data)
26 | shebang = data.lines.first
27 |
28 | # First line must start with #!
29 | return unless shebang && shebang.start_with?("#!")
30 |
31 | s = StringScanner.new(shebang)
32 |
33 | # There was nothing after the #!
34 | return unless path = s.scan(/^#!\s*\S+/)
35 |
36 | # Keep going
37 | script = path.split('/').last
38 |
39 | # if /usr/bin/env type shebang then walk the string
40 | if script == 'env'
41 | s.scan(/\s+/)
42 | s.scan(/.*=[^\s]+\s+/) # skip over variable arguments e.g. foo=bar
43 | script = s.scan(/\S+/)
44 | end
45 |
46 | # Interpreter was /usr/bin/env with no arguments
47 | return unless script
48 |
49 | # "python2.6" -> "python2"
50 | script.sub!(/(\.\d+)$/, '')
51 |
52 | # #! perl -> perl
53 | script.sub!(/^#!\s*/, '')
54 |
55 | # Check for multiline shebang hacks that call `exec`
56 | if script == 'sh' &&
57 | data.lines.first(5).any? { |l| l.match(/exec (\w+).+\$0.+\$@/) }
58 | script = $1
59 | end
60 |
61 | File.basename(script)
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lib/linguist/strategy/extension.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | module Strategy
3 | # Detects language based on extension
4 | class Extension
5 | # Public: Use the file extension to detect the blob's language.
6 | #
7 | # blob - An object that quacks like a blob.
8 | # candidates - A list of candidate languages.
9 | #
10 | # Examples
11 | #
12 | # Extension.call(FileBlob.new("path/to/file"))
13 | #
14 | # Returns an array of languages associated with a blob's file extension.
15 | # Selected languages must be in the candidate list, except if it's empty,
16 | # in which case any language is a valid candidate.
17 | def self.call(blob, candidates)
18 | languages = Language.find_by_extension(blob.name.to_s)
19 | candidates.any? ? candidates & languages : languages
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/linguist/strategy/filename.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | module Strategy
3 | # Detects language based on filename
4 | class Filename
5 | # Public: Use the filename to detect the blob's language.
6 | #
7 | # blob - An object that quacks like a blob.
8 | # candidates - A list of candidate languages.
9 | #
10 | # Examples
11 | #
12 | # Filename.call(FileBlob.new("path/to/file"))
13 | #
14 | # Returns an array of languages with a associated blob's filename.
15 | # Selected languages must be in the candidate list, except if it's empty,
16 | # in which case any language is a valid candidate.
17 | def self.call(blob, candidates)
18 | name = blob.name.to_s
19 | languages = Language.find_by_filename(name)
20 | candidates.any? ? candidates & languages : languages
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/linguist/strategy/modeline.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | module Strategy
3 | class Modeline
4 | EMACS_MODELINE = /
5 | -\*-
6 | (?:
7 | # Short form: `-*- ruby -*-`
8 | \s* (?= [^:;\s]+ \s* -\*-)
9 | |
10 | # Longer form: `-*- foo:bar; mode: ruby; -*-`
11 | (?:
12 | .*? # Preceding variables: `-*- foo:bar bar:baz;`
13 | [;\s] # Which are delimited by spaces or semicolons
14 | |
15 | (?<=-\*-) # Not preceded by anything: `-*-mode:ruby-*-`
16 | )
17 | mode # Major mode indicator
18 | \s*:\s* # Allow whitespace around colon: `mode : ruby`
19 | )
20 | ([^:;\s]+) # Name of mode
21 |
22 | # Ensure the mode is terminated correctly
23 | (?=
24 | # Followed by semicolon or whitespace
25 | [\s;]
26 | |
27 | # Touching the ending sequence: `ruby-*-`
28 | (?]?\d+|m)? # Version-specific modeline
42 | |
43 | [\t\x20] # `ex:` requires whitespace, because "ex:" might be short for "example:"
44 | ex
45 | )
46 |
47 | # If the option-list begins with `set ` or `se `, it indicates an alternative
48 | # modeline syntax partly-compatible with older versions of Vi. Here, the colon
49 | # serves as a terminator for an option sequence, delimited by whitespace.
50 | (?=
51 | # So we have to ensure the modeline ends with a colon
52 | : (?=[ \t]* set? [ \t] [^\n:]+ :) |
53 |
54 | # Otherwise, it isn't valid syntax and should be ignored
55 | : (?![ \t]* set? [ \t])
56 | )
57 |
58 | # Possible (unrelated) `option=value` pairs to skip past
59 | (?:
60 | # Option separator. Vim uses whitespace or colons to separate options (except if
61 | # the alternate "vim: set " form is used, where only whitespace is used)
62 | (?:
63 | [ \t]
64 | |
65 | [ \t]* : [ \t]* # Note that whitespace around colons is accepted too:
66 | ) # vim: noai : ft=ruby:noexpandtab
67 |
68 | # Option's name. All recognised Vim options have an alphanumeric form.
69 | \w*
70 |
71 | # Possible value. Not every option takes an argument.
72 | (?:
73 | # Whitespace between name and value is allowed: `vim: ft =ruby`
74 | [ \t]*=
75 |
76 | # Option's value. Might be blank; `vim: ft= ` says "use no filetype".
77 | (?:
78 | [^\\[ \t]] # Beware of escaped characters: titlestring=\ ft=ruby
79 | | # will be read by Vim as { titlestring: " ft=ruby" }.
80 | \\.
81 | )*
82 | )?
83 | )*
84 |
85 | # The actual filetype declaration
86 | [[ \t]:] (?:filetype|ft|syntax) [ \t]*=
87 |
88 | # Language's name
89 | (\w+)
90 |
91 | # Ensure it's followed by a legal separator
92 | (?=[ \t]|:|$)
93 | /xi
94 |
95 | MODELINES = [EMACS_MODELINE, VIM_MODELINE]
96 |
97 | # Scope of the search for modelines
98 | # Number of lines to check at the beginning and at the end of the file
99 | SEARCH_SCOPE = 5
100 |
101 | # Public: Detects language based on Vim and Emacs modelines
102 | #
103 | # blob - An object that quacks like a blob.
104 | #
105 | # Examples
106 | #
107 | # Modeline.call(FileBlob.new("path/to/file"))
108 | #
109 | # Returns an Array with one Language if the blob has a Vim or Emacs modeline
110 | # that matches a Language name or alias. Returns an empty array if no match.
111 | def self.call(blob, _ = nil)
112 | return [] if blob.symlink?
113 |
114 | header = blob.first_lines(SEARCH_SCOPE).join("\n")
115 | footer = blob.last_lines(SEARCH_SCOPE).join("\n")
116 | Array(Language.find_by_alias(modeline(header + footer)))
117 | end
118 |
119 | # Public: Get the modeline from the first n-lines of the file
120 | #
121 | # Returns a String or nil
122 | def self.modeline(data)
123 | match = MODELINES.map { |regex| data.match(regex) }.reject(&:nil?).first
124 | match[1] if match
125 | end
126 | end
127 | end
128 | end
129 |
--------------------------------------------------------------------------------
/lib/linguist/strategy/xml.rb:
--------------------------------------------------------------------------------
1 | module Linguist
2 | module Strategy
3 | # Detects XML files based on root tag.
4 | class XML
5 | # Scope of the search for the root tag
6 | # Number of lines to check at the beginning of the file
7 | SEARCH_SCOPE = 2
8 |
9 | # Public: Use the root tag to detect the XML blobs, only if no other
10 | # candidates were previously identified.
11 | #
12 | # blob - An object that quacks like a blob.
13 | # candidates - A list of candidate languages.
14 | #
15 | # Examples
16 | #
17 | # XML.call(FileBlob.new("path/to/file"))
18 | #
19 | # Returns the list of candidates if it wasn't empty, an array with the
20 | # XML language as sole item if the root tag is detected, and an empty
21 | # Array otherwise.
22 | def self.call(blob, candidates = [])
23 | return candidates if candidates.any?
24 |
25 | header = blob.first_lines(SEARCH_SCOPE).join("\n")
26 | /
10 | {% raw %} {{employee.id}} {% endraw %}
11 | {% raw %} {{employee.name}} {% endraw %}
12 |
13 |
14 |
15 | ```
16 |
17 | **my-first-component.component.ts**
18 |
19 | ```javascript
20 |
21 | import { Component, OnInit } from '@angular/core';
22 |
23 | @Component({
24 | selector: 'app-my-first-component',
25 | templateUrl: './my-first-component.component.html',
26 | styleUrls: ['./my-first-component.component.css']
27 | })
28 |
29 | export class MyFirstComponentComponent implements OnInit {
30 |
31 | constructor() { }
32 |
33 | ngOnInit() {
34 | }
35 |
36 | employee1=[{
37 | id:1,
38 | name:'Akshay Patel'
39 | },
40 | {
41 | id:2,
42 | name:'Panth Patel'
43 | }]
44 | }
45 |
46 | ```
47 |
--------------------------------------------------------------------------------
/ngIf.md:
--------------------------------------------------------------------------------
1 | ## *ngIf
2 |
3 | If employee.id is not there, then don’t render id div.
4 |
5 | **my-first-component.component.html**
6 |
7 | ```html
8 |
9 |
10 |
11 | {% raw %} {{employee.id}} {% endraw %}
12 |
13 | {% raw %} {{employee.name}} {% endraw %}
14 |
15 |
16 |
17 | ```
18 |
19 | **my-first-component.component.ts**
20 |
21 | ```javascript
22 |
23 | import { Component, OnInit } from '@angular/core';
24 |
25 | @Component({
26 | selector: 'app-my-first-component',
27 | templateUrl: './my-first-component.component.html',
28 | styleUrls: ['./my-first-component.component.css']
29 | })
30 |
31 | export class MyFirstComponentComponent implements OnInit {
32 |
33 | constructor() { }
34 |
35 | ngOnInit() {
36 | }
37 |
38 | employee1=[{
39 | name:'Akshay Patel'
40 | },
41 | {
42 | id:2,
43 | name:'Panth Patel'
44 | }]
45 | }
46 |
47 | ```
48 |
--------------------------------------------------------------------------------
/ngSwitch.md:
--------------------------------------------------------------------------------
1 | ## *ngSwitch
2 |
3 | **my-first-component.component.html**
4 |
5 | ```html
6 |
7 |
8 |
9 | {% raw %} {{employee.id}} {% endraw %}
10 |
11 | Odd Number
12 |
13 |
14 | Even Number
15 |
16 |
17 | Not Defined
18 |
19 |
20 | {% raw %} {{employee.name}} {% endraw %}
21 |
22 |
23 |
24 | ```
25 |
26 | **my-first-component.component.ts**
27 |
28 | ```javascript
29 |
30 | import { Component, OnInit } from '@angular/core';
31 |
32 | @Component({
33 | selector: 'app-my-first-component',
34 | templateUrl: './my-first-component.component.html',
35 | styleUrls: ['./my-first-component.component.css']
36 | })
37 |
38 | export class MyFirstComponentComponent implements OnInit {
39 |
40 | constructor() { }
41 |
42 | ngOnInit() {
43 | }
44 |
45 | employee1=[{
46 | id:1,
47 | name:'Akshay Patel'
48 | },
49 | {
50 | id:2,
51 | name:'Panth Patel'
52 | }]
53 | }
54 |
55 | ```
56 |
--------------------------------------------------------------------------------