Schreiben Sie ein Programm, das sich selbst herunterlädt


66

Schreiben Sie ein Programm, das eine Verbindung zu dieser Site herstellt, die Antwort, in der es veröffentlicht wurde, herunterlädt, seinen eigenen Quellcode extrahiert und ihn ausgibt. Die Ausgabe muss mit dem Quellcode identisch sein. Kürzester Code (in Bytes) gewinnt.

Regeln:

  • Es sind keine URL-Kürzungen zulässig.
  • Die Antwort muss ein reguläres Format haben - eine Überschrift mit Name und Größe der Sprache, optionale Beschreibung, Codeblock, optionale Beschreibung und Erläuterung. Keine unnatürlichen Begrenzer erlaubt.
  • Die Ausgabe muss aus dem eigentlichen Codeblock stammen, der auf der Site veröffentlicht wurde.
  • Die Funktionalität darf nicht von der Position in der Antwortliste abhängen. es sollte funktionieren, auch wenn es mehrere seiten gibt und die antwort es nicht auf der ersten.
  • Neu: Besonderer Hinweis für Antworten, die in einem Browser ausgeführt werden sollen: Es ist in Ordnung, sie in der Codegolf-Domäne auszuführen (um der Richtlinie des gleichen Ursprungs zu entsprechen), aber die Domäne und der Pfad sollten in die Lösung aufgenommen werden, um mach es fair.

39
Catch-22: Wie soll ich meine Einreichung testen?
Martin Ender

9
Ich möchte, dass Leute Antworten posten und sie löschen, damit sie ihren Code testen können.
Justin

4
@ m.buettner Antworten können auf andere Antworten (auf andere Fragen) getestet werden, dann gepostet, dann bearbeitet, um die URL zu ändern :)
aditsu

8
@hexafraction Wenn Kommentare in der Lage sind, eine Antwort zu stören, dann ist die Antwort nicht sehr gut ...
aditsu

17
Eine Frage steckte in meinem Kopf: Wie schreibe ich einen Tweet, der ohne URL-Kürzel auf sich selbst verweist, aber indem ich die Tweet-ID Ihres Tweets abschätze?
Ming-Tang

Antworten:


34

Bash + Coreutils + Lynx-Browser, 61 Byte

Danke an @FDinoff für die Tipps:

lynx -dump codegolf.stackexchange.com/posts/28164/body|grep 2

4
Und was passiert, wenn ich das Zauberwort eingebe, nach dem grep sucht?
Schatten

3
luchs luchs luchs luchs luchs. Dieser Kommentar wird (und auch die Überschrift)
ausgeblendet

1
@hexafraction Awww. Du musstest gehen und es ruinieren!
Schatten

8
Diese URL sollte funktionieren. codegolf.stackexchange.com/posts/28164/bodyUnd es ignoriert Kommentare. Ich denke auch, dass es innerhalb der Regeln liegt, dass Sie es verwenden können ...
FDinoff

3
@DigitalTrauma awww ... verdammt.
Haneefmubarak

22

Ruby, 155 186 195 148 138 110 97 Zeichen

require'open-uri';puts open('http://codegolf.stackexchange.com/posts/28159/body').read[/req.+;/];

Ich musste es eine Zeile machen, da es sonst Zeilenumbrüche \nanstelle von tatsächlichen Zeilenumbrüchen ausgibt.

  • +31 Zeichen, da ich nicht bemerkt habe, dass einige Zeichen ausgeblendet wurden.
  • +9 Zeichen, um den lästigen Backslash loszuwerden.
  • Vielen Dank an Nathan Osman für das Speichern von 2 Zeichen und Ventero für das Speichern von 55 Zeichen (!!!), indem er die meisten der oben aufgeführten Korrekturen überflüssig gemacht hat.

Die Erklärung

Lassen Sie uns dies zuerst ein wenig verschönern. Allerdings muss ich in diesem Code eine etwas ... interessante Schreibweise verwenden. Ich kann in diesem Beitrag aus später erläuterten Gründen überhaupt keine Semikolons verwenden, daher verwende ich {SEMI}stattdessen Semikolons.

require 'open-uri'
resp = open('http://codegolf.stackexchange.com/posts/28159/body').read
puts resp.match(/req.+{SEMI}/){SEMI}

Okay, jetzt lass uns das durchgehen. Die ersten beiden Zeilen sind ziemlich selbsterklärend - sie rufen den HTML-Text dieser Antwort ab.

Nun ist die letzte Zeile hier die interessante. Sehen Sie das scheinbar nutzlose Semikolon am Ende des Codes? Es ist absolut erforderlich, und hier ist der Grund.

Zuerst resp.matchextrahiert der Code gedruckt werden. Die regexp es dafür verwendet , ist der Trick: /req.+{SEMI}/. Es ergreift den Anfang des Codes, REQuire'net/http'indem es nach req( rewürde mein REputation) sucht . Dann findet es das Ende des Codes, indem es nach einem Semikolon sucht! Da +es standardmäßig gierig ist, wird es so lange fortgesetzt, bis es das Semikolon findet, das das Ende des Codes kennzeichnet. Sehen Sie, warum ich keine Semikolons mehr verwenden kann?

Danach muss ich nichts mehr außer Acht lassen, da Ventero überhaupt nichts \mehr verwendet. Alles, was ich tun muss, ist das {AMPERSAND}Umrüsten in {AMPERSAND}amp{SEMI}, was einfach durch Entfernen des amp{SEMI}Teils erreicht werden kann. Dies ist aufgrund der neuen URL nicht mehr erforderlich. Danach wurde der ursprüngliche Code abgerufen! (Hinweis: Ich kann auch kein kaufmännisches Und verwenden, da dieses HTML-codiert wird, wodurch ein Semikolon erstellt wird.)


Einige Charaktere werden entkommen ..
Aditsu

1
@aditsu Gah; habe das nicht bemerkt. Fest.
Türklinke

Du wirst das hassen ... ein Backslash wird dupliziert. Es gibt auch einen Zeilenumbruch, aber das ist eine Kleinigkeit.
Aditsu

@aditsu Argh! : P Auch behoben. Die Newline-Sache ist wegen puts; es könnte aber mit printmeh behoben werden . Stellen Sie sich einfach vor, der Code enthält eine nachgestellte neue Zeile, obwohl SE diese nicht anzeigen kann.
Türklinke

1
Für den Link http://codegolf.stackexchange.com/a/28159würde das gleiche Ergebnis wie bei Ihnen geben und einige Zeichen sparen.
Mhmd

20

PowerShell - 69 62

(irm codegolf.stackexchange.com/posts/28236/body).div.pre.code

DOM in einer Shell. Nett!
Fregante

Benötigt irm kein Azure Rights Management? Ohne dieses Modul könnten Sie es mit Invoke-WebRequest machen.
Scott Leadley

@ScottLeadley irmist der Alias ​​für Invoke-RestMethodund wurde mit PowerShell v3 Core eingeführt. computerperformance.co.uk/powershell/powershell3-alias.htm
Rynant

10
Heiliger Strohsack. Eine PowerShell-Code-Golfantwort mit einer Länge in der Größenordnung der führenden Antworten. +1
Adam Maras

@AdamMaras Ha, ich weiß was du meinst! Es kommt jedoch gelegentlich vor. codegolf.stackexchange.com/a/26811/4565 und codegolf.stackexchange.com/a/21982/4565 waren nicht allzu weit von der Spitze entfernt.
Rynant

15

JavaScript - 123 122 101 95 92 91 87 86 114

with(new XMLHttpRequest)send(open(0,/\codegolf.stackexchange.com\posts\28175\body/,0)),alert(/w.*/.exec(response))

Läuft in der Konsole Ihres Webbrowsers auf dieser Seite. Getestet auf dem neuesten Chrome und Firefox .

Bearbeiten: +28 Bytes, um die vollständige Domain hinzuzufügen.

Firefox mag meinen Regex-URL-Trick mit diesem Update nicht mehr :(

Hier ist die regelbrechende 86-Byte-Lösung:

with(new XMLHttpRequest)send(open(0,/posts\28175\body/,0)),alert(/w.*/.exec(response))

Das hat mich beeindruckt. Mehrmals.
Fregante

1
@ bfred.it Ich habe gerade ein Byte mit einem interessanten regulären Ausdruck geschnitten. Ich hoffe, es macht dich noch einmal ehrfürchtig.
Nderscore

Wenn das Drucken auf der Konsole eine akzeptable Ausgabemethode ist, können Sie durch Entfernen der Warnung die Zeichen um 7 Zeichen verkürzen.
Tejas Kale

Außerdem müssen Sie gemäß der neuen Regel codegolf.stackexchange.com/der URL hinzufügen .
Tejas Kale

1
@TejasKale Nach allem, was ich gesehen habe, staunen die Leute über Lösungen, die / document.write / console.log eigentlich nicht benachrichtigen, die Antwort.
Nderscore

10

Ruby + Wget + Gunzip , 159 86 82 71

Mit Tipp von @FDinoff zu verwenden http://codegolf.stackexchange.com/posts/28173/body.

puts `wget -qO- codegolf.stackexchange.com/posts/28173/body`[/pu.*\]/]

Geprüft. Vielen Dank an @ace und @Bob für die Befehlszeilenoptimierung.


2
Sie können die Flags in wgetwie in kombinieren wget -qO- url. Außerdem benötigen Sie in bash keine doppelten Anführungszeichen für die URL, sodass dies möglicherweise auch für Sie funktioniert.
ace_HongKongIndependence

Sie können das weglassen http://.
Bob

6

CJam - 53

"codegolf.stackexchange.com/posts/28184/body"g54/1=);

Ich erstelle dieses Community-Wiki, da ich meine eigene Frage beantworte und nicht wirklich konkurrieren möchte: p
Danksagung an FDinoff für die URL-Auswahl.


Woot, +1 für Smiley im Code
Cruncher

1
@ Cruncher );sieht nicht zu smiley für mich ...
MD XF

5

Rebmu, 91 Zeichen

Aufgrund des Catch-22 muss ich posten, um die URL dieser Antwort zu erhalten. : - / Okay, verstanden.

paTSrd http://codegolf.stackexchange.com/a/28154[th<a name="28154">th<code>cpCto</code>]prC

Rebmu ist ein Dialekt von Rebol, und Sie können alles darüber lesen . Der äquivalente Rebol wäre hier:

parse to-string read http://codegolf.stackexchange.com/a/28154 [
    thru <a name="28154">
    thru <code>
    copy c to </code>
]
print c

Rebols PARSE ist eine Art hochkompetente Antwort auf RegEx. Es startet eine Parser-Position der Eingabe (die eine beliebige Reihe sein kann, einschließlich Strukturblöcken ... Binärdaten ... oder Zeichenfolgentypen) . Die Regeln sind eine Sprache für die Bewegung der Analyseposition.

Tags und URLs sind in der Sprache eigentlich nur Strings unter der Haube. Aber sie sind "gewürzt", und da Rebol dynamisch eingegeben wird, können Sie diesen Typ überprüfen. READ weiß beispielsweise, dass, wenn Sie eine URL-gewürzte Zeichenfolge angeben, diese an einen Schema-Handler gesendet werden sollte, um das Lesen durchzuführen. (In diesem Fall der für HTTP registrierte). Sie erhalten standardmäßig UTF-8-Bytes zurück. Daher verwenden wir to-string, um dies zu dekodieren und eine Reihe von Codepunkten in einer normalen Unicode-Zeichenfolge abzurufen.

Im Fall des Parser-Dialekts wird die Erkennung eines Tag-Typs nur als Zeichenfolge abgeglichen, die dem Tag ähnelt. THRU ist eine Anweisung mit der Bedeutung "Überspringen, bis die folgende Regel übereinstimmt, und dann die Übereinstimmungsposition an das Ende des gerade Übereinstimmungsgegenstands setzen". (TO ist das Analogon, das übereinstimmt, aber die Analyseposition vor dem Element belässt).

Also rasten wir an der <a name="28154">. Vorbei . Dann überqueren wir das nächste Vorkommen von <code>, wobei sich unsere Analyseposition jetzt direkt nach dem befindet >. Mit dem Befehl COPY von PARSE können wir dann Daten in eine andere Regel kopieren. In diesem Fall lautet diese Regel [TO </code>]..., sodass wir bis dahin alles in die Variable C aufnehmen <.

Cool , was? :-)

Technisch könnte ich mehr davon abschneiden, zum Beispiel durch Suchen TO "</"und das spart drei Zeichen - es ist nicht nötig, das gesamte </code>End-Tag abzugleichen, wenn es nur geht </. Ähnliche Argumente könnte ich für das Start-Tag machen. Aber Rebmu ist etwa gebildete Golf ... auch wenn Sie vielleicht denken , es auf den ersten seltsam aussieht!

UPDATE : Der /bodyTrick ist aus dem Sack, aber ich werde ihn genauso belassen wie er ist ... weil ich denke, dass er auf diese Weise lehrreicher ist.


5

Java jetzt 634, 852, war 1004

Code wurde aktualisiert; danke für vorschläge. Golf: Ersetzt jetzt & gt mit>

//bacchus
package golf;
import java.net.*;
import java.util.*;
public class G{
public static void main(String[] a) throws Exception {
Scanner z;
URL u;
int x=0;
String s;
u=new URL("http://codegolf.stackexchange.com/questions/28154/write-a-program-that-downloads-itself");
z=new Scanner(u.openConnection().getInputStream());
z.useDelimiter("\\s*//bacchus\\s*");
while(z.hasNext())
{
s=z.next();
s=s.replace("&gt;", ">");
if(x>0)System.out.println("//bacchus\n"+s);
x++;
if(x>2)break;
}
System.out.println("//bacchus\n");
}
}
//bacchus

Zum Testen einreichen, ich werde es in Kürze bearbeiten und ausprobieren. Muss x> 1 in x> 2 ändern, da die Testzeichenfolge auch in meinem Code enthalten ist. Hinweis: Code Golf ersetzt> Symbol zu & gt.

//bacchus
package golf;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class Golf {

    public static void main(String[] args) throws IOException {
        URL u;
        URLConnection c;
        InputStream i;
        InputStreamReader r;
        BufferedReader b;
        String s;
        int x=0;
        try {
            u=new URL("http://codegolf.stackexchange.com/questions/28154/write-a-program-that-downloads-itself");
            c=u.openConnection();
            i=c.getInputStream();
            r=new InputStreamReader(i);
            b=new BufferedReader(r);
            while((s=b.readLine())!=null)
            {
                if(s.contains("//bacchus")) x++;
                if(x>0)System.out.println(s);
                if(x>2) break;
            }
            i.close();
            b.close();
        } catch (MalformedURLException ex) {

        }
    }

}
//bacchus

7
Wie gehst du mit Kommentaren um //bacchus?
ζ--

3
Sie könnten viele Dinge einbinden, Ressourcen ausprobieren und *Importe verwenden, um viel Code zu sparen.
Simon Kuang

@ SimonKuang - Ich würde auch einfach die Streams offen lassen, anstatt das Zeug zu schließen. Auch throws Exceptionanstatt zu versuchen, irgendetwas zu handhaben. Ich denke auch, dass die Verwendung eines Scanners anstelle eines BufferedReader einfacher wäre, zumal Sie das Trennzeichen auf setzen könnten //bacchus, was die Dinge etwas einfacher machen würde ...
Jules

5

Python, 175 167 Bytes

Dies verwendet zwei externe Bibliotheken; Ich habe nicht gelesen, dass es nicht autorisiert ist.

import bs4,requests
print(bs4.BeautifulSoup(requests.get('http://codegolf.stackexchange.com/q/28154').text).select('#answer-28171')[0].select('pre > code')[0].string)

Längerer, aber schöner aussehender Code:

import bs4, requests
request = requests.get('http://codegolf.stackexchange.com/q/28154')
soup = bs4.BeautifulSoup(request.text)
answer = soup.select('#answer-28171')[0]
code = answer.select('pre > code')[1].string
print(code)

1
Die questionsin der URL kann ersetzt werden durch q:http://codegolf.stackexchange.com/q/28154
Justin

1
Das Leerzeichen in bs4, requests(Zeile 1) kann entfernt werden, um 1 Byte zu reduzieren.
ace_HongKongIndependence

5

JavaScript, 228

r=new XMLHttpRequest()
c='code'
r.open('GET','//'+c+'golf.stackexchange.com/posts/28157/body')
r.onreadystatechange=function(){this.readyState==4&&alert((a=r.responseText).substr(i=a.indexOf(c)+5,a.indexOf('/'+c)-i-1))}
r.send()

Läuft auf dieser Seite.


Wie machst du das?
Aditsu

@aditsu Es soll auf der JavaScript-Konsole eines Browsers ausgeführt werden. Aber ich
teste

@aditsu Es sollte jetzt funktionieren. Öffnen Sie Ihre Browserkonsole (drücken Sie F12) und fügen Sie diesen Code dort ein.
ace_HongKongIndependence

Sie, Sir, brauchen einen if(this.readyState == this.DONE)Einblick in die Funktion.
Fabricio

1
@ace Ich verstehe :) Ich habe die Antwort der anderen js bis jetzt nicht gesehen. Dann nimm diese Gegenstimme von mir
C5H8NNaO4

4

Haskell, 563613 Bytes

import Control.Monad
import Data.List
import Network.HTTP
m%f=join(fmap f m)
q s=(simpleHTTP(getRequest"http://codegolf.stackexchange.com/questions/28154/write-a-program-that-downloads-itself?answertab=oldest#tab-top"))%getResponseBody%(putStrLn.head.filter((==)(s++show s)).map(take 613).tails)
main=q"import Control.Monad\nimport Data.List\nimport Network.HTTP\nm%f=join(fmap f m)\nq s=(simpleHTTP(getRequest\"http://codegolf.stackexchange.com/questions/28154/write-a-program-that-downloads-itself?answertab=oldest#tab-top\"))%getResponseBody%(putStrLn.head.filter((==)(s++show s)).map(take 613).tails)\nmain=q"

Geprüft. Hat Seitenunterstützung über "älteste Beiträge" -Funktion. Verwendet eine Quine-Line-Struktur, um herauszufinden, was gedruckt werden soll. Das import Control.Monadliegt nur daran >>=, dass &gt;in HTML generiert wird .


4

Javascript + jQuery, 87 , 67

Ich bin nicht sicher, ob ich jQuery verwenden darf, aber:

$('body').load('//codegolf.stackexchange.com/posts/28268/body pre')

Javascript + jQuery, falls auf dieser Seite ausgeführt: 27 , 25

Zum Spaß, wenn es hier ausgeführt würde:

$('[id$=268] pre').html()

$('[id$=28268] pre').html()


1
Dies gibt mehr als den Quellcode aus.
nderscore

1
67:$('body').load('//codegolf.stackexchange.com/posts/28268/body pre')
nderscore

Sie haben Recht, ich habe fälschlicherweise die gesamte Antwort anstelle des Codes verwendet
Martijn


3

Dart, 164

Ich dachte, ich würde es in Dart versuchen, es macht Spaß, imo zu benutzen.

Dies kann in der Konsole in DartEditor ausgeführt werden, erfordert jedoch das in pubspec.yaml hinzugefügte http-Paket

import"package:http/http.dart"as h;h.read("http://codegolf.stackexchange.com/posts/28215/body").then((s){print(new RegExp(r"im.+(?:})").firstMatch(s).group(0));});}

Ungolfed-Version:

import "package:http/http.dart" as h;

void main()
{
  h.read("http://codegolf.stackexchange.com/posts/28215/body").then((s)
  {
    print(new RegExp(r"im.+(?:})").firstMatch(s).group(0));
  });
}

2

R 114 Zeichen

library(XML);cat(xpathSApply(xmlParse("http://codegolf.stackexchange.com/posts/28216/body"),'//code',xmlValue)[1])

Keine wirkliche Magie hier: Es nimmt den Wert des Feldes zwischen den HTML-Tags <code></code>. Benutzt die Bibliothek XML(wie man im Code ganz offensichtlich sehen kann). Gibt das Ergebnis als stdout aus.


1

Java 300, 294

import java.net.*;import java.util.*;public class G{public static void main (String [] a) throws Exception{Scanner s=new Scanner(new URL("http://codegolf.stackexchange.com/posts/28189/body").openConnection().getInputStream()).useDelimiter("./?[c]ode\\W");s.next();System.out.print(s.next());}}

Eine verbesserte Version der Antwort von Bacchusbeale, die:

  • Schließt Ressourcen nicht unnötig
  • deklariert keine unnötigen Variablen
  • verwendet a Scanner, um eine Schleife über den Eingang zu vermeiden
  • verwendet einen regulären Ausdruck, der nicht mit sich selbst übereinstimmt, um zu vermeiden, dass ein mittleres Vorkommen des Start- / End-Markers übersprungen werden muss.

Aktualisiert:

  • Verwenden Sie eine direkte URL zum Beitrag, damit wir keinen eindeutigen Kommentar benötigen, um den Anfang / das Ende des Codes zu identifizieren. verwendet nun <code>[...]</code>als Begrenzer für die Suche (tatsächlich unter Verwendung des regulären Ausdrucks "./?[c[ode\W"), um zu vermeiden, dass dekodiert werden muss &lt;und &gt;- das "\ W" anstelle des kürzeren "erforderlich ist." um zu vermeiden, dass ein Teil der URL mit dem Beitrag übereinstimmt, der leider 2 Zeichen kostet, und die eckigen Klammern um c verhindern, dass der reguläre Ausdruck selbst übereinstimmt).

1
Sie haben eine Menge unnötiger Leerzeichen. Auch Ihre Klasse muss nicht öffentlich sein.
Aditsu

1
. openconnection () getInputStream () kann auch auf openstream verkürzt werden ()
aditsu

1

W3M 55 Byte

w3m codegolf.stackexchange.com/posts/28242/body|grep x

Basierend auf @DigitalTrauma


1

Ruby, 237 215 146 132

require'mechanize'
a=Mechanize.new
puts a.get('http://codegolf.stackexchange.com/a/28159').search('.lang-rb code:nth-child(1)').text

Ziemlich sicher, dass Sie hier und da ein paar Leerzeichen entfernen können, um ein paar Bytes zu sparen.
MisterBla

@richard wen interessiert es, ich werde sowieso nicht gewinnen.
Mhmd

1
Tun Sie es für die Lols, nicht fürs Gewinnen.
MisterBla

@RichardA erledigt, und ich habe auch ein paar Zeichen aus dem regulären Ausdruck entfernt.
Mhmd

1

Verarbeitung, 90

print(loadStrings("http://codegolf.stackexchange.com/posts/28657/body")[2].substring(11));

Edit: Endlich ist es soweit!



0

Javascript, 138

a=window.open("http://codegolf.stackexchange.com/posts/28160/body");setTimeout('alert(a.document.body.innerHTML.match(/a=.*9\\)/)[0])',99)

Dies funktioniert unter der Annahme, dass die Seite in weniger als 99 ms geladen wird. Es muss auch über eine Konsole ausgeführt werden, die auf einer codegolf.SE-Seite geöffnet ist, da dieselbe Ursprungsrichtlinie gilt.


Nur eine Anmerkung: Sie brauchen den Slug in der URL nicht und Fragen können durch q ersetzt werden.
Schism

1
Beachten Sie, dass Sie http://codegolf.stackexchange.com/a/28160anstelle vonhttp://codegolf.stackexchange.com/a/28160/12551
Justin

Chrome gefällt das nicht: "Uncaught TypeError: Eigenschaft 'document' von undefined kann nicht gelesen werden"
Spedwards

@Spedwards sollten Sie den Popup-Blocker deaktivieren.
Nderscore

0

Perl 5.10, 155 127 122 117 Bytes

use XML::LibXML;say XML::LibXML->new->parse_file('http://codegolf.stackexchange.com/posts/28330/body')->find('//pre')

Verwenden XML::LibXML.


0

Shell und xmllint, 82 Bytes

xmllint --xpath 'string(//pre)' http://codegolf.stackexchange.com/posts/28333/body

0

Python, 164

Funktioniert durch Extrahieren des Texts zwischen den Code-Tags. Es ist ziemlich lang, aber es funktioniert immer richtig, es sei denn, die HTML-Seite wird direkt bearbeitet oder ein neuer Codeblock wird vor dem folgenden hinzugefügt (ein Codeblock danach sollte keine Auswirkung auf die Ausgabe des Programms haben).

import urllib2
print urllib2.urlopen("http://codegolf.stackexchange.com/posts/28617/body").read().split(chr(60)+"code"+chr(62))[1].split(chr(60)+"/code"+chr(62))[0]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.