text:
import {TestBed, ComponentFixture, inject, async, fakeAsync, tick} from '@angular/core/testing';
import {createGenericTestComponent} from '../test/common';
import {Key} from '../util/key';

import {Component, DebugElement} from '@angular/core';
import {FormsModule, ReactiveFormsModule, FormGroup, FormControl, Validators} from '@angular/forms';

import {NgbRatingModule} from './rating.module';
import {NgbRating} from './rating';
import {NgbRatingConfig} from './rating-config';
import {By} from '@angular/platform-browser';

const createTestComponent = (html: string) =>
    createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;

function createKeyDownEvent(key: number) {
  const event = {which: key, preventDefault: () => {}};
  spyOn(event, 'preventDefault');
  return event;
}

function getAriaState(compiled) {
  const stars = getStars(compiled, '.sr-only');
  return stars.map(star => star.textContent === '(*)');
}

function getStar(compiled, num: number) {
  return getStars(compiled)[num - 1];
}

function getStars(element, selector = 'span:not(.sr-only)') {
  return <HTMLElement[]>Array.from(element.querySelectorAll(selector));
}

function getDbgStar(element, num: number) {
  return element.queryAll(By.css('span:not(.sr-only)'))[num - 1];
}

function getState(element: DebugElement | HTMLElement) {
  const stars = getStars(element instanceof DebugElement ? element.nativeElement : element);
  return stars.map(star => star.textContent !.trim() === String.fromCharCode(9733));
}

function getStateText(compiled) {
  const stars = getStars(compiled);
  return stars.map(star => star.textContent !.trim());
}

describe('ngb-rating', () => {
  beforeEach(() => {
    TestBed.configureTestingModule(
        {declarations: [TestComponent], imports: [NgbRatingModule, FormsModule, ReactiveFormsModule]});
  });

  it('should initialize inputs with default values', () => {
    const defaultConfig = new NgbRatingConfig();
    const rating = new NgbRating(new NgbRatingConfig(), <any>null);
    expect(rating.max).toBe(defaultConfig.max);
    expect(rating.readonly).toBe(defaultConfig.readonly);
  });

  it('should show as many stars as the configured max by default', () => {
    const fixture = TestBed.createComponent(NgbRating);
    fixture.detectChanges();

    const compiled = fixture.nativeElement;

    const stars = getStars(compiled);
    expect(stars.length).toBe(new NgbRatingConfig().max);
  });

  it('should change the num of stars with `max`', () => {
    const fixture = createTestComponent('<ngb-rating max="3"></ngb-rating>');

    const compiled = fixture.nativeElement;
    const stars = getStars(compiled);
    expect(stars.length).toBe(3);
  });

  it('initializes the default star icons as selected', () => {
    const fixture = createTestComponent('<ngb-rating rate="3" max="5"></ngb-rating>');

    const compiled = fixture.nativeElement;
    expect(getState(compiled)).toEqual([true, true, true, false, false]);
  });

  it('sets stars within 0..max limits', () => {
    const fixture = createTestComponent('<ngb-rating [rate]="rate" max="5"></ngb-rating>');

    const compiled = fixture.nativeElement;
    expect(getState(compiled)).toEqual([true, true, true, false, false]);

    fixture.componentInstance.rate = 0;
    fixture.detectChanges();
    expect(getState(compiled)).toEqual([false, false, false, false, false]);

    fixture.componentInstance.rate = -5;
    fixture.detectChanges();
    expect(getState(compiled)).toEqual([false, false, false, false, false]);

    fixture.componentInstance.rate = 20;
    fixture.detectChanges();
    expect(getState(compiled)).toEqual([true, true, true, true, true]);
  });

  it('should now fire change event initially', fakeAsync(() => {
       const fixture = createTestComponent('<ngb-rating [rate]="3" (rateChange)="changed = true"></ngb-rating>');
       tick();
       expect(fixture.componentInstance.changed).toBeFalsy();
     }));

  it('handles correctly the click event', fakeAsync(() => {
       const fixture = createTestComponent('<ngb-rating [(rate)]="rate" max="5"></ngb-rating>');
       const el = fixture.debugElement;
       const rating = el.query(By.directive(NgbRating)).children[0];

       // 3/5
       expect(getState(el)).toEqual([true, true, true, false, false]);

       // enter 2 -> 2/5, rate = 3
       getDbgStar(el, 2).triggerEventHandler('mouseenter', {});
       fixture.detectChanges();
       expect(getState(el)).toEqual([true, true, false, false, false]);
       expect(fixture.componentInstance.rate).toBe(3);

       // click 2 -> 2/5, rate = 2
       getStar(el.nativeElement, 2).click();
       fixture.detectChanges();
       tick();
       expect(getState(el)).toEqual([true, true, false, false, false]);
       expect(fixture.componentInstance.rate).toBe(2);

       // leave 2 -> 2/5, rate = 2
       rating.triggerEventHandler('mouseleave', {});
       fixture.detectChanges();
       expect(getState(el)).toEqual([true, true, false, false, false]);
       expect(fixture.componentInstance.rate).toBe(2);
     }));

  it('ignores the click event on a readonly rating', () => {
    const fixture = createTestComponent('<ngb-rating [(rate)]="rate" max="5" [readonly]="true"></ngb-rating>');
    const el = fixture.debugElement;
    const rating = el.query(By.directive(NgbRating)).children[0];

    // 3/5
    expect(getState(el)).toEqual([true, true, true, false, false]);

    // enter 2 -> 3/5
    getDbgStar(el, 2).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // click 2 -> 2/5
    getStar(el.nativeElement, 2).click();
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // leave 2 -> 3/5
    rating.triggerEventHandler('mouseleave', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);
  });

  it('should not reset rating to 0 by default', fakeAsync(() => {
       const fixture = createTestComponent('<ngb-rating [(rate)]="rate" max="5"></ngb-rating>');
       const el = fixture.debugElement;

       // 3/5 initially
       expect(getState(el)).toEqual([true, true, true, false, false]);
       expect(fixture.componentInstance.rate).toBe(3);

       // click 3 -> 3/5
       getStar(el.nativeElement, 3).click();
       fixture.detectChanges();
       expect(getState(el)).toEqual([true, true, true, false, false]);
       expect(fixture.componentInstance.rate).toBe(3);
     }));

  it('should set `resettable` rating to 0', fakeAsync(() => {
       const fixture = createTestComponent('<ngb-rating [(rate)]="rate" max="5" [resettable]="true"></ngb-rating>');
       const el = fixture.debugElement;

       // 3/5 initially
       expect(getState(el)).toEqual([true, true, true, false, false]);
       expect(fixture.componentInstance.rate).toBe(3);

       // click 3 -> 0/5
       getStar(el.nativeElement, 3).click();
       tick();
       fixture.detectChanges();
       expect(getState(el)).toEqual([false, false, false, false, false]);
       expect(fixture.componentInstance.rate).toBe(0);

       // click 2 -> 2/5
       getStar(el.nativeElement, 2).click();
       tick();
       fixture.detectChanges();
       expect(getState(el)).toEqual([true, true, false, false, false]);
       expect(fixture.componentInstance.rate).toBe(2);
     }));

  it('handles correctly the mouse enter/leave', () => {
    const fixture = createTestComponent('<ngb-rating [(rate)]="rate" max="5"></ngb-rating>');
    const el = fixture.debugElement;
    const rating = el.query(By.directive(NgbRating));

    // 3/5
    expect(getState(el)).toEqual([true, true, true, false, false]);

    // enter 1 -> 1/5, rate = 3
    getDbgStar(el, 1).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, false, false, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // leave -> 3/5, rate = 3
    rating.triggerEventHandler('mouseleave', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // enter 5 -> 5/5, rate = 3
    getDbgStar(el, 5).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, true, true]);
    expect(fixture.componentInstance.rate).toBe(3);

    // enter 4 -> 4/5, rate = 3
    getDbgStar(el, 4).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, true, false]);
    expect(fixture.componentInstance.rate).toBe(3);
  });

  it('handles correctly the mouse enter/leave on readonly rating', () => {
    const fixture = createTestComponent('<ngb-rating [(rate)]="rate" max="5" [readonly]="true"></ngb-rating>');
    const el = fixture.debugElement;
    const rating = el.query(By.directive(NgbRating)).children[0];

    // 3/5
    expect(getState(el)).toEqual([true, true, true, false, false]);

    // enter 1 -> 3/5, rate = 3
    getDbgStar(el, 1).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // leave -> 3/5, rate = 3
    rating.triggerEventHandler('mouseleave', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // enter 5 -> 3/5, rate = 3
    getDbgStar(el, 5).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);

    // enter 4 -> 3/5, rate = 3
    getDbgStar(el, 4).triggerEventHandler('mouseenter', {});
    fixture.detectChanges();
    expect(getState(el)).toEqual([true, true, true, false, false]);
    expect(fixture.componentInstance.rate).toBe(3);
  });

  it('should set pointer cursor on stars when not readonly', () => {
    const fixture = TestBed.createComponent(NgbRating);
    fixture.detectChanges();

    const compiled = fixture.nativeElement;

    expect(window.getComputedStyle(getStar(compiled, 1)).getPropertyValue('cursor')).toBe('pointer');
  });

  it('should set default cursor on stars when readonly', () => {
    const fixture = createTestComponent('<ngb-rating [readonly]="true"></ngb-rating>');

    const compiled = fixture.nativeElement;

    expect(window.getComputedStyle(getStar(compiled, 1)).getPropertyValue('cursor')).toBe('default');
  });

  it('should allow custom star template', () => {
    const fixture = createTestComponent(`
      <ng-template #t let-fill="fill">{{ fill === 100 ? 'x' : 'o' }}</ng-template>
      <ngb-rating [starTemplate]="t" rate="2" max="4"></ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['x', 'x', 'o', 'o']);
  });

  it('should allow custom template as a child element', () => {
    const fixture = createTestComponent(`
      <ngb-rating rate="2" max="4">
        <ng-template let-fill="fill">{{ fill === 100 ? 'x' : 'o' }}</ng-template>
      </ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['x', 'x', 'o', 'o']);
  });

  it('should prefer explicitly set custom template to a child one', () => {
    const fixture = createTestComponent(`
      <ng-template #t let-fill="fill">{{ fill === 100 ? 'a' : 'b' }}</ng-template>
      <ngb-rating [starTemplate]="t" rate="2" max="4">
        <ng-template let-fill="fill">{{ fill === 100 ? 'c' : 'd' }}</ng-template>
      </ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['a', 'a', 'b', 'b']);
  });

  it('should calculate fill percentage correctly', () => {
    const fixture = createTestComponent(`
      <ng-template #t let-fill="fill">{{fill}}</ng-template>
      <ngb-rating [starTemplate]="t" [rate]="rate" max="4"></ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['100', '100', '100', '0']);

    fixture.componentInstance.rate = 0;
    fixture.detectChanges();
    expect(getStateText(compiled)).toEqual(['0', '0', '0', '0']);

    fixture.componentInstance.rate = 2.2;
    fixture.detectChanges();
    expect(getStateText(compiled)).toEqual(['100', '100', '20', '0']);

    fixture.componentInstance.rate = 2.25;
    fixture.detectChanges();
    expect(getStateText(compiled)).toEqual(['100', '100', '25', '0']);

    fixture.componentInstance.rate = 2.2548;
    fixture.detectChanges();
    expect(getStateText(compiled)).toEqual(['100', '100', '25', '0']);

    fixture.componentInstance.rate = 7;
    fixture.detectChanges();
    expect(getStateText(compiled)).toEqual(['100', '100', '100', '100']);
  });

  it('should allow custom star template based on index', () => {
    const fixture = createTestComponent(`
      <ng-template #t let-index="index">{{ index === 1 ? 'x' : 'o' }}</ng-template>
      <ngb-rating [starTemplate]="t" rate="2" max="4"></ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['o', 'x', 'o', 'o']);
  });

  it('should allow custom template based on index as a child element', () => {
    const fixture = createTestComponent(`
      <ngb-rating rate="2" max="4">
        <ng-template let-index="index">{{ index === 1 ? 'x' : 'o' }}</ng-template>
      </ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['o', 'x', 'o', 'o']);
  });

  it('should prefer explicitly set custom template based on index to a child one', () => {
    const fixture = createTestComponent(`
      <ng-template #t let-index="index">{{ index === 1 ? 'a' : 'b' }}</ng-template>
      <ngb-rating [starTemplate]="t" rate="2" max="4">
        <ng-template let-index="index">{{ index === 1 ? 'c' : 'd' }}</ng-template>
      </ngb-rating>`);

    const compiled = fixture.nativeElement;
    expect(getStateText(compiled)).toEqual(['b', 'a', 'b', 'b']);
  });

  describe('aria support', () => {
    it('contains aria-valuemax with the number of stars', () => {
      const fixture = createTestComponent('<ngb-rating [max]="max"></ngb-rating>');

      const rating = fixture.debugElement.query(By.directive(NgbRating));

      expect(rating.attributes['aria-valuemax']).toBe('10');
    });

    it('contains aria-valuemin', () => {
      const fixture = createTestComponent('<ngb-rating [max]="max"></ngb-rating>');

      const rating = fixture.debugElement.query(By.directive(NgbRating));

      expect(rating.attributes['aria-valuemin']).toBe('0');
    });

    it('contains a hidden span for each star for screenreaders', () => {
      const fixture = createTestComponent('<ngb-rating max="5"></ngb-rating>');

      const compiled = fixture.nativeElement;
      const hiddenStars = getStars(compiled, '.sr-only');

      expect(hiddenStars.length).toBe(5);
    });

    it('initializes populates the current rate for screenreaders', () => {
      const fixture = createTestComponent('<ngb-rating rate="3" max="5"></ngb-rating>');

      const compiled = fixture.nativeElement;
      expect(getAriaState(compiled)).toEqual([true, true, true, false, false]);
    });

    it('contains aria-valuenow with the current rate', () => {
      const fixture = createTestComponent('<ngb-rating [max]="max" rate="3"></ngb-rating>');

      const rating = fixture.debugElement.query(By.directive(NgbRating));

      expect(rating.attributes['aria-valuenow']).toBe('3');
    });

    it('updates aria-valuenow when the rate changes', () => {
      const fixture = createTestComponent('<ngb-rating [max]="max" rate="3"></ngb-rating>');

      const rating = fixture.debugElement.query(By.directive(NgbRating));

      getStar(rating.nativeElement, 7).click();
      fixture.detectChanges();

      expect(rating.attributes['aria-valuenow']).toBe('7');
    });

    it('updates aria-valuetext when the rate changes', () => {
      const fixture = createTestComponent('<ngb-rating [max]="max" rate="3"></ngb-rating>');

      const rating = fixture.debugElement.query(By.directive(NgbRating));

      getStar(rating.nativeElement, 7).click();
      fixture.detectChanges();

      expect(rating.attributes['aria-valuetext']).toBe('7 out of 10');
    });

    it('updates aria-disabled when readonly', () => {
      const fixture = createTestComponent('<ngb-rating></ngb-rating>');
      let ratingEl = fixture.debugElement.query(By.directive(NgbRating));
      fixture.detectChanges();
      expect(ratingEl.attributes['aria-disabled'] == null).toBeTruthy();

      let ratingComp = <NgbRating>ratingEl.componentInstance;
      ratingComp.readonly = true;
      fixture.detectChanges();
      expect(ratingEl.attributes['aria-disabled']).toBe('true');
    });
  });

  it('should set tabindex to -1 when disabled', () => {
    const fixture = createTestComponent('<ngb-rating></ngb-rating>');
    let ratingEl = fixture.debugElement.query(By.directive(NgbRating));
    let ratingComp = <NgbRating>ratingEl.componentInstance;

    fixture.detectChanges();
    expect(ratingEl.nativeElement.getAttribute('tabindex')).toEqual('0');

    ratingComp.disabled = true;
    fixture.detectChanges();
    expect(ratingEl.nativeElement.getAttribute('tabindex')).toEqual('-1');
  });

  describe('keyboard support', () => {

    it('should handle arrow keys', () => {
      const fixture = createTestComponent('<ngb-rating [rate]="3" [max]="5"></ngb-rating>');

      const element = fixture.debugElement.query(By.directive(NgbRating));

      // right -> +1
      let event = createKeyDownEvent(Key.ArrowRight);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, true, false]);
      expect(event.preventDefault).toHaveBeenCalled();

      // up -> +1
      event = createKeyDownEvent(Key.ArrowUp);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, true, true]);
      expect(event.preventDefault).toHaveBeenCalled();

      // left -> -1
      event = createKeyDownEvent(Key.ArrowLeft);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, true, false]);
      expect(event.preventDefault).toHaveBeenCalled();

      // down -> -1
      event = createKeyDownEvent(Key.ArrowDown);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, false, false]);
      expect(event.preventDefault).toHaveBeenCalled();

      // any other -> 0
      event = createKeyDownEvent(Key.Space);
      const expectedState = getState(element.nativeElement);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual(expectedState);
      expect(event.preventDefault).not.toHaveBeenCalled();
    });

    it('should handle home/end keys', () => {
      const fixture = createTestComponent('<ngb-rating [rate]="3" [max]="5"></ngb-rating>');

      const element = fixture.debugElement.query(By.directive(NgbRating));

      // home -> 0
      let event = createKeyDownEvent(Key.Home);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
      expect(event.preventDefault).toHaveBeenCalled();

      // end -> max
      event = createKeyDownEvent(Key.End);
      element.triggerEventHandler('keydown', event);
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, true, true]);
      expect(event.preventDefault).toHaveBeenCalled();
    });
  });

  describe('forms', () => {

    it('should work with template-driven form validation', async(() => {
         const html = `
        <form>
          <ngb-rating [(ngModel)]="model" name="control" max="5" required></ngb-rating>
        </form>`;

         const fixture = createTestComponent(html);
         const element = fixture.debugElement.query(By.directive(NgbRating));

         fixture.detectChanges();
         fixture.whenStable()
             .then(() => {
               fixture.detectChanges();
               expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
               expect(element.nativeElement).toHaveCssClass('ng-invalid');
               expect(element.nativeElement).toHaveCssClass('ng-untouched');

               fixture.componentInstance.model = 1;
               fixture.detectChanges();
               return fixture.whenStable();
             })
             .then(() => {
               fixture.detectChanges();
               expect(getState(element.nativeElement)).toEqual([true, false, false, false, false]);
               expect(element.nativeElement).toHaveCssClass('ng-valid');
               expect(element.nativeElement).toHaveCssClass('ng-untouched');

               fixture.componentInstance.model = 0;
               fixture.detectChanges();
               return fixture.whenStable();
             })
             .then(() => {
               fixture.detectChanges();
               expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
               expect(element.nativeElement).toHaveCssClass('ng-valid');
               expect(element.nativeElement).toHaveCssClass('ng-untouched');
             });
       }));

    it('should work with reactive form validation', () => {
      const html = `
        <form [formGroup]="form">
          <ngb-rating formControlName="rating" max="5"></ngb-rating>
        </form>`;

      const fixture = createTestComponent(html);
      const element = fixture.debugElement.query(By.directive(NgbRating));

      expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
      expect(element.nativeElement).toHaveCssClass('ng-invalid');
      expect(element.nativeElement).toHaveCssClass('ng-untouched');

      fixture.componentInstance.form.patchValue({rating: 3});
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, false, false]);
      expect(element.nativeElement).toHaveCssClass('ng-valid');
      expect(element.nativeElement).toHaveCssClass('ng-untouched');

      fixture.componentInstance.form.patchValue({rating: 0});
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
      expect(element.nativeElement).toHaveCssClass('ng-valid');
      expect(element.nativeElement).toHaveCssClass('ng-untouched');
    });

    it('should not update template driven form by clicking disabled control', async(() => {
         const html = `
          <ngb-rating [(ngModel)]="model" class="control" max="5"></ngb-rating>
          <ngb-rating [(ngModel)]="model" class="control-disabled" max="5" disabled></ngb-rating>`;

         const fixture = createTestComponent(html);
         const element = fixture.debugElement.query(By.css('.control'));
         const disabledElement = fixture.debugElement.query(By.css('.control-disabled'));

         fixture.detectChanges();
         fixture.whenStable()
             .then(() => {
               getStar(element.nativeElement, 3).click();

               fixture.detectChanges();
               return fixture.whenStable();
             })
             .then(() => {
               expect(getState(element.nativeElement)).toEqual([true, true, true, false, false]);
               expect(getState(disabledElement.nativeElement)).toEqual([false, false, false, false, false]);
               expect(fixture.componentInstance.model).toEqual(3);

               getStar(disabledElement.nativeElement, 4).click();
               fixture.detectChanges();
               return fixture.whenStable();
             })
             .then(() => {
               fixture.detectChanges();
               expect(getState(element.nativeElement)).toEqual([true, true, true, false, false]);
               expect(getState(disabledElement.nativeElement)).toEqual([false, false, false, false, false]);
               expect(fixture.componentInstance.model).toEqual(3);
             });
       }));

    it('should handle clicks and update form control', () => {
      const html = `
        <form [formGroup]="form">
          <ngb-rating formControlName="rating" max="5"></ngb-rating>
        </form>`;

      const fixture = createTestComponent(html);
      const element = fixture.debugElement.query(By.directive(NgbRating));

      expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
      expect(element.nativeElement).toHaveCssClass('ng-invalid');
      expect(element.nativeElement).toHaveCssClass('ng-untouched');

      getStar(element.nativeElement, 3).click();
      fixture.detectChanges();
      expect(getState(element.nativeElement)).toEqual([true, true, true, false, false]);
      expect(element.nativeElement).toHaveCssClass('ng-valid');
      expect(element.nativeElement).toHaveCssClass('ng-touched');
    });

    it('should work with both rate input and form control', fakeAsync(() => {
         const html = `
        <form [formGroup]="form">
          <ngb-rating [(rate)]="rate" formControlName="rating" max="5"></ngb-rating>
        </form>`;

         const fixture = createTestComponent(html);
         const element = fixture.debugElement.query(By.directive(NgbRating));

         expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
         expect(element.nativeElement).toHaveCssClass('ng-invalid');

         getStar(element.nativeElement, 2).click();
         fixture.detectChanges();
         tick();
         expect(getState(element.nativeElement)).toEqual([true, true, false, false, false]);
         expect(fixture.componentInstance.rate).toBe(2);
         expect(element.nativeElement).toHaveCssClass('ng-valid');

         fixture.componentInstance.rate = 4;
         fixture.detectChanges();
         tick();
         expect(getState(element.nativeElement)).toEqual([true, true, true, true, false]);
         expect(fixture.componentInstance.form.get('rating') !.value).toBe(4);
         expect(element.nativeElement).toHaveCssClass('ng-valid');
       }));

    it('should disable widget when a control is disabled', fakeAsync(() => {
         const html = `
        <form [formGroup]="form">
          <ngb-rating formControlName="rating" max="5"></ngb-rating>
        </form>`;

         const fixture = createTestComponent(html);
         const element = fixture.debugElement.query(By.directive(NgbRating));

         expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
         expect(fixture.componentInstance.form.get('rating') !.disabled).toBeFalsy();

         fixture.componentInstance.form.get('rating') !.disable();
         fixture.detectChanges();
         expect(fixture.componentInstance.form.get('rating') !.disabled).toBeTruthy();

         getStar(element.nativeElement, 3).click();
         fixture.detectChanges();
         expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
       }));

    it('should mark control as touched on blur', fakeAsync(() => {
         const html = `
        <form [formGroup]="form">
          <ngb-rating formControlName="rating" max="5"></ngb-rating>
        </form>`;

         const fixture = createTestComponent(html);
         const element = fixture.debugElement.query(By.directive(NgbRating));

         expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
         expect(element.nativeElement).toHaveCssClass('ng-untouched');

         element.triggerEventHandler('blur', {});
         fixture.detectChanges();
         expect(getState(element.nativeElement)).toEqual([false, false, false, false, false]);
         expect(element.nativeElement).toHaveCssClass('ng-touched');
       }));
  });

  describe('Custom config', () => {
    let config: NgbRatingConfig;

    beforeEach(() => { TestBed.configureTestingModule({imports: [NgbRatingModule]}); });

    beforeEach(inject([NgbRatingConfig], (c: NgbRatingConfig) => {
      config = c;
      config.max = 5;
      config.readonly = true;
    }));

    it('should initialize inputs with provided config', () => {
      const fixture = TestBed.createComponent(NgbRating);
      fixture.detectChanges();

      let rating = fixture.componentInstance;
      expect(rating.max).toBe(config.max);
      expect(rating.readonly).toBe(config.readonly);
    });
  });

  describe('Custom config as provider', () => {
    let config = new NgbRatingConfig();
    config.max = 5;
    config.readonly = true;

    beforeEach(() => {
      TestBed.configureTestingModule(
          {imports: [NgbRatingModule], providers: [{provide: NgbRatingConfig, useValue: config}]});
    });

    it('should initialize inputs with provided config as provider', () => {
      const fixture = TestBed.createComponent(NgbRating);
      fixture.detectChanges();

      let rating = fixture.componentInstance;
      expect(rating.max).toBe(config.max);
      expect(rating.readonly).toBe(config.readonly);
    });
  });
});

@Component({selector: 'test-cmp', template: ''})
class TestComponent {
  changed = false;
  form = new FormGroup({rating: new FormControl(null, Validators.required)});
  max = 10;
  model;
  rate = 3;
}


text:
<?php

class Google_Service_Directory_DomainAliases extends Google_Collection
{
  protected $collection_key = 'domainAliases';
  protected $domainAliasesType = 'Google_Service_Directory_DomainAlias';
  protected $domainAliasesDataType = 'array';
  public $etag;
  public $kind;

  public function setDomainAliases($domainAliases)
  {
    $this->domainAliases = $domainAliases;
  }
  public function getDomainAliases()
  {
    return $this->domainAliases;
  }
  public function setEtag($etag)
  {
    $this->etag = $etag;
  }
  public function getEtag()
  {
    return $this->etag;
  }
  public function setKind($kind)
  {
    $this->kind = $kind;
  }
  public function getKind()
  {
    return $this->kind;
  }
}

redpajama_set_name: RedPajamaGithub


text:
import re

import requests

import mavri

wiki = 'tr.wikipedia'
xx = mavri.login(wiki, 'Mavrikant')
months = ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık']
Templates = ['Düzenle', 'Kaynaksız', 'Temizleme', 'Viki-bağlantısız', 'Çıkmaz sokak']

for temp in Templates:
    pages = mavri.embeddedin(wiki, 'Şablon:' + temp)
    for page in pages:
        title = page['title']
        print temp + ': ' + title
        content = str(mavri.content_of_page(wiki, title))
        date = re.findall('\{\{\s*' + temp + '\s*\|([^\}]*)\}\}', content, flags=re.IGNORECASE)
        if not date:
            revisions = requests.get(
                    'https://tr.wikipedia.org/w/api.php?action=query&format=json&prop=revisions&titles=' + title + '&utf8=1&rvprop=ids|timestamp|content&rvlimit=1000')
            for revision in revisions.json()['query']['pages'].itervalues().next()['revisions']:
                if not re.search('\{\{\s*' + temp + '\s*\}\}', str(revision['*']), re.IGNORECASE):
                    break
                timestamp = revision['timestamp']
                diff = revision['revid']
            year = timestamp[:4]
            month = months[int(timestamp[5:7]) - 1]
            text = re.sub('\{\{\s*' + temp + '\s*\}\}', '{{' + temp + '|' + month + ' ' + year + '}}', content,
                          flags=re.I)
            summary = temp + ' şablonuna tarih eklendi. [[Special:Diff/' + str(diff) + '|Kaynak]]'
            mavri.change_page(wiki, title, text, summary, xx)


text:
/**
 * Methods for traversing the DOM structure.
 *
 * @module cheerio/traversing
 */
import { hasChildren, isDocument, } from 'domhandler';
import * as select from 'cheerio-select';
import { domEach, isTag, isCheerio } from '../utils.js';
import { contains } from '../static.js';
import { getChildren, getSiblings, nextElementSibling, prevElementSibling, uniqueSort, } from 'domutils';
const reSiblingSelector = /^\s*[~+]/;
/**
 * Get the descendants of each element in the current set of matched elements,
 * filtered by a selector, jQuery object, or element.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('#fruits').find('li').length;
 * //=> 3
 * $('#fruits').find($('.apple')).length;
 * //=> 1
 * ```
 *
 * @param selectorOrHaystack - Element to look for.
 * @returns The found elements.
 * @see {@link https://api.jquery.com/find/}
 */
export function find(selectorOrHaystack) {
    var _a;
    if (!selectorOrHaystack) {
        return this._make([]);
    }
    const context = this.toArray();
    if (typeof selectorOrHaystack !== 'string') {
        const haystack = isCheerio(selectorOrHaystack)
            ? selectorOrHaystack.toArray()
            : [selectorOrHaystack];
        return this._make(haystack.filter((elem) => context.some((node) => contains(node, elem))));
    }
    const elems = reSiblingSelector.test(selectorOrHaystack)
        ? context
        : this.children().toArray();
    const options = {
        context,
        root: (_a = this._root) === null || _a === void 0 ? void 0 : _a[0],
        // Pass options that are recognized by `cheerio-select`
        xmlMode: this.options.xmlMode,
        lowerCaseTags: this.options.lowerCaseTags,
        lowerCaseAttributeNames: this.options.lowerCaseAttributeNames,
        pseudos: this.options.pseudos,
        quirksMode: this.options.quirksMode,
    };
    return this._make(select.select(selectorOrHaystack, elems, options));
}
/**
 * Creates a matcher, using a particular mapping function. Matchers provide a
 * function that finds elements using a generating function, supporting filtering.
 *
 * @private
 * @param matchMap - Mapping function.
 * @returns - Function for wrapping generating functions.
 */
function _getMatcher(matchMap) {
    return function (fn, ...postFns) {
        return function (selector) {
            var _a;
            let matched = matchMap(fn, this);
            if (selector) {
                matched = filterArray(matched, selector, this.options.xmlMode, (_a = this._root) === null || _a === void 0 ? void 0 : _a[0]);
            }
            return this._make(
            // Post processing is only necessary if there is more than one element.
            this.length > 1 && matched.length > 1
                ? postFns.reduce((elems, fn) => fn(elems), matched)
                : matched);
        };
    };
}
/** Matcher that adds multiple elements for each entry in the input. */
const _matcher = _getMatcher((fn, elems) => {
    const ret = [];
    for (let i = 0; i < elems.length; i++) {
        const value = fn(elems[i]);
        ret.push(value);
    }
    return new Array().concat(...ret);
});
/** Matcher that adds at most one element for each entry in the input. */
const _singleMatcher = _getMatcher((fn, elems) => {
    const ret = [];
    for (let i = 0; i < elems.length; i++) {
        const value = fn(elems[i]);
        if (value !== null) {
            ret.push(value);
        }
    }
    return ret;
});
/**
 * Matcher that supports traversing until a condition is met.
 *
 * @returns A function usable for `*Until` methods.
 */
function _matchUntil(nextElem, ...postFns) {
    // We use a variable here that is used from within the matcher.
    let matches = null;
    const innerMatcher = _getMatcher((nextElem, elems) => {
        const matched = [];
        domEach(elems, (elem) => {
            for (let next; (next = nextElem(elem)); elem = next) {
                // FIXME: `matched` might contain duplicates here and the index is too large.
                if (matches === null || matches === void 0 ? void 0 : matches(next, matched.length))
                    break;
                matched.push(next);
            }
        });
        return matched;
    })(nextElem, ...postFns);
    return function (selector, filterSelector) {
        // Override `matches` variable with the new target.
        matches =
            typeof selector === 'string'
                ? (elem) => select.is(elem, selector, this.options)
                : selector
                    ? getFilterFn(selector)
                    : null;
        const ret = innerMatcher.call(this, filterSelector);
        // Set `matches` to `null`, so we don't waste memory.
        matches = null;
        return ret;
    };
}
function _removeDuplicates(elems) {
    return Array.from(new Set(elems));
}
/**
 * Get the parent of each element in the current set of matched elements,
 * optionally filtered by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.pear').parent().attr('id');
 * //=> fruits
 * ```
 *
 * @param selector - If specified filter for parent.
 * @returns The parents.
 * @see {@link https://api.jquery.com/parent/}
 */
export const parent = _singleMatcher(({ parent }) => (parent && !isDocument(parent) ? parent : null), _removeDuplicates);
/**
 * Get a set of parents filtered by `selector` of each element in the current
 * set of match elements.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.orange').parents().length;
 * //=> 2
 * $('.orange').parents('#fruits').length;
 * //=> 1
 * ```
 *
 * @param selector - If specified filter for parents.
 * @returns The parents.
 * @see {@link https://api.jquery.com/parents/}
 */
export const parents = _matcher((elem) => {
    const matched = [];
    while (elem.parent && !isDocument(elem.parent)) {
        matched.push(elem.parent);
        elem = elem.parent;
    }
    return matched;
}, uniqueSort, (elems) => elems.reverse());
/**
 * Get the ancestors of each element in the current set of matched elements, up
 * to but not including the element matched by the selector, DOM node, or cheerio object.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.orange').parentsUntil('#food').length;
 * //=> 1
 * ```
 *
 * @param selector - Selector for element to stop at.
 * @param filterSelector - Optional filter for parents.
 * @returns The parents.
 * @see {@link https://api.jquery.com/parentsUntil/}
 */
export const parentsUntil = _matchUntil(({ parent }) => (parent && !isDocument(parent) ? parent : null), uniqueSort, (elems) => elems.reverse());
/**
 * For each element in the set, get the first element that matches the selector
 * by testing the element itself and traversing up through its ancestors in the DOM tree.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.orange').closest();
 * //=> []
 *
 * $('.orange').closest('.apple');
 * // => []
 *
 * $('.orange').closest('li');
 * //=> [<li class="orange">Orange</li>]
 *
 * $('.orange').closest('#fruits');
 * //=> [<ul id="fruits"> ... </ul>]
 * ```
 *
 * @param selector - Selector for the element to find.
 * @returns The closest nodes.
 * @see {@link https://api.jquery.com/closest/}
 */
export function closest(selector) {
    var _a;
    const set = [];
    if (!selector) {
        return this._make(set);
    }
    const selectOpts = {
        xmlMode: this.options.xmlMode,
        root: (_a = this._root) === null || _a === void 0 ? void 0 : _a[0],
    };
    const selectFn = typeof selector === 'string'
        ? (elem) => select.is(elem, selector, selectOpts)
        : getFilterFn(selector);
    domEach(this, (elem) => {
        while (elem && isTag(elem)) {
            if (selectFn(elem, 0)) {
                // Do not add duplicate elements to the set
                if (!set.includes(elem)) {
                    set.push(elem);
                }
                break;
            }
            elem = elem.parent;
        }
    });
    return this._make(set);
}
/**
 * Gets the next sibling of the first selected element, optionally filtered by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.apple').next().hasClass('orange');
 * //=> true
 * ```
 *
 * @param selector - If specified filter for sibling.
 * @returns The next nodes.
 * @see {@link https://api.jquery.com/next/}
 */
export const next = _singleMatcher((elem) => nextElementSibling(elem));
/**
 * Gets all the following siblings of the first selected element, optionally
 * filtered by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.apple').nextAll();
 * //=> [<li class="orange">Orange</li>, <li class="pear">Pear</li>]
 * $('.apple').nextAll('.orange');
 * //=> [<li class="orange">Orange</li>]
 * ```
 *
 * @param selector - If specified filter for siblings.
 * @returns The next nodes.
 * @see {@link https://api.jquery.com/nextAll/}
 */
export const nextAll = _matcher((elem) => {
    const matched = [];
    while (elem.next) {
        elem = elem.next;
        if (isTag(elem))
            matched.push(elem);
    }
    return matched;
}, _removeDuplicates);
/**
 * Gets all the following siblings up to but not including the element matched
 * by the selector, optionally filtered by another selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.apple').nextUntil('.pear');
 * //=> [<li class="orange">Orange</li>]
 * ```
 *
 * @param selector - Selector for element to stop at.
 * @param filterSelector - If specified filter for siblings.
 * @returns The next nodes.
 * @see {@link https://api.jquery.com/nextUntil/}
 */
export const nextUntil = _matchUntil((el) => nextElementSibling(el), _removeDuplicates);
/**
 * Gets the previous sibling of the first selected element optionally filtered
 * by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.orange').prev().hasClass('apple');
 * //=> true
 * ```
 *
 * @param selector - If specified filter for siblings.
 * @returns The previous nodes.
 * @see {@link https://api.jquery.com/prev/}
 */
export const prev = _singleMatcher((elem) => prevElementSibling(elem));
/**
 * Gets all the preceding siblings of the first selected element, optionally
 * filtered by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.pear').prevAll();
 * //=> [<li class="orange">Orange</li>, <li class="apple">Apple</li>]
 *
 * $('.pear').prevAll('.orange');
 * //=> [<li class="orange">Orange</li>]
 * ```
 *
 * @param selector - If specified filter for siblings.
 * @returns The previous nodes.
 * @see {@link https://api.jquery.com/prevAll/}
 */
export const prevAll = _matcher((elem) => {
    const matched = [];
    while (elem.prev) {
        elem = elem.prev;
        if (isTag(elem))
            matched.push(elem);
    }
    return matched;
}, _removeDuplicates);
/**
 * Gets all the preceding siblings up to but not including the element matched
 * by the selector, optionally filtered by another selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.pear').prevUntil('.apple');
 * //=> [<li class="orange">Orange</li>]
 * ```
 *
 * @param selector - Selector for element to stop at.
 * @param filterSelector - If specified filter for siblings.
 * @returns The previous nodes.
 * @see {@link https://api.jquery.com/prevUntil/}
 */
export const prevUntil = _matchUntil((el) => prevElementSibling(el), _removeDuplicates);
/**
 * Get the siblings of each element (excluding the element) in the set of
 * matched elements, optionally filtered by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.pear').siblings().length;
 * //=> 2
 *
 * $('.pear').siblings('.orange').length;
 * //=> 1
 * ```
 *
 * @param selector - If specified filter for siblings.
 * @returns The siblings.
 * @see {@link https://api.jquery.com/siblings/}
 */
export const siblings = _matcher((elem) => getSiblings(elem).filter((el) => isTag(el) && el !== elem), uniqueSort);
/**
 * Gets the element children of each element in the set of matched elements.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('#fruits').children().length;
 * //=> 3
 *
 * $('#fruits').children('.pear').text();
 * //=> Pear
 * ```
 *
 * @param selector - If specified filter for children.
 * @returns The children.
 * @see {@link https://api.jquery.com/children/}
 */
export const children = _matcher((elem) => getChildren(elem).filter(isTag), _removeDuplicates);
/**
 * Gets the children of each element in the set of matched elements, including
 * text and comment nodes.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('#fruits').contents().length;
 * //=> 3
 * ```
 *
 * @returns The children.
 * @see {@link https://api.jquery.com/contents/}
 */
export function contents() {
    const elems = this.toArray().reduce((newElems, elem) => hasChildren(elem) ? newElems.concat(elem.children) : newElems, []);
    return this._make(elems);
}
/**
 * Iterates over a cheerio object, executing a function for each matched
 * element. When the callback is fired, the function is fired in the context of
 * the DOM element, so `this` refers to the current element, which is equivalent
 * to the function parameter `element`. To break out of the `each` loop early,
 * return with `false`.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * const fruits = [];
 *
 * $('li').each(function (i, elem) {
 *   fruits[i] = $(this).text();
 * });
 *
 * fruits.join(', ');
 * //=> Apple, Orange, Pear
 * ```
 *
 * @param fn - Function to execute.
 * @returns The instance itself, useful for chaining.
 * @see {@link https://api.jquery.com/each/}
 */
export function each(fn) {
    let i = 0;
    const len = this.length;
    while (i < len && fn.call(this[i], i, this[i]) !== false)
        ++i;
    return this;
}
/**
 * Pass each element in the current matched set through a function, producing a
 * new Cheerio object containing the return values. The function can return an
 * individual data item or an array of data items to be inserted into the
 * resulting set. If an array is returned, the elements inside the array are
 * inserted into the set. If the function returns null or undefined, no element
 * will be inserted.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('li')
 *   .map(function (i, el) {
 *     // this === el
 *     return $(this).text();
 *   })
 *   .toArray()
 *   .join(' ');
 * //=> "apple orange pear"
 * ```
 *
 * @param fn - Function to execute.
 * @returns The mapped elements, wrapped in a Cheerio collection.
 * @see {@link https://api.jquery.com/map/}
 */
export function map(fn) {
    let elems = [];
    for (let i = 0; i < this.length; i++) {
        const el = this[i];
        const val = fn.call(el, i, el);
        if (val != null) {
            elems = elems.concat(val);
        }
    }
    return this._make(elems);
}
/**
 * Creates a function to test if a filter is matched.
 *
 * @param match - A filter.
 * @returns A function that determines if a filter has been matched.
 */
function getFilterFn(match) {
    if (typeof match === 'function') {
        return (el, i) => match.call(el, i, el);
    }
    if (isCheerio(match)) {
        return (el) => Array.prototype.includes.call(match, el);
    }
    return function (el) {
        return match === el;
    };
}
export function filter(match) {
    var _a;
    return this._make(filterArray(this.toArray(), match, this.options.xmlMode, (_a = this._root) === null || _a === void 0 ? void 0 : _a[0]));
}
export function filterArray(nodes, match, xmlMode, root) {
    return typeof match === 'string'
        ? select.filter(match, nodes, { xmlMode, root })
        : nodes.filter(getFilterFn(match));
}
/**
 * Checks the current list of elements and returns `true` if _any_ of the
 * elements match the selector. If using an element or Cheerio selection,
 * returns `true` if _any_ of the elements match. If using a predicate function,
 * the function is executed in the context of the selected element, so `this`
 * refers to the current element.
 *
 * @category Attributes
 * @param selector - Selector for the selection.
 * @returns Whether or not the selector matches an element of the instance.
 * @see {@link https://api.jquery.com/is/}
 */
export function is(selector) {
    const nodes = this.toArray();
    return typeof selector === 'string'
        ? select.some(nodes.filter(isTag), selector, this.options)
        : selector
            ? nodes.some(getFilterFn(selector))
            : false;
}
/**
 * Remove elements from the set of matched elements. Given a Cheerio object that
 * represents a set of DOM elements, the `.not()` method constructs a new
 * Cheerio object from a subset of the matching elements. The supplied selector
 * is tested against each element; the elements that don't match the selector
 * will be included in the result.
 *
 * The `.not()` method can take a function as its argument in the same way that
 * `.filter()` does. Elements for which the function returns `true` are excluded
 * from the filtered set; all other elements are included.
 *
 * @category Traversing
 * @example
Selector

 *
 * ```js
 * $('li').not('.apple').length;
 * //=> 2
 * ```
 *
 * @example
Function

 *
 * ```js
 * $('li').not(function (i, el) {
 *   // this === el
 *   return $(this).attr('class') === 'orange';
 * }).length; //=> 2
 * ```
 *
 * @param match - Value to look for, following the rules above.
 * @param container - Optional node to filter instead.
 * @returns The filtered collection.
 * @see {@link https://api.jquery.com/not/}
 */
export function not(match) {
    let nodes = this.toArray();
    if (typeof match === 'string') {
        const matches = new Set(select.filter(match, nodes, this.options));
        nodes = nodes.filter((el) => !matches.has(el));
    }
    else {
        const filterFn = getFilterFn(match);
        nodes = nodes.filter((el, i) => !filterFn(el, i));
    }
    return this._make(nodes);
}
/**
 * Filters the set of matched elements to only those which have the given DOM
 * element as a descendant or which have a descendant that matches the given
 * selector. Equivalent to `.filter(':has(selector)')`.
 *
 * @category Traversing
 * @example
Selector

 *
 * ```js
 * $('ul').has('.pear').attr('id');
 * //=> fruits
 * ```
 *
 * @example
Element

 *
 * ```js
 * $('ul').has($('.pear')[0]).attr('id');
 * //=> fruits
 * ```
 *
 * @param selectorOrHaystack - Element to look for.
 * @returns The filtered collection.
 * @see {@link https://api.jquery.com/has/}
 */
export function has(selectorOrHaystack) {
    return this.filter(typeof selectorOrHaystack === 'string'
        ? // Using the `:has` selector here short-circuits searches.
            `:has(${selectorOrHaystack})`
        : (_, el) => this._make(el).find(selectorOrHaystack).length > 0);
}
/**
 * Will select the first element of a cheerio object.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('#fruits').children().first().text();
 * //=> Apple
 * ```
 *
 * @returns The first element.
 * @see {@link https://api.jquery.com/first/}
 */
export function first() {
    return this.length > 1 ? this._make(this[0]) : this;
}
/**
 * Will select the last element of a cheerio object.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('#fruits').children().last().text();
 * //=> Pear
 * ```
 *
 * @returns The last element.
 * @see {@link https://api.jquery.com/last/}
 */
export function last() {
    return this.length > 0 ? this._make(this[this.length - 1]) : this;
}
/**
 * Reduce the set of matched elements to the one at the specified index. Use
 * `.eq(-i)` to count backwards from the last selected element.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('li').eq(0).text();
 * //=> Apple
 *
 * $('li').eq(-1).text();
 * //=> Pear
 * ```
 *
 * @param i - Index of the element to select.
 * @returns The element at the `i`th position.
 * @see {@link https://api.jquery.com/eq/}
 */
export function eq(i) {
    var _a;
    i = +i;
    // Use the first identity optimization if possible
    if (i === 0 && this.length <= 1)
        return this;
    if (i < 0)
        i = this.length + i;
    return this._make((_a = this[i]) !== null && _a !== void 0 ? _a : []);
}
export function get(i) {
    if (i == null) {
        return this.toArray();
    }
    return this[i < 0 ? this.length + i : i];
}
/**
 * Retrieve all the DOM elements contained in the jQuery set as an array.
 *
 * @example
 *
 * ```js
 * $('li').toArray();
 * //=> [ {...}, {...}, {...} ]
 * ```
 *
 * @returns The contained items.
 */
export function toArray() {
    return Array.prototype.slice.call(this);
}
/**
 * Search for a given element from among the matched elements.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.pear').index();
 * //=> 2 $('.orange').index('li');
 * //=> 1
 * $('.apple').index($('#fruit, li'));
 * //=> 1
 * ```
 *
 * @param selectorOrNeedle - Element to look for.
 * @returns The index of the element.
 * @see {@link https://api.jquery.com/index/}
 */
export function index(selectorOrNeedle) {
    let $haystack;
    let needle;
    if (selectorOrNeedle == null) {
        $haystack = this.parent().children();
        needle = this[0];
    }
    else if (typeof selectorOrNeedle === 'string') {
        $haystack = this._make(selectorOrNeedle);
        needle = this[0];
    }
    else {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        $haystack = this;
        needle = isCheerio(selectorOrNeedle)
            ? selectorOrNeedle[0]
            : selectorOrNeedle;
    }
    return Array.prototype.indexOf.call($haystack, needle);
}
/**
 * Gets the elements matching the specified range (0-based position).
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('li').slice(1).eq(0).text();
 * //=> 'Orange'
 *
 * $('li').slice(1, 2).length;
 * //=> 1
 * ```
 *
 * @param start - An position at which the elements begin to be selected. If
 *   negative, it indicates an offset from the end of the set.
 * @param end - An position at which the elements stop being selected. If
 *   negative, it indicates an offset from the end of the set. If omitted, the
 *   range continues until the end of the set.
 * @returns The elements matching the specified range.
 * @see {@link https://api.jquery.com/slice/}
 */
export function slice(start, end) {
    return this._make(Array.prototype.slice.call(this, start, end));
}
/**
 * End the most recent filtering operation in the current chain and return the
 * set of matched elements to its previous state.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('li').eq(0).end().length;
 * //=> 3
 * ```
 *
 * @returns The previous state of the set of matched elements.
 * @see {@link https://api.jquery.com/end/}
 */
export function end() {
    var _a;
    return (_a = this.prevObject) !== null && _a !== void 0 ? _a : this._make([]);
}
/**
 * Add elements to the set of matched elements.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('.apple').add('.orange').length;
 * //=> 2
 * ```
 *
 * @param other - Elements to add.
 * @param context - Optionally the context of the new selection.
 * @returns The combined set.
 * @see {@link https://api.jquery.com/add/}
 */
export function add(other, context) {
    const selection = this._make(other, context);
    const contents = uniqueSort([...this.get(), ...selection.get()]);
    return this._make(contents);
}
/**
 * Add the previous set of elements on the stack to the current set, optionally
 * filtered by a selector.
 *
 * @category Traversing
 * @example
 *
 * ```js
 * $('li').eq(0).addBack('.orange').length;
 * //=> 2
 * ```
 *
 * @param selector - Selector for the elements to add.
 * @returns The combined set.
 * @see {@link https://api.jquery.com/addBack/}
 */
export function addBack(selector) {
    return this.prevObject
        ? this.add(selector ? this.prevObject.filter(selector) : this.prevObject)
        : this;
}
//# sourceMappingURL=traversing.js.map


text:
from django.test import TestCase

from django.conf import settings

from dajaxice.core import DajaxiceConfig
from dajaxice.core.Dajaxice import DajaxiceModule, DajaxiceFunction, Dajaxice
from dajaxice.exceptions import FunctionNotCallableError

class DajaxiceModuleTest(TestCase):

    def setUp(self):
        self.module = DajaxiceModule()

    def test_constructor(self):
        self.assertEqual(self.module.functions, {})
        self.assertEqual(self.module.submodules, {})

    def test_add_function(self):
        function = lambda x: x

        self.module.add('test', function)
        self.assertEqual(self.module.functions, {'test': function})
        self.assertEqual(self.module.submodules, {})

        self.module.add('foo.bar', function)
        self.assertEqual(self.module.functions, {'test': function})
        self.assertEqual(self.module.submodules.keys(), ['foo'])
        self.assertEqual(self.module.submodules['foo'].functions, {'bar': function})

class DajaxiceFunctionTest(TestCase):

    def test_constructor(self):

        class CalledEception(Exception):
            pass

        def callback():
            raise CalledEception

        function = DajaxiceFunction(callback, 'foo', 'POST')

        self.assertEqual(function.function, callback)
        self.assertEqual(function.name, 'foo')
        self.assertEqual(function.method, 'POST')
        self.assertRaises(CalledEception, function.call)

class DajaxiceTest(TestCase):

    def setUp(self):
        self.dajaxice = Dajaxice()
        self.function = lambda x: x

    def test_constructor(self):
        self.assertEqual(self.dajaxice._registry, {})

    def test_register(self):
        self.dajaxice.register(self.function, 'foo')
        self.assertTrue('foo' in self.dajaxice._registry)
        self.assertEqual(type(self.dajaxice._registry['foo']), DajaxiceFunction)

        def bar_function():
            return

        self.dajaxice.register(bar_function)
        self.assertTrue('dajaxice.tests.bar_function' in self.dajaxice._registry)
        self.assertEqual(type(self.dajaxice._registry['dajaxice.tests.bar_function']), DajaxiceFunction)

    def test__is_callable(self):
        self.dajaxice.register(self.function, 'foo')
        self.dajaxice.register(self.function, 'bar', method='GET')

        self.assertTrue(self.dajaxice.is_callable('foo', 'POST'))
        self.assertTrue(self.dajaxice.is_callable('bar', 'GET'))
        self.assertFalse(self.dajaxice.is_callable('foo', 'GET'))
        self.assertFalse(self.dajaxice.is_callable('bar', 'POST'))
        self.assertFalse(self.dajaxice.is_callable('test', 'POST'))
        self.assertFalse(self.dajaxice.is_callable('test', 'GET'))

    def test_clean_method(self):
        self.assertEqual(self.dajaxice.clean_method('post'), 'POST')
        self.assertEqual(self.dajaxice.clean_method('get'), 'GET')
        self.assertEqual(self.dajaxice.clean_method('POST'), 'POST')
        self.assertEqual(self.dajaxice.clean_method('GET'), 'GET')
        self.assertEqual(self.dajaxice.clean_method('other'), 'POST')

    def test_modules(self):
        self.dajaxice.register(self.function, 'foo')
        self.dajaxice.register(self.function, 'bar')

        self.assertEqual(type(self.dajaxice.modules), DajaxiceModule)
        self.assertEqual(self.dajaxice.modules.functions.keys(), ['foo', 'bar'])

class DajaxiceConfigTest(TestCase):

    def setUp(self):
        self.config = DajaxiceConfig()

    def test_defaults(self):
        self.assertTrue(self.config.DAJAXICE_XMLHTTPREQUEST_JS_IMPORT)
        self.assertTrue(self.config.DAJAXICE_JSON2_JS_IMPORT)
        self.assertEqual(self.config.DAJAXICE_EXCEPTION, 'DAJAXICE_EXCEPTION')
        self.assertEqual(self.config.DAJAXICE_MEDIA_PREFIX, 'dajaxice')

        dajaxice_url = r'^%s/' % self.config.DAJAXICE_MEDIA_PREFIX
        self.assertEqual(self.config.dajaxice_url, dajaxice_url)

        self.assertEqual(type(self.config.modules), DajaxiceModule)

class DjangoIntegrationTest(TestCase):

    urls = 'dajaxice.tests.urls'

    def setUp(self):
        settings.INSTALLED_APPS += ('dajaxice.tests',)

    def test_calling_not_registered_function(self):
        self.failUnlessRaises(FunctionNotCallableError, self.client.post, '/dajaxice/dajaxice.tests.this_function_not_exist/')

    def test_calling_registered_function(self):
        response = self.client.post('/dajaxice/dajaxice.tests.test_foo/')

        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, '{"foo": "bar"}')

    def test_calling_registered_function_with_params(self):

        response = self.client.post('/dajaxice/dajaxice.tests.test_foo_with_params/', {'argv': '{"param1":"value1"}'})

        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, '{"param1": "value1"}')

    def test_bad_function(self):

        response = self.client.post('/dajaxice/dajaxice.tests.test_ajax_exception/')
        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, "DAJAXICE_EXCEPTION")

    def test_get_register(self):

        response = self.client.get('/dajaxice/dajaxice.tests.test_get_register/')

        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, '{"foo": "user"}')

    def test_get_custom_name_register(self):

        response = self.client.get('/dajaxice/get_user_data/')

        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, '{"bar": "user"}')

    def test_multi_register(self):

        response = self.client.get('/dajaxice/get_multi/')
        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, '{"foo": "multi"}')

        response = self.client.post('/dajaxice/post_multi/')
        self.failUnlessEqual(response.status_code, 200)
        self.failUnlessEqual(response.content, '{"foo": "multi"}')


text:
<?php

namespace Bootstrap\Cli\Providers;

use Hubzero\Debug\Profiler;
use Hubzero\Base\ServiceProvider;

/**
 * Profiler service provider
 */
class ProfilerServiceProvider extends ServiceProvider
{
	/**
	 * Register the service provider.
	 *
	 * @return  void
	 */
	public function register()
	{
		$this->app['profiler'] = function($app)
		{
			if ($app['config']['debug'] || $app['config']['profile'])
			{
				return new Profiler($app['client']->name);
			}

			return null;
		};
	}
}


text:
def yes_no(prompt):
    """
    Prompt the user with a yes/no question until an answer is received

    :param prompt: the message to display
    :type prompt: str
    :return: whether the user answered yes or no
    :rtype: bool
    """
    while True:
        resp = input('{0} (Y/N) '.format(prompt.strip())).lower().strip()

        if resp == 'y' or resp == 'yes':
            return True
        elif resp == 'n' or resp == 'no':
            return False

def convert_xml_to_dict(root_node):
    """
    Convert an XML ElementTree to a dictionary

    This is only designed to support Gelbooru's idiosyncratic XML layout where all information
    is stored in attributes. It is not meant to handle all cases.

    :param root_node: the XML tree of post metadata
    :type root_node: xml.etree.ElementTree.Element
    :return: a dictionary equivalent of the XML
    :rtype: dict
    """
    new_dict = {}

    for key, value in root_node.items():
        try:
            new_dict[key] = int(value)
        except ValueError:
            new_dict[key] = value

    children = root_node.getchildren()

    if children:
        new_dict[root_node.tag] = [convert_xml_to_dict(child) for child in children]

    return new_dict


text:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from .ooyala import OoyalaIE

class TheSunIE(InfoExtractor):
    _VALID_URL = r'https://(?:www\.)?thesun\.co\.uk/[^/]+/(?P<id>\d+)'
    _TEST = {
        'url': 'https://www.thesun.co.uk/tvandshowbiz/2261604/orlando-bloom-and-katy-perry-post-adorable-instagram-video-together-celebrating-thanksgiving-after-split-rumours/',
        'info_dict': {
            'id': '2261604',
            'title': 'md5:cba22f48bad9218b64d5bbe0e16afddf',
        },
        'playlist_count': 2,
    }

    def _real_extract(self, url):
        article_id = self._match_id(url)

        webpage = self._download_webpage(url, article_id)

        entries = []
        for ooyala_id in re.findall(
                r'<[^>]+\b(?:id\s*=\s*"thesun-ooyala-player-|data-content-id\s*=\s*")([^"]+)',
                webpage):
            entries.append(OoyalaIE._build_url_result(ooyala_id))

        return self.playlist_result(
            entries, article_id, self._og_search_title(webpage, fatal=False))


text:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.r028367.serviceandbinder">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">

        </activity>

        <service
            android:name=".MService"
            android:enabled="true"
            android:exported="true" />
        <service
            android:name=".ExampleService"
            android:enabled="true"
            android:exported="true" />
        <service
            android:name=".com.br.study.intentservice.MyIntentService"
            android:exported="false"></service>

        <activity android:name=".com.br.study.intentservice.ActivityIntentService">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


text:
#include "postgres.h"

#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/fd.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"

typedef struct
{
	char	   *location;
	DIR		   *dirdesc;
} directory_fctx;

/*
 * Convert a "text" filename argument to C string, and check it's allowable.
 *
 * Filename may be absolute or relative to the DataDir, but we only allow
 * absolute paths that match DataDir or Log_directory.
 */
static char *
convert_and_check_filename(text *arg)
{
	int			input_len = VARSIZE(arg) - VARHDRSZ;
	char	   *filename = palloc(input_len + 1);

	memcpy(filename, VARDATA(arg), input_len);
	filename[input_len] = '\0';

	canonicalize_path(filename);	/* filename can change length here */

	/* Disallow ".." in the path */
	if (path_contains_parent_reference(filename))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
			(errmsg("reference to parent directory (\"..\") not allowed"))));

	if (is_absolute_path(filename))
	{
		/* Allow absolute references within DataDir */
		if (path_is_prefix_of_path(DataDir, filename))
			return filename;
		/* The log directory might be outside our datadir, but allow it */
		if (is_absolute_path(Log_directory) &&
			path_is_prefix_of_path(Log_directory, filename))
			return filename;

		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 (errmsg("absolute path not allowed"))));
		return NULL;			/* keep compiler quiet */
	}
	else
	{
		return filename;
	}
}

/*
 * Read a section of a file, returning it as text
 */
Datum
pg_read_file(PG_FUNCTION_ARGS)
{
	text	   *filename_t = PG_GETARG_TEXT_P(0);
	int64		seek_offset = PG_GETARG_INT64(1);
	int64		bytes_to_read = PG_GETARG_INT64(2);
	char	   *buf;
	size_t		nbytes;
	FILE	   *file;
	char	   *filename;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 (errmsg("must be superuser to read files"))));

	filename = convert_and_check_filename(filename_t);

	if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\" for reading: %m",
						filename)));

	if (fseeko(file, (off_t) seek_offset,
			   (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not seek in file \"%s\": %m", filename)));

	if (bytes_to_read < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("requested length cannot be negative")));

	/* not sure why anyone thought that int64 length was a good idea */
	if (bytes_to_read > (MaxAllocSize - VARHDRSZ))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("requested length too large")));

	buf = palloc((Size) bytes_to_read + VARHDRSZ);

	nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file);

	if (ferror(file))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not read file \"%s\": %m", filename)));

	SET_VARSIZE(buf, nbytes + VARHDRSZ);

	FreeFile(file);
	pfree(filename);

	PG_RETURN_TEXT_P(buf);
}

/*
 * stat a file
 */
Datum
pg_stat_file(PG_FUNCTION_ARGS)
{
	text	   *filename_t = PG_GETARG_TEXT_P(0);
	char	   *filename;
	struct stat fst;
	Datum		values[6];
	bool		isnull[6];
	HeapTuple	tuple;
	TupleDesc	tupdesc;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 (errmsg("must be superuser to get file information"))));

	filename = convert_and_check_filename(filename_t);

	if (stat(filename, &fst) < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m", filename)));

	/*
	 * This record type had better match the output parameters declared for me
	 * in pg_proc.h.
	 */
	tupdesc = CreateTemplateTupleDesc(6, false);
	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
					   "size", INT8OID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
					   "access", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber) 3,
					   "modification", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber) 4,
					   "change", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
					   "creation", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
					   "isdir", BOOLOID, -1, 0);
	BlessTupleDesc(tupdesc);

	memset(isnull, false, sizeof(isnull));

	values[0] = Int64GetDatum((int64) fst.st_size);
	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
	/* Unix has file status change time, while Win32 has creation time */
#if !defined(WIN32) && !defined(__CYGWIN__)
	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
	isnull[4] = true;
#else
	isnull[3] = true;
	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));

	tuple = heap_form_tuple(tupdesc, values, isnull);

	pfree(filename);

	PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}

/*
 * List a directory (returns the filenames only)
 */
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
	FuncCallContext *funcctx;
	struct dirent *de;
	directory_fctx *fctx;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 (errmsg("must be superuser to get directory listings"))));

	if (SRF_IS_FIRSTCALL())
	{
		MemoryContext oldcontext;

		funcctx = SRF_FIRSTCALL_INIT();
		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

		fctx = palloc(sizeof(directory_fctx));
		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));

		fctx->dirdesc = AllocateDir(fctx->location);

		if (!fctx->dirdesc)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not open directory \"%s\": %m",
							fctx->location)));

		funcctx->user_fctx = fctx;
		MemoryContextSwitchTo(oldcontext);
	}

	funcctx = SRF_PERCALL_SETUP();
	fctx = (directory_fctx *) funcctx->user_fctx;

	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
	{
		int			len = strlen(de->d_name);
		text	   *result;

		if (strcmp(de->d_name, ".") == 0 ||
			strcmp(de->d_name, "..") == 0)
			continue;

		result = palloc(len + VARHDRSZ);
		SET_VARSIZE(result, len + VARHDRSZ);
		memcpy(VARDATA(result), de->d_name, len);

		SRF_RETURN_NEXT(funcctx, PointerGetDatum(result));
	}

	FreeDir(fctx->dirdesc);

	SRF_RETURN_DONE(funcctx);
}


text:
title: Adding Categories
kind: article
created_at: 2012-03-30
location: New York, NY
h1: Categories
categories:
- blog
- ruby
- nanoc
- categories
---

## nanoc

Before discussing how I added categories to the blog, I need to say
a little about how this site works. I use [nanoc][n], a static site
generator. (If you already know all about tools to make static websites or
nanoc in particular, you should probably skip this section.)

[n]: http://nanoc.stoneship.org/

Here's a simplified version of how nanoc works. You create an input folder
and fill it with content. The content can be images and other media, but in
general it will be text files in whatever format. Common examples will be
HTML, CSS, Javascript, Markdown and so on. Once you have everything ready,
you run nanoc and it processes the input folder and creates an output
folder. The output folder *is* your website, which you can then upload to
your server via FTP or rsync or whatever means you like.

Based on what I've said so far, you might be wondering, "Why not just
create the files, place them directly in the output folder and skip the
nanoc step?" That is, what does nanoc add? Again this is simplified, but
what nanoc adds is filters and processing rules. These give you a lot of
the strengths of a dynamic website (rapid site-wide changes, easy to work
on in pieces) without giving up the strengths of a static site (speed,
security). For example, I write content in Markdown and layouts in ERB.
When I have new content or a new layout ready, nanoc automates the process
of (1) turning the Markdown into HTML, (2) piping that HTML though the ERB
layout and then (3) running the finalized HTML content + ERB layout through
Typogruby to give me prettier content. Another simple example: the front
page of my site always shows the latest five articles in reverse order of
posting. If the site were completely static, I would have to update that
list of links every time I wrote a new article. With nanoc, that's handled
automatically (via a little Ruby in a template).

Out of the box, nanoc provides a ton of [helpers][h] and [filters][f], but
I wanted a categories helper. There is a [tagging][t] helper, but it
wasn't quite what I had in mind. Also, this was a nice opportunity to learn
more about nanoc's internals by building something extra for it. For the
most part, this was very simple, but I'm going to walk through it anyhow.
It will help me solidify what I learned and maybe help someone else too.

[h]: http://nanoc.stoneship.org/docs/api/3.3/Nanoc/Helpers.html
[f]: http://nanoc.stoneship.org/docs/api/3.3/Nanoc/Filters.html
[t]: http://nanoc.stoneship.org/docs/api/3.3/Nanoc/Helpers/Tagging.html

## Categories?

What do I mean by categories? My site is mostly posts. Each post covers one
or more topics, and those topics often overlap. I talk about Vim in
a number of places. Same with Ruby, Bash and so on. I wanted every post to
display its key topics (its categories), and I also wanted to have the
categories be clickable links. Click on [Ruby][r], and you should be taken
to a page that shows you all the posts that include Ruby as a topic. Also,
I wanted to create a page on the site that displays [all the categories][c]
as clickable links. When a new category is added anywhere in the site, that
page should be updated. (Another good example where a static site generator
makes life easier than a truly static site.)

[r]: ../../../categories/ruby.html
[c]: ../../../categories.html

## Code, please.

So enough talking, how does it work? There are three main pieces of
functionality:

+ Add categories to posts and display those categories in individual posts
+ Create a page for all categories
+ Create pages for individual categories

The first step is simple. In nanoc, textual items are always content plus
metadata. The metadata is stored as YAML, and it's completely freeform. So
you can add whatever you need there. As an example, the YAML for this page
looks like this:

    ---
    title: Adding Categories
    kind: article
    created_at: 2012-03-30
    location: New York, NY
    h1: Categories
    categories:
    - blog
    - nanoc
    - categories
    ---

Add that `categories` item to the YAML, and boom. Now every page has an
array of categories.

It's equally easy to access that information about an item. Any metadata
for a page can be accessed at `@item[:attribute]`. So to add the list of
categories to each post, I just added this to the posts layout:

Categories: <%= link_categories(@item[:categories]).join(', ') %>

The `link_categories` method hasn't been explained yet, but we'll get
there soon. Notice that it takes zero new code to add metadata to an item
in nanoc. In order to create category pages, however, and to make the
categories links, we need to add helper code. By default, nanoc will
require any Ruby code we place in a `lib` folder at the root of our
project. The following lives at `lib/categories.rb`:

      def all_categories(posts=articles)
        cats = []
        posts.each do |article|
          next if article[:categories].nil?
          cats << article[:categories]
        end
        cats.flatten.compact.uniq
      end
      memoize :all_categories

      def has_category?(category, article)
        if article[:categories].nil?
          false
        else
          article[:categories].include?(category)
        end
      end

      def articles_with_category(category, posts=articles)
        posts.select { |article| has_category?(category, article) }
      end
      memoize :articles_with_category

      def articles_by_category(posts=articles)
        cats = []
        all_categories.each do |cat|
          cats << [cat, articles_with_category(cat)]
        end
        cats
      end
      memoize :articles_by_category

      def link_categories(cats)
        cats.map do |cat|
          ['', cat, ''].join
        end
      end

The first method goes through an array of articles (all of them by default,
though you can pass in a smaller selection) and returns an array of all the
unique categories. (Maybe I should use a [Set][s]? I'm thinking about it.)
The `has_category?` method should be pretty clear: return true or false if
a given category is found for a given article. That method in turn is used
by `articles_with_category` to find all the posts for a given category. The
`articles_by_category` method returns what I think of as an array of
tuples. Each item in the array is a two-member array: the first item is
a category, and the second is an array of all the articles in that
category. I'll need that method soon to build up all the individual
category pages and display their posts. The final method, which is pretty
kludgy takes an array of categories and returns an array of those category
names as HTML links. It's ugly, but better hidden away here then in an ERB
view.

[s]: http://ruby-doc.org/stdlib-1.9.3/libdoc/set/rdoc/Set.html

## The Hard Part

So far, all of this has been pretty simple stuff. The only thing that was
complicated was the following. Normally nanoc scans the input folder for
items which it then runs through filters according to a set of rules,
placing the final results in the output folder. But in this case, the
individual category files aren't yet in the input folder. In fact, *they
can't be there yet*. Until we've scanned the articles for their categories,
there's no way to know what pages we will need. This gave me a bit of
a headache at first, but it turns out that nanoc has us covered. During
processing nanoc works with an `@items` array, and it's possible to add
things to that array dynamically and in-memory. That is, you can add things
to `@items` that have no textual representation in advance. In addition,
nanoc provides a `preprocess` method that you can call during compilation.
Whatever code you run in that method happens *after* the site has been
scanned but *before* nanoc compiles the `@items`. This is exactly the hook
I needed. Here's the final method in `lib/categories.rb`:

    def create_category_pages
      articles_by_category.each do |category, posts|
        @items << Nanoc::Item.new(
          "<%= render('category', :category => '#{category}') %>",
          {
            :title => "Posts in #{category}",
            :h1 => "#{category} posts",
            :posts => posts
          },
          "/categories/#{category}",
          :binary => false
        )
      end
    end

Using the `articles_by_category` method from above, we go through each
category, create a new nanoc item for it and add that item to the `@items`
array. The content of each item is a single line of ERB that renders some
metadata through a template. The rest of the parameters provide the
metadata, specify the items identifier (which is sort of its filename) and
specify that the item is textual not binary. (For more information about
`Nanoc::Item` initialization, see [the API docs][docs].) All of the posts
for each category are fed as metadata to the category's in-memory page,
waiting to be rendered as links later in a template. It's a little bit of
hocus-pocus, but not very much work at all for us to create a large set of
pages with no textual reality at all. Shiny.

[docs]: http://nanoc.stoneship.org/docs/api/3.3/Nanoc/Item.html#constructor_details

## Links and Credits

The source code of my site is available [on Github][source]. Here's the
[categories code][cats] if that's all you want to look at. I just noticed
that I don't have a license there, but I'll put one up shortly. In
a nutshell, it's "take what you like, but don't blame me". While working on
all of this, I received advice from [Justin Hileman][jh] and [Denis
Defreyne][dd] (nanoc's creator). Justin also shared a gist of some code he
uses on [his blog][jhb] for tags. However, nobody but me is to blame for
anything stupid in my categories implementation. That's all me.

[source]: https://github.com/telemachus/ithaca
[cats]: https://github.com/telemachus/ithaca/blob/master/lib/categories.rb
[jh]: http://twitter.com/bobthecow
[dd]: http://twitter.com/ddfreyne
[jhb]: http://justinhileman.info/


text:
var fs = require('fs');

import { createMysqlAdaptor } from '../../src/data/mysql-adaptor';

function readLines(input, func) {
  var remaining = '';

  input.on('data', function(data) {
    remaining += data;
    var index = remaining.indexOf('\n');
    var last  = 0;
    while (index > -1) {
      var line = remaining.substring(last, index);
      last = index + 1;
      func(line);
      index = remaining.indexOf('\n', last);
    }

    remaining = remaining.substring(last);
  });

  input.on('end', function() {
    if (remaining.length > 0) {
      func(remaining);
    }
    func('$');
  });
}

function ltrim(str) {
    return str.replace(/^\s+/,'');
}

function rtrim(str) {
    return str.replace(/\s+$/,'');
}

function trim(str) {
  return str.replace(/^\s+|\s+$/g, '');
}

function fulltrim(str){
  return str.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ');
}

function readSqlCommands(file) {
  let pending = "";
  const sqlCommands = [];
  const input = fs.createReadStream(file);

  return new Promise((resolve, reject) => {
    // first we read all the sql comamnds
    readLines(input, (line) => {
      const trimmed = trim(line);
      pending += trimmed;

      if (trimmed.length > 0) {
        if (trimmed === '$') {
          return resolve(sqlCommands);
        }
        if (trimmed.charAt(trimmed.length - 1) == ';') {
          sqlCommands.push(pending);
          pending = '';
        } else {
          pending += ' ';
        }
      }
    });
  });
}

class ExecSqlFile {
  constructor(configuration) {
    this.configuration = configuration;
  }

  // http://stackoverflow.com/questions/20100245/how-can-i-execute-array-of-promises-in-sequential-order

  exec(file) {
    let sqlCommands;
    const adaptor = createMysqlAdaptor(this.configuration);

    return new Promise((resolve, reject) => {
      readSqlCommands(file)
        .then((result) => {
          sqlCommands = result;
          return adaptor.start();
        })
        .then(() => {
          return sqlCommands.reduce((promise, command) =>{
            return promise.then(()=>{
              return adaptor.doQuery(command);
            });
          },Promise.resolve());
        })
        .then(() => {
          return adaptor.stop();
        })
        .then(() => {
          return resolve();
        })
        .catch((error) => {
          return reject(err);
        });
    });
  }
}

// this function reads the SQL commands from a SQL file
// and executes them
export function execSqlFile(configuration, file) {
    const sqlf = new ExecSqlFile(configuration);
    return sqlf.exec(file);

}