├── 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 |

{{ site.title | default: site.github.repository_name }}

20 |

{{ site.description | default: site.github.project_tagline }}

21 | 22 | 29 | 30 | {% if site.github.is_project_page %} 31 |

32 | {% include nav.html %} 33 |

34 | {% endif %} 35 | 36 | {% if site.github.is_user_page %} 37 | 40 | {% endif %} 41 |
42 | 43 |
44 | {{ content }} 45 |
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 | --------------------------------------------------------------------------------